计算程序运行时间(time_t, clock_t)
轉載自:http://blog.chinaunix.net/uid-23208702-id-75182.html
計算程序運行時間(time_t, clock_t)-whyliyi-ChinaUnix博客
我們有時需要得到程序的運行時間,但我們也要知道,根本不可能精確測量某一個程序運行的確切時間 -[3] ,文獻 [4] 中說的很明白,現摘錄如 下。
我們平時常用的測量運行時間的方法并不是那么精確的,換句話說,想精確獲取程序運行時間并不是那么 容易的。也許你會想,程序不就是一條條指令么,每一條指令序列都有固定執行時間,為什么不好算?真實情況下,我們的計算機并不是只運行一個程序的,進程的 切換,各種中斷,共享的多用戶,網絡流量,高速緩存的訪問,轉移預測等,都會對計時產生影響。文獻 [4] 中還提到:對于進程調度來 講,花費的時間分為兩部分,第一是計時器中斷處理的時間,也就是當且僅當這個時間間隔的時候,操作系統會選擇,是繼續當前進程的執行還是切換到另外一個進 程中去。第二是進程切換時間,當系統要從進程 A 切換到進程 B 時,它必須先進入內核模式將進程 A 的狀態 保存,然后恢復進程 B 的狀態。因此,這個切換過程是有內核活動來消耗時間的。具體到進程的執行時間,這個時間也包括內核 模式和用戶模式兩部分,模式之間的切換也是需要消耗時間,不過都算在進程執行時間中了。那么有哪些方法能統計程序的運行時間呢?通過查找一些資料并結合自己的實踐體會,摘錄和總結了下面 幾種方法。
一、 Linux 的 time 命令
Linux 系統下統計程序運行實踐最簡單直接的方法就是使用 time 命令,文獻 [1, 2] 中詳細介紹了 time 命令的用法。此命令的用途在于測量特定指令執行時所需消耗的時間及系統資源等資訊,在統計的時間結 果中包含以下數據:***(1) 實際時間( real time ):從命令行執行到運行終止的消逝時間;(2) 用戶 CPU 時間( user CPU time ):命 令執行完成花費的系統 CPU 時間,即命令在用戶態中執行時間的總和;(3) 系統 CPU 時間( system CPU time ): 命令執行完成花費的系統 CPU 時間,即命令在核心態中執行時間的總和。其中,用戶 CPU 時 間和系統 CPU 時間之和為 CPU 時 間,即命令占用 CPU 執行的時間總和。實際時間要大于 CPU 時 間,因為 Linux 是多任務操作系統,往往在執行一條命令時,系統還要處理其他任務。另一個需要注意的問題是即使每次 執行相同的命令,所花費的時間也不一定相同,因為其花費的時間與系統運行相關。***
二、間隔計數 [4]
上面介紹的 time 命 令能測量特定進程執行時所消耗的時間,它是怎么做到的呢?操作系統用計時器來記錄每個進程使用的累計時間,原理很簡單,計時器中斷發生時,操作系統會在當前 進程列表中尋找哪個進程是活動的,一旦發現進程 A 正在運行立馬就給進程 A 的計數值增加計時器的時間間隔(這也是引起較大誤差的原因)。當然不是統一增加的,還要確定這個進程 是在用戶空間活動還是在內核空間活動,如果是用戶模式,就增加用戶時間,如果是內核模式,就增加系統時間。這種方法的原理雖然簡單但不精確。如果一個進程 的運行時間很短,短到和系統的計時器間隔一個數量級,用這種方法測出來的結果必然是不夠精確的,頭尾都有誤差。不過,如果程序的時間足夠長,這種誤差有時 能夠相互彌補,一些被高估一些被低估,平均下來剛好。從理論上很難分析這個誤差的值,所以一般只有程序達到秒的數量級時用這種方法測試程序時間才有意義。這種方法最大的優點是它的準確性不是非常依賴于系統負載。實現方法之一就是上面介紹的 time 命 令,之二是使用 tms 結構體和 times 函 數。在 Linux 中,提供了一個 times 函數,原型是clock_t times( struct tms * buf );
這個 tms 的結構體為
struct tms
{
clock_t tms_utime; //user timeclock_t tms_stime; //system timeclock_t tms_cutime; //user time of reaped childrenclock_t tms_cstime; //system time of reaped children
}
這里的 cutime 和 cstime ,都是對已經終止并回收的時間的累計,也就是說, times 不能監視任何正在進行中的子進程所使用的時間。使用 times 函數需要包含頭文件 sys/times.h 。
三、周期計數 [4]
為了給計時測量提供更高的準確度,很多處理器還包含一個運行在始終周期級別的計時器,它是一個特殊 的寄存器,每個時鐘周期它都會自動加 1 。這個周期計數器呢,是一個 64 位無 符號數,直觀理解,就是如果你的處理器是 1GHz 的,那么需要 570 年,它才會從 2 的 64 次方繞回到 0 ,所以 你大可不必考慮溢出的問題。但是這種方法是依賴于硬件的。首先,并不是每種處理器都有這樣的寄存器的;其次,即使大多數都有,實現機制也不一樣,因此,我 們無法用統一的,與平臺無關的接口來使用它們。這下,就要使用匯編了。當然,在這里實際用的是 C 語言 的嵌入匯編:
- void counter( unsigned *hi, unsigned *lo ){asm(”rdtsc; movl %%edx,%0; movl %%eax, %1″
- “=r” (*hi), “=r” (*lo)
: - “%edx”, “%eax”);
}
第一行的指令負責讀取周期計數器,后面的指令表示將其轉移到指定地點或寄存器。這樣,我們將這段代碼封裝到函數中,就可以在需要測量 的代碼前后均加上這個函數即可。最后得到的 hi 和 lo 值都是兩個,除了相減得到間隔值外,還要進行一些處理,在此 不表。
不得不提出的是,周期計數方式還有一個問題,就是我們得到了 兩次調用 counter 之間總的周期數,但我們不知道是哪個進程使用了這些周期,或者說處理器是在內核還是在用戶模式 中。間隔計數的好處就是它是操作系統控制給進程計時的,我們可以知道具體哪個進程呢個模式;但是周期計數只測量經過的時間,他不管是哪個進程使用的。所 以,用周期計數的話必須很小心。舉個例子:double time(){start_counter();p();get_counter();
}
這樣一段程序,如果機器的負載很重,會導致 p 運行 時間很長,而其實 p 函數本身是不需要運行這么長時間的,而是上下文切換等過程將它的時間拖長了。
而且,轉移預測和高速緩存的命中率,對這個計數值也會有影響。通常情況下,為了減少高速緩存不命中 給我們程序執行時間帶來的影響,可以執行這樣的代碼:double time_warm(void){p();start_counter();p();get_counter();
}
它讓指令高速緩存和數據高速緩存都得到了 warm-up 。
接下來又有問題。如果我們的應用是屬于那種每次執行都希望訪問新的數據的那種呢?在這種情況下,我 們希望讓指令高速緩存 warm-up ,而數據高速緩存不能 warm-up ,很明顯, time-warm 函數低估我們的運行時間了。進一步修改:double time_cold( void ){p();clear_cache();start_counter();p();get_counter();
}
注意,程序中加入了一個清除數據緩存的函數,這個函數的具體實現很簡 單,依情況而定,比如舉個例子:
volatile int tmp;static int dummy[N]; //N 是需要清理緩存的字節數void clear_cache( void ){int i, sum = 0;for( i=1; idummy[i] = 2;for( i=1; isum += dummy[i];tmp = sum;
}
具體原理很簡單,定義一個數組并在其上執行一個計算,計算過程中的數據會覆蓋高速數據緩存中原有的數 據。每一次的 store 和 load 都會讓高速數據緩存 cache 這個數組,而定義為 volatile 的 tmp 則保證這段代碼不會被優化。
這樣做,是不是就萬無一失了呢?不是的,因為大多數處理器, L2 高速緩存是不分指令和數據的,這樣 clear_cache 會讓所有 p 的指令也被清除,只不過: L1 緩存中的指令還會保留而已。其實上面提到的諸多原因,都是我們不能控制的,我們無法控制讓高速緩存去加載什么,不去加載什么, 加載時去掉什么。保留什么。而且,這些誤差通常都是會過高估計真實的運行時間。那么具體使用時,有沒有什么辦法來改善這種情況呢?有,就是 The K-Best Measurement Scheme 。這其實很麻煩,所以在具體實踐中都不用它。
四、 gettimeofday 函數計時 [4]
gettimeofday 是一個庫函數,包含在 time.h 中。它的功能是查詢系統時鐘,以確定當前的日期和時間。相對于間隔計數的小適用范圍和周期計數的麻煩性, gettimeofday 是一個可移植性更好相對較準確的方法。它的原型如下:struct timeval{long tv_sec; // 秒 域long tv_usec; // 微妙域
}
int gettimeofday( struct timeval *tv, NULL);
這個機制呢,具體的實現方式在不同系統上是不一樣的,而且具體的精確程度是和系統相關的:比如在 Linux 下,是用周期計數來實現這個函數的,所以和周期計數的精確度差不多,但是在 Windows NT 下,是使用間隔計數實現的,精確度就很低了。具體使用,就是在要計算運行時間的程序段之前和之后分別加上 gettimeofday( &tvstart, NULL) 、 gettimeofday( &tvend, NULL) ,然后計算:
(tvend.tv_sec-tvstart.tv_sec)+(tvend.tv_usec-tvstart.tv_usec)/1000000
就得到了以秒為單位的計時時間。
五、 clock 函數
clock 也是一個庫函數,仍然包含在 time.h 中,函數原型是:clock_t clock( void );
功能:返回自程序開始運行的處理器時間,如果無可用信息,返回 -1 。轉換返回值若以秒計需除以 CLOCKS_PER_SECOND 。(注:如果編譯器是 POSIX 兼 容的, CLOCKS_PER_SECOND 定義為 1000000 。) [5]
使用 clock 函數也比較簡單:在要計 時程序段前后分別調用 clock 函數,用后一次的返回值減去前一次的返回值就得到運行的處理器時間,然后再轉換為秒。舉例如下:clock_t starttime, endtime;double totaltime;starttime = clock();…endtime = clock();totaltime = (double)( (endtime - starttime)/(double)CLOCKS_PER_SEC );
六、 time 函數
在 time.h 中還包含另一個時間函 數: time 。文獻 [6] 對其進行了詳細的介紹。通 過 time() 函數來獲得日歷時間( Calendar Time ),其原型為: time_t time( time_t * timer ) 。通過 difftime 函數可以計算前后 兩次的時間差: double difftime( time_t time1, time_t time0 ) 。用 time_t 表示的時間(日歷時 間)是從一個時間點(例如: 1970 年 1 月 1 日 0 時 0 分 0 秒)到此時的秒數,則此函數的前后兩次時間差也是以秒為單位。比如:time_t startT, endT;double totalT;startT = time( NULL );…endT = time( NULL );totalT = difftime( startT, endT);關于此函數的其他應用請參見文獻 [6] 。
總結:
使用相應的方法,調用相應的函數,還需要關注它們可以表示的范圍和精度,這樣才能“挑肥揀瘦”。先 來看看時間函數中經常用到的兩個數據類型的定義:// clock_t 的定義
#ifndef _CLOCK_T_DEFINED
typedef long clock_t;
#define _CLOCK_T_DEFINED
#endif
// time_t 的定義
#ifndef _TIME_T_DEFINED
typedef long time_t;
#define _TIME_T_DEFINED
#endif
long 型數據的取值范圍是 -2147483648 ~ +2147483647 。所以, gettimeofday 函數取得的時間最大值為 2147483647 + 2147483647 / 1000000 = 2147485794.483647 s ,大約為 68.096 年; clock 函 數取得的時間最大值為 2147483647 / 1000000 = 2147.483647 s ,大約為 35.79 分 鐘;
time 函數取得的時間最大值為 2147483647 s ,大約為 68 年。
這里只是介紹 Linux 平臺下 c 語言中計算程序運行時間的方法, 它們各有利弊,依據自己的需要可以使用對應的方法。在 Windows 平臺下還有其他計算程序運 行時間的方法,在此不敘。
參考文獻
[1] “ linux time 命令詳解”, http://www.admin99.net/read.php/185.htm ;
[2] “ Linux 命令詳解—— time ”,
http://blog.csdn.net/thinkerABC/archive/2006/04/01/647272.aspx ;
[3] “測量程序運行時間的幾種方法”, http://oss.lzu.edu.cn/blog/article.php?tid_905.html ;
[4] “如何精確測量程序運行時間”, http://www.forwind.cn/2008/05/10/measure-time-preciely/ ;
[5] “ clock ”, http://blog.csdn.net/xxyakoo/archive/2008/12/17/3539590.aspx ;
[6] “ c 語言對時間的處理函數和計時的實 現”,
http://blog.csdn.net/adm_qxx/archive/2007/05/02/1594788.aspx 。
總結
以上是生活随笔為你收集整理的计算程序运行时间(time_t, clock_t)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于Blocking IO, Non-B
- 下一篇: C语言编程技巧-signal(信号机制)