Linux 中的計時

Augusdi發表於2016-08-28

本文描述了Linux系統中一些與計時相關的問題和解決方法。因為在學習和研究的過程中我們經常需要統計程式或程式段的耗時,評估它們的效能好壞。因而這些問題對於我們來說,經常會遇到。掌握多種計時方法對於開發人員或科研工作者來說都是必須掌握的一項技能。本文解決了在Linux計時當中經常會遇到的一些技術問題,相信會對他人的工作有所幫助。

實際上,可能還會有其它一些方法可以完成本文討論的任務。我僅討論了我所使用的方法,這並不意味著除此之外的其它方法就很差勁,只不過對我來說這些方法相對簡單有效而已。

Linux中的時間

在Linux系統中,時間扮演著一個非常重要的角色,它幾乎無處不在。開機時,會顯示如下的資訊:

Last login: Tue Sep 23 22:12:50 from 192.168.6.100

關機時,我們可以使用shutdown命令指定何時或多長時間後機器將會定時關閉。我們有可能還會設定一臺Linux時間伺服器與網際網路上的一級或二級時間伺服器同步,總之,在Linux系統中,我們必須要了解時間。

實際上,linux系統有著自己的計時器時鐘。可以實驗一下,分別執行date和/sbin/clock(或sbin/hwclock)命令,得到的時間是不同的。

[grandiose@Bygone grandiose]$ date
Sun Sep 28 21:11:02 EDT 2003
[grandiose@Bygone grandiose]$ /sbin/clock
Sun 28 Sep 2003 09:07:07 PM EDT  -0.466994 seconds

當你以 root 身份改變了系統時間之後,請記住以 clock -w 來將系統時間寫入 CMOS 中。


使用C語言進行計時

在使用者空間中可以使用C語言函式gettimeofday 得到時間,它的呼叫格式是:

#include <sys/time.h> 
int gettimeofday(struct timeval *tv, struct timezone *tz); 
int settimeofday(const struct timeval *tv , const struct timezone *tz);
	結構timeval的定義為:
strut timeval {
long tv_sec; /* 秒數 */
long tv_usec; /* 微秒數 */
};

可以看出,使用這種方式計時,精度可達微秒,也就是10-6秒。進行計時的時候,我們需要前後呼叫兩次gettimeofday,然後計算中間的差值:

gettimeofday( &start, NULL );
foo(); 
gettimeofday( &end, NULL );
timeuse = 1000000 * ( end.tv_sec - start.tv_sec ) + end.tv_usec - start.tv_usec; 
timeuse /= 1000000;

Shell計時

在Linux的Shell下,我們經常也使用Shell內建的time命令和GNU版的time命令來測試程式執行的時間。

內建的time提供的引數選項比較少,而GNU的time則提供了豐富的引數選項,包括指定輸出檔案等功能。

[grandiose@Bygone grandiose]$ /usr/bin/time --output=foo.txt foo

上句只有時間資訊輸出到了foo.txt檔案中,如果想要包括foo執行的結果,就需要按下句這樣使用:

[grandiose@Bygone grandiose]$ /usr/bin/time --output=foo.txt --append foo >foo.txt

如果想要控制輸出時間的格式,可以使用-f開關進行格式化:

[grandiose@Bygone grandiose]$ /usr/bin/time 
   --output=foo.txt -f "\\t%E real,\\t%U user,\\t%S sys" foo

如果仍需要使用Shell內建的time命令,可以用下面一句將結果輸出至檔案:

[grandiose@Bygone grandiose]$ (time foo) 2>foo.txt

這樣寫是因為內建命令time的輸出是到標準錯誤的,檔案描述符2表示標準錯誤stderr。如果還想要包括foo執行的結果,就要這樣:

[grandiose@Bygone grandiose]$ (time foo) >foo.txt 2>&1

其中2>&1的含義是2與1 相同,一起送入foo.txt中。

nohup命令可以保證程式在退出系統之後仍能執行,這是它的常規用法。我們也可以這樣使用nohup:

[grandiose@Bygone grandiose]$ nohup time foo

結果全部輸出至nohup.out,也包括程式執行的時間資訊。可以使用下面的語句將時間資訊輸出至檔案foo.txt中。

[grandiose@Bygone grandiose]$ tail -2 nohup.out > foo.txt

為了保證和POSIX一致,輸出的時間格式為(nohup.out中的內容除外):

real    0m0.007s
user    0m0.002s
sys     0m0.004s

我們可以使用linux下面一些過濾命令如awk、sed、grep、tr等過濾出我們想要得到的內容,例如想要得到real段對應的時間:

[grandiose@Bygone grandiose]$ grep real foo.txt | cut -f2,或者
[grandiose@Bygone grandiose]$ sed -n '2p' foo.txt | cut -f2

在Shell下,輸出的時間精度為毫秒級,如果需要微秒級的計時,那就應該在程式中進行處理。

核心空間中的計時

如果要定製自己的裝置驅動程式,可能就會用到核心裡的計時功能。Linux核心空間中的計時與使用者空間的計時不太相同。在核心空間裡,有一個全域性變數Jiffies維護著當前的時間。與系統時鐘有關的呼叫有(新的定時服務):

#include <asm/param.h> 
#include <linux/timer.h> 
void add_timer(struct timer_list * timer); 
int del_timer(struct timer_list * timer); 
inline void init_timer(struct timer_list * timer);

結構struct timer_list的定義為:

struct timer_list { 
	struct timer_list *next; 
	struct timer_list *prev; 
	unsigned long expires; 
	unsigned long data; 
	void (*function)(unsigned long d); 
};

其中過期時間expires是要執行function的時間。一般在呼叫add_timer時jiffies = jiffies + num,表示在num個系統最小時間間隔後執行function。系統最小時間間隔與所用的硬體平臺有關, 在核心裡定義了常數HZ表示一秒內最小時間間隔的數目,則num*HZ表示num 秒。系統計時到預定時間就呼叫function,並把此子程式從定時佇列裡刪除, 因此如果想要每隔一定時間間隔執行一次的話,就必須在function裡再一次呼叫add_timer。function的引數d即為timer裡面的data項。

Jiffies的計時精度是百分之一秒,如果在核心中需要更為精確的計時,就需要用到time_calls.h中的函式,它們可用於高精度的時間計算。

補充

有的時候,我們需要較為精確地得出被測目標的執行時間,這時一般需要多次執行取均值以消除誤差。

gettimeofday( &start, NULL );
for ( int i = 0; i< 10; i++ ) foo(); 
gettimeofday( &end, NULL );
timeuse = 1000000 * ( end.tv_sec - start.tv_sec ) + end.tv_usec - start.tv_usec; 
timeuse /= 10000000;

上面的統計實際也引入了新的誤差,當迴圈指令的執行時間與foo()相比可忽略的話,這種計時才是可以接受的;否則我們就要除去迴圈指令的執行時間,才會得到正確的統計計時。

在Linux Shell下,如果統計次數較少,則可以:

for i in 1 2 3 4 5 6 7 8 9 10
do
	(time foo) 2>foo.tmp
	grep real foo.tmp | cut -f2 >> foo.txt
done

如果計時次數較多,則需要:

i=1
while [ $i -le 100 ]
do
	(time foo) 2>foo.tmp
	grep real foo.tmp | cut -f2 >> foo.txt
	i=`expr $i + 1`
done

寫進foo.txt的內容如果手動來計算平均值,會比較費時,我們可以寫一段Shell指令碼或用C語言來讀取檔案,計算其均值。

/*耗時中分部總和*/
cut -d'm' -f1 foo.txt > foo.tmp
sum=0
while read line
do
	sum=$(echo "$sum+$line" | bc -l)
done < foo.tmp
echo $sum
/*耗時中秒部總和*/
cut -d'm' -f2 foo.txt | tr -d 's'> foo.tmp
sum=0
while read line
do
	sum=$(echo "$sum+$line" | bc -l)
done < foo.tmp
echo $sum

計算出分部與秒部總和之後,然後再手動計算平均值,這樣要容易得多。注意,上面沒有使用expr進行計算的原因,是因為expr只能支援整型值。在Linux shell下,如果要計算浮點數,就需要使用bc或者是gexpr。

結束語

實際上,我們還可以使用諸如Perl、Python等多種語言在Linux系統中進行計時。選擇何種工具或語言進行計時,這與被測程式或程式段的型別以及它們的編寫語言相關。綜合考慮精度、執行時間、執行次數等要求,才能合理可靠地得出程式的執行時間。

參考資料


http://www.ibm.com/developerworks/cn/linux/l-time/


相關文章