在系統測試時,尤其在需要測試算法或者某些模塊的運行時間時,往往需要調 用一些時間函數庫(如VC中的timeGetTime等可以獲取毫秒級的時間),在待測試 的模塊前後分別測試時間,然後,計算前後兩個時間的差值,就得到模塊的運行 時間,如圖 1。
圖 1 一個典型的模塊計時方法
但是,使用原始的計時函數直接進行時間測試 在很多復雜情況下不方便,如圖 1,當在一個模塊中有多個子模塊需要分別計時 ,所編寫的計時代碼甚至比原有的代碼還多,這增加了程序維護和閱讀的難度, 容易出錯。作者結合自己在相關工作中的經驗,封裝了一組計時函數,共享給大 家。該組函數有如下幾個優點:
計時精確:封裝的是高精度的計時API函 數QueryPerformanceCounter(),該函數根據硬件定時器的頻率,理論上可以得到 微秒(us)級精度的計時結果;
使用簡單:只用在待測試的模塊前後加上兩 個宏BM_START和BM_END,不需要對結果進行計算,也不需要考慮對各個模塊測試 結果數據的維護,這些操作已經被封裝。
結果輸出獨立:在系統運行結果 時,只需要調用一個函數就可以把計時結果保存在一個文本文件裡,如圖 5和圖 8所示。
1. 高精度計時函數
在Windows系統下,程序員通常可以使 用多種方式來進行時間控制:如使用前文提到的timeGetTime()函數,或者使用 GetTickCount()函數,又或實現WM_TIMER消息的映射等等。但是這些方法得到的 時間精度都有一定的局限性,為了增加下文將到介紹的計時函數庫的適用性,本 文采用高精度的時控API函數QueryPerformanceCounter()。
計時之前,調 用QueryPerformanceFrequency()函數獲得機器內部定時器的時鐘頻率,然後在需 要計時的模塊前後分別調用QueryPerformanceCounter()函數,利用兩次獲得的計 數之差獲得時鐘頻率,計算出模塊的運行時間。代碼如圖 2:
圖 2 精確計時代碼段
2. 封裝計時函數
2.1. 數據結構
為了維 護計時結果,我們定義如下幾個數據:
#define BENCHMARK_MAX_COUNT 20
double gStarts[BENCHMARK_MAX_COUNT];
double gEnds[BENCHMARK_MAX_COUNT];
double gCounters [BENCHMARK_MAX_COUNT];
double dfFreq = 1;
其中, BENCHMARK_MAX_COUNT定義了需要計時的模塊總數,20表示最多可以定時20個模塊 ,該值可以根據具體應用而定。gStarts和gEnds分別用於保存開始計時和終止計 時的計數器的值,gCounters用來保存計時結果。全局變量dfFreq用來保存上文介 紹的時鐘頻率,如圖 2所示。
2.2. 初始化InitBenchmark()
初始 化函數InitBenchmark()包括兩部分內容:
對數組gStarts, gEnds, gCounter清零;
獲得機器內部定時器時鐘頻率。
InitBenchmark() 代碼如下所示:void InitBenchmark()
{
ResetBenchmarkCounters();
GetClockFrequent();
}
該函數一般在程序運行最初調用。
2.3. 開始計時 BMTimerStart()
開始計時函數BMTimerStart()放在計時模塊的開始,函數 定義如下:
void BMTimerStart(int iModel)
其中參數iModel表示當前計時的模塊序號, 0<=iModel<=BENCHMARK_MAX_COUNT;為了簡化調用代碼,我們給出一個宏定 義如下:
{
LARGE_INTEGER litmp;
QueryPerformanceCounter (&litmp);
gStarts[iModel] = litmp.QuadPart;
}#define BM_START(t) BMTimerStart (t);
2.4. 終止計時BMTimerEnd()
終止計時函數BMTimerEnd()放 在計時模塊的結束,函數定義如下:void BMTimerEnd(int iModel)
{
LARGE_INTEGER litmp;
QueryPerformanceCounter(&litmp);
gEnds[iModel] = litmp.QuadPart;
gCounters[iModel] += (((gEnds[iModel] - gStarts[iModel]) / dfFreq) * 1000000);
}
參數iModel同 BMTimerStart()。本函數首先獲取當前的時鐘數,然後除以dfFreq得到運行時間 。對於最後一條語句:
gCounters[iModel] += (((gEnds[iModel] - gStarts[iModel]) / dfFreq) * 1000000);
要注意兩點:
用 “+=”而不是“=”,這個看似簡單的代替,可以實現 對同一個模塊的重復計時,後文3.3節列舉的情況;
乘以1000000,表示計 時單位為微秒(us)。
類似BMTimerStart(),同樣為BMTimerEnd()定義一 個宏:
#define BM_END(t) BMTimerEnd (t);
2.5. 結果輸出WriteData()
以一個文本文件(見圖 5和圖 8)把全局變量gCounters中的所有值輸出,該函數一般在程序結束處調用,如圖 4中最後一行代碼所示。由於篇幅限制,具體實現代碼請參考源程序。
3. 計時測試實例
3.1. 多個模塊計時
圖 3展示了嵌套計時以及對一個 函數中多個模塊進行計時的代碼,圖中可以看到,利用輸入參數我們對計時模塊 進行統一編號,測試代碼相對圖 1更清晰、直觀。
圖 3 用我們的函數實現嵌套計時
3.2. 循環內部計時
圖 4中的代 碼展示了我們對循環體內每次執行運算的計時,只需簡單地給出參數 就可以得到 從1到20的階乘的每次計算計算時間,計時結果輸出為文件 “D:\log.txt”,如圖 5。
圖 4 對循環中每次運算的計時
圖 5 計時結果1
3.3. 循環累加計時
從圖 5可以看到,由於現代計算 機處理速度越來越快,一些簡單運算的模塊,微秒的計時單位幾乎都不夠精確, 因此,一種常用的測試方法就是對同一模塊進行N (N 取1000,10000等)次重復 執行。使用本文介紹的計時函數,我們可以采用兩種方式對這種情況進行測試, 代碼分別如圖 6和圖 7,請注意二者的區別,並請讀者分析為何圖 7中的方法也 是可行的。 N次運算計時結果如圖 8。
圖 6 累加計時1
圖 7 累加計時2
圖 8 計時結果2
4. 結束語
本文實現了一組計時函數的封裝,並給 出幾種特殊情況下的測試實例,實驗表明該組函數可以滿足各種復雜情況下的計 時,能夠很方便地應用的實際的測試工作中。當然,還可以進一步封裝成一個計 時類,留給讀者們自己去做。
本文配套源碼