Linux下clock計(jì)時(shí)函數(shù)學(xué)習(xí)
平時(shí)在Linux和Winows下都有編碼的時(shí)候,移植代碼的時(shí)候免不了發(fā)現(xiàn)一些問(wèn)題。
1. 你到底準(zhǔn)不準(zhǔn)?關(guān)于clock()計(jì)時(shí)函數(shù)
首先是一段簡(jiǎn)單的測(cè)試代碼,功能為測(cè)試從文本文件讀取數(shù)據(jù)并賦值給向量最后打印輸出的運(yùn)行時(shí)間。
int main(int argc, char **argv)
{
clock_t t1=clock();
ifstream in("data.txt");
vector<int> v;
for(int a;in>>a;v.push_back(a));
cout<<v.size()<<endl;
for(int i=0;i<v.size();i++)
cout<<v[i]<<" ";
cout<<endl;
clock_t t2=clock();
cout<<"TotalTime:"<<t2-t1<<"ms"<<endl;
}
這段代碼中用了兩個(gè)clock_t類型的clock()函數(shù)來(lái)計(jì)算程序運(yùn)行時(shí)間,
在windows下編譯運(yùn)行,
TotalTime:465ms
在Linux下編譯運(yùn)行,
TotalTime:420000ms
ok,問(wèn)題來(lái)了,除了正常碼農(nóng)都能理解的系統(tǒng)誤差原因,明顯是由于clock()函數(shù)在不同平臺(tái)下的返回值不同原因造成的,查clock函數(shù)定義有下面的描述:
clock returns the processor time used by program since the beginning of the execution, or -1 if unavailable.
這里提到clock()函數(shù)返回的是程序運(yùn)行過(guò)程中耗掉得process time,也就是CPU time。
你以為它返回的是一個(gè)標(biāo)準(zhǔn)時(shí)間單位,你錯(cuò)了,因?yàn)檫€有下一句描述:
clock() / CLOCKS_PER_SEC is a time in seconds.
CLOCKS_PER_SEC,它用來(lái)表示一秒鐘會(huì)有多少個(gè)時(shí)鐘計(jì)時(shí)單元,也就是硬件滴答數(shù)。
先不管什么叫硬件滴答數(shù),你需要知道clock()是基于時(shí)鐘計(jì)時(shí)單元(clock tick)的這個(gè)東西來(lái)計(jì)數(shù)的。一個(gè)clock tick不是CPU的一個(gè)時(shí)鐘周期,而是C/C++的一個(gè)基本計(jì)時(shí)單位,因此只與編譯器有關(guān)。在TC2.0中硬件每18.2個(gè)滴答是一秒,在VC中硬件每1000個(gè)滴答是一秒,在標(biāo)準(zhǔn)POSIX中定義為1000000個(gè)滴答為一秒。
ok,知道了這么多,要想讓程序正確運(yùn)行輸出,并保證多平臺(tái)的兼容性,代碼作如下處理:
cout<<"TotalTime:"<<(double)( t2-t1)/CLOCKS_PER_SEC<<"s"<<endl;
很簡(jiǎn)單,程序正確輸出之后,你迫不及待的多次運(yùn)行測(cè)試,發(fā)現(xiàn)無(wú)論如何你的程序都是一個(gè)精確到10毫秒的整數(shù),真是因?yàn)槊看味寄敲辞擅???jīng)搜索發(fā)現(xiàn),標(biāo)準(zhǔn)POSIX平臺(tái)的clock()只能精確到10ms,至于為什么我目前還不得而知。
2. 做個(gè)理性的偏執(zhí)狂,關(guān)于系統(tǒng)計(jì)時(shí)誤差
如果你是一個(gè)偏執(zhí)狂,當(dāng)你正為使用clock()函數(shù)丟失了幾毫秒而懊惱不已時(shí),我卻你還是回家陪你老婆逛逛街吧。因?yàn)榫_測(cè)量某一個(gè)程序運(yùn)行的確切時(shí)間是根本不可能的,至少現(xiàn)在是這樣。我不厭其煩的從其他文章中找到這樣的一些話,希望對(duì)你理解為何有誤差有所幫助。
文章中提到“我們平時(shí)常用的測(cè)量運(yùn)行時(shí)間的方法并不是那么精確的,換句話說(shuō),想精確獲取程序運(yùn)行時(shí)間并不是那么容易的。也許你會(huì)想,程序不就是一條條指令么,每一條指令序列都有固定執(zhí)行時(shí)間,為什么不好算?真實(shí)情況下,我們的計(jì)算機(jī)并不是只運(yùn)行一個(gè)程序的,進(jìn)程的切換,各種中斷,共享的多用戶,網(wǎng)絡(luò)流量,高速緩存的訪問(wèn),轉(zhuǎn)移預(yù)測(cè)等,都會(huì)對(duì)計(jì)時(shí)產(chǎn)生影響。
對(duì)于進(jìn)程調(diào)度來(lái)講,花費(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è)切換過(guò)程是有內(nèi)核活動(dòng)來(lái)消耗時(shí)間的。具體到進(jìn)程的執(zhí)行時(shí)間,這個(gè)時(shí)間也包括內(nèi)核模式和用戶模式兩部分,模式之間的切換也是需要消耗時(shí)間,不過(guò)都算在進(jìn)程執(zhí)行時(shí)間中了”。
ok,讀完上面這段話,你應(yīng)該很清楚的知道一個(gè)程序即使每次執(zhí)行相同的命令,所花費(fèi)的時(shí)間也不一定相同,因?yàn)槠浠ㄙM(fèi)的時(shí)間與系統(tǒng)運(yùn)行相關(guān)。原因就在于無(wú)論是windows還是linux,都是多任務(wù)操作系統(tǒng)。為了讓你更明白,現(xiàn)在從另一個(gè)角度對(duì)執(zhí)行一個(gè)程序所消耗的時(shí)間進(jìn)行分類,如下:
(1) 實(shí)際運(yùn)行時(shí)間(real time):從命令行執(zhí)行到運(yùn)行終止的消逝時(shí)間;
(2) 用戶CPU時(shí)間(user CPU time):命令在用戶態(tài)中執(zhí)行時(shí)間的總和;
(3) 系統(tǒng)CPU時(shí)間(system CPU time):命令在系統(tǒng)核心態(tài)中執(zhí)行時(shí)間的總和。
現(xiàn)在有沒(méi)有這樣的疑問(wèn),實(shí)際運(yùn)行時(shí)間(1)是不是等于(2)+(3)?對(duì)于一個(gè)進(jìn)程而言,除了用戶和系統(tǒng)之外難道還有別人么?
答案是肯定的。抬頭看看上面提到的進(jìn)程調(diào)度,回頭想想你大二學(xué)的操作系統(tǒng)課程里的時(shí)間片輪轉(zhuǎn),進(jìn)程五狀態(tài),明白了吧,此時(shí)的程序表面在給你執(zhí)行代碼,CPU早跑別人家去了。
ok,現(xiàn)在回頭看看開頭那段代碼的運(yùn)行結(jié)果,clock()算出的時(shí)間究竟屬于上面的哪一個(gè)呢?為此我們?cè)O(shè)計(jì)如下一個(gè)實(shí)驗(yàn):
該段代碼為測(cè)試執(zhí)行一億次空循環(huán)所消耗的時(shí)間
int main( void )
{
clock_t start, finish;
double duration;
long i,j;
start = clock();
for( i=0;i<100;i++){
for( j=0;j<1000000;j++);
}
finish = clock();
duration = (double)(finish- start) / CLOCKS_PER_SEC;
printf( "Time to do %ld empty loops is ",i*j);
printf( "%f seconds\n",duration );
return 0;
}
編譯連接
g++ test.cpp -o test
運(yùn)行程序
./test
顯示結(jié)果
Time to do 100000000 empty loops is 0.290000 seconds
僅這條結(jié)果當(dāng)然證明不了什么,好吧,我們?cè)谄渲屑右痪?/p>
sleep(5); // 需要包含頭文件<unistd.h>
sleep函數(shù)會(huì)使程序暫時(shí)掛起,也就是阻塞狀態(tài),在這5s內(nèi),CPU并不在該進(jìn)程上花費(fèi)時(shí)間。如果最后顯示的時(shí)間大于5s的話,可以證明clock函數(shù)計(jì)算的是程序?qū)嶋H運(yùn)行時(shí)間,如果依然接近290ms的話,則是CPU運(yùn)行時(shí)間。
運(yùn)行程序 ./test
顯示結(jié)果
Time to do 100000000 empty loops is 0.290000 seconds
一樣!現(xiàn)在證明了clock函數(shù)是計(jì)算的CPU時(shí)間,聰明且記憶力好的你此時(shí)會(huì)發(fā)現(xiàn)我有欺騙大眾智商的嫌疑,clock的定義里
明確之處計(jì)算的是processor time,不就是CPU時(shí)間啊,好吧,我就算設(shè)計(jì)一個(gè)實(shí)驗(yàn)證實(shí)了這個(gè)說(shuō)法沒(méi)錯(cuò),但究竟clock函數(shù)計(jì)算的是哪個(gè)CPU時(shí)間,暫時(shí)是無(wú)能為力證明了。
偏執(zhí)狂你的開始不滿意了,究竟有沒(méi)有辦法分別統(tǒng)計(jì)一個(gè)程序的這三樣時(shí)間呢?答案是有的。
Linux下有一個(gè)很簡(jiǎn)單很好用的命令就可以來(lái)統(tǒng)計(jì)程序運(yùn)行的這三種時(shí)間——time命令。此命令的用途在于測(cè)量特定指令執(zhí)行
時(shí)所需消耗的時(shí)間及系統(tǒng)資源等資訊。如果你只是在優(yōu)化一小段精簡(jiǎn)的代碼,這對(duì)你來(lái)說(shuō)是個(gè)好消息。
下面用time命令運(yùn)行上面的那段測(cè)試代碼:
運(yùn)行shell命令
time ./test
顯示結(jié)果
Time to do 100000000 empty loops is 0.290000 seconds
real 0m0.915s
user 0m0.031s
sys 0m0.266s
現(xiàn)在簡(jiǎn)單分析結(jié)果,終于真相大白了。上面程序中使用clock()函數(shù)計(jì)算出來(lái)的時(shí)間就為總的CPU時(shí)間。也就是說(shuō),clock函數(shù)不能區(qū)分用戶空間和內(nèi)核空間。
上面介紹的time命令能測(cè)量特定進(jìn)程執(zhí)行時(shí)所消耗的時(shí)間,它是怎么做到的呢?方法叫做間隔計(jì)數(shù)。如果你對(duì)他感興趣的話,
可以繼續(xù)讀讀下面的介紹,你會(huì)發(fā)現(xiàn)原理其實(shí)很簡(jiǎn)單。
操作系統(tǒng)用計(jì)時(shí)器來(lái)記錄每個(gè)進(jìn)程使用的累計(jì)時(shí)間,計(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è)出來(lái)的結(jié)果必然是不夠精確的,頭尾都有誤差。不過(guò),如果程序的時(shí)間足夠長(zhǎng),這種誤差有時(shí)能夠相互彌補(bǔ),一些被高估一些被低估,平均下來(lái)剛好。從理論上很難分析這個(gè)誤差的值,所以一般只有程序達(dá)到秒的數(shù)量級(jí)時(shí)用這種方法測(cè)試程序時(shí)間才有意義。這種方法最大的優(yōu)點(diǎn)是它的準(zhǔn)確性不是非常依賴于系統(tǒng)負(fù)載。
3. 事實(shí)果真如此么?關(guān)于”三多“話題
多核多進(jìn)程多線程技術(shù)的發(fā)展使得運(yùn)算效率不斷提高的同時(shí),也給碼農(nóng)們帶來(lái)新的煩惱。
3.1 多核計(jì)算
上面已經(jīng)知道了clock函數(shù)的實(shí)現(xiàn)是基于時(shí)鐘計(jì)時(shí)單元的。問(wèn)題就出在了cpu的時(shí)鐘計(jì)時(shí)單元上。當(dāng)采用多核cpu時(shí),進(jìn)程或線程調(diào)
用clock,記錄了當(dāng)前核時(shí)鐘。但在下次調(diào)用clock之前很可能發(fā)生cpu調(diào)度,進(jìn)程或線程被調(diào)度到其他cpu上運(yùn)行。這導(dǎo)致兩次取得
計(jì)時(shí)單元并不是同一個(gè)cpu的,產(chǎn)生計(jì)時(shí)錯(cuò)誤。但究竟這個(gè)誤差有多大,有待實(shí)驗(yàn)論證。
3.2 多進(jìn)程計(jì)算
上面通過(guò)time函數(shù)進(jìn)行了驗(yàn)證,clock函數(shù)計(jì)算的時(shí)間貌似是等于用戶CPU時(shí)間+系統(tǒng)CPU時(shí)間。果真如此么?我們?cè)俅螌?duì)測(cè)試代碼
進(jìn)行修改,將循環(huán)次數(shù)減少至一千次,并在空循環(huán)里加一句
system("cd");
這一句用于模擬子進(jìn)程的運(yùn)行,這里圖方便選擇系統(tǒng)進(jìn)程作為子進(jìn)程。
運(yùn)行shell命令
time ./test
顯示結(jié)果
Time to do 1000 empty loops is 0.010000 seconds
real 0m3.492s
user 0m0.512s
sys 0m2.972s
這個(gè)實(shí)驗(yàn)說(shuō)明了,clock函數(shù)并沒(méi)有考慮CPU被子進(jìn)程消耗的時(shí)間。
3.3 多線程計(jì)算
上面提到了三個(gè)時(shí)間Real time, User time和Sys time。real time > user time + sys time 這種關(guān)系始終成立么?
答案是否定的。原因就在于并行計(jì)算。現(xiàn)在再次回憶一下這三種時(shí)間的概念:
Real指的是實(shí)際經(jīng)過(guò)的時(shí)間,User和Sys指的是該進(jìn)程使用的CPU時(shí)間。
1. Real是墻上時(shí)間(wall clock time),也就是進(jìn)程從開始到結(jié)束所用的實(shí)際時(shí)間。這個(gè)時(shí)間包括其他進(jìn)程使用的時(shí)間片和進(jìn)程阻塞的時(shí)間(比如等待I/O完成)。
2. User指進(jìn)程執(zhí)行用戶態(tài)代碼(核心之外)所使用的時(shí)間。這是執(zhí)行此進(jìn)程所消耗的實(shí)際CPU時(shí)間,其他進(jìn)程和此進(jìn)程阻塞的時(shí)間并不包括在內(nèi)。
3. Sys指進(jìn)程在內(nèi)核態(tài)消耗的CPU時(shí)間,即在內(nèi)核執(zhí)行系統(tǒng)調(diào)用所使用的CPU時(shí)間。
那么,什么情況下進(jìn)程開始到結(jié)束所經(jīng)過(guò)的時(shí)間會(huì)比進(jìn)程所消耗的用戶時(shí)間和系統(tǒng)時(shí)間(user time + sys time)小呢?
User+Sys為進(jìn)程所使用的實(shí)際CPU時(shí)間。在多處理器的系統(tǒng)上,一個(gè)進(jìn)程如果有多個(gè)線程或者有多個(gè)子進(jìn)程并行執(zhí)行,就可能導(dǎo)致Real time比CPU time(User + Sys time)要小,這是很容易理解的。
原文地址:https://www.cnblogs.com/wfwenchao/p/5195022.html
*博客內(nèi)容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀點(diǎn),如有侵權(quán)請(qǐng)聯(lián)系工作人員刪除。