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