程序/進程執行時間分析(user cpu time, system cpu time, elpapsed time)

1. 背景

近日做程序優化任務,代碼不長,時間測試起來需要在for循環里加,破壞了程序執行的流水線,導致時間不準。網上查看了些相關資料,學到了一些東西,雖然對本任務沒用,但對於程序/進程執行時間可加深理解。

首先,需要明白的是:

我們平時常用的測量運行時間的方法並不是那麼精確的,換句話說,想精確獲取程序運行時間並不是那麼容易的。也許你會想,程序不就是一條條指令麼,每一條指令序列都有固定執行時間,爲什麼不好算?真實情況下,我們的計算機並不是只運行一個程序的,進程切換,各種中斷,共享的多用戶,網絡流量,高速緩存的訪問,轉移預測等,都會對計時產生影響。

這個是比較好理解的,通常實際中跑程序跑幾次,每次的時間是略有差異的。那麼程序的運行時間究竟是怎樣的呢?

2. 執行時間

2.1 構成

程序運行時是以進程的形式,因此討論是在進程層面。

  • 真實時間:進程從開始執行到最後結束的時間,包括阻塞 + 就緒 + 運行的時間。稱爲 wall clock time/牆上時鐘時間/elpapsed time,是我們跑程序實際等待的時間;

  • 系統cpu時間:用戶進程獲得CPU資源後,在內核態的執行時間,如write,read等系統調用;

  • 用戶cpu時間:用戶進程獲得CPU資源後,在用戶態執行的時間,主要是我們自己編寫的代碼;

其中可大致認爲:

真實時間 = 阻塞時間 + 就緒時間 +CPU運行時間

CPU運行時間 = 用戶CPU時間 + 系統CPU時間

之所以說大致可以認爲,是因爲時間測試中方法不能做到十分精確,但主要因素就是這幾個。

2.2 如何獲取時間

2.2.1 clock()函數

作用:獲取CPU運行時間(用戶CPU時間 + 系統CPU時間),程序中常用前後2次差值來獲取時間。

差值表示該進程經過了多少個clock。1個clock是1個計時基本單元。注意其不是cpu cycle(1/主頻)。在Hi3559開發板上是1000000個clock/秒,即1us/clock。各平臺因編譯器不同而不同。出於平臺移植性,使用宏CLOCKS_PER_SEC獲取該值並參與時間的計算。

// 計算進程運行時間,單位:s
clock_t t1 = clock();
proc();
clock_t t2 = clock();
TotalTime = (double)(t2 - t1) / CLOCKS_PER_SEC;

2.2.2 gettimeofday()函數

作用:獲取真實時間(us),目前項目中測試時間用的是該函數。

struct timeval start, end;
long long proc_time;
gettimeofday(&start, NULL);
proc();
gettimeofday(&end, NULL);
proc_time = (end.tv_sec - start.tv_sec) * 10E6 + (end.tv_usec - start.tv_usec);
printf("%lld us", proc_time);

ps:如何判斷獲取的是真實時間還是CPU時間?
在兩次取時間中間加各sleep(5),如果差值時間增加了5s,則是真實時間,反之若差別很小則是CPU時間。因爲sleep的功能是把當前進程掛起,此時CPU幹別的事了。

2.2.3 time命令

運行程序時,shell中採用:

# time ./test_exe
real 0m 5.69s
user 0m 0.08s
sys  0m 0.61s

即可打印出該程序的真實時間、CPU用戶時間、CPU系統時間。上述如果用clock統計,則測試的是後兩者之和real=user+sys=0.69s,它不會把掛起的5s算進去。若用gettimeofday統計,則測試的是真實時間。

2.2.4 times()函數

clock()函數和gettimeofday()函數均不能區分CPU用戶時間和系統時間。time命令雖然可以區分CPU用戶時間和系統時間,但是從程序整體層面統計的。若我們想獲取程序中某一段代碼的CPU用戶時間和系統時間,如何處理?採用times()函數即可。

note:times函數獲取的是進程CPU時間(CPU用戶時間、CPU系統時間)自進程run後發生的tick數,所謂的滴答,CPU的一個基本計時單位,宏定義_SC_CLK_TCK表示1s包含的滴答數,在Hi3559開發板上測試100個tick/秒,即10ms/tick。

// 包含頭文件
#include<sys/times> 

// 函數原型
clock_t times(struct  tms * buf);

// 數據結構
struct tms {
    clock_t tms_utime;   /* user   time */
    clock_t tms_stime;   /* system time */
    clock_t tms_cutime;  /* user   time of children */
    clock_t tms_cstime;  /* system time of children */
};

struct tms start, end;

if (times(&start) == -1)    
    err_exit("times error");

proc();

if (times(&end) == -1)  
    err_exit("times error");

int tick_per_sec = sysconf(_SC_CLK_TCK);

printf("user time: %d s\n", (end.tms_utime - start.tms_utime) / (tick_per_sec));
printf("syst time: %d s\n", (end.tms_stime - start.tms_stime) / (tick_per_sec));

note:linux中clock函數並不能統計子進程的時間,而times函數是可以獲取子進程時間的。struct tms 的最後兩個成員變量tms_cutime和tms_cstime是用來記錄子進程(們)的system cpu time 和 user cpu time,其值是在父進程中執行wait 或 waitpid 時開始記錄,等到wait返回後停止記錄。

時間構成討論

1、2.1節中時間構成是在單核情況下討論,真實時間通常大於CPU時間(CPU用戶時間 + CPU系統時間),因爲存在進程切換等諸多複雜開銷。

2、多核情況下帶來了困擾,使這個問題不好討論。比如兩個CPU並行做proc,則CPU時間之和可能會大於真實時間。

3、瞭解了上述時間測試的內容,可以幫助我們在實際測試中選擇最合適的方法,雖然它不是如此精確,但主要問題還是可以抓住,爲我們提供有效的時間參考。

參考

[1] https://www.cnblogs.com/wfwenchao/p/5195022.html
[2] https://blog.csdn.net/youjun9007228198/article/details/26090423

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章