程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 探討性能測試中的計時問題

探討性能測試中的計時問題

編輯:關於VC++

在系統測試時,尤其在需要測試算法或者某些模塊的運行時間時,往往需要調 用一些時間函數庫(如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)
{
      LARGE_INTEGER litmp;
     QueryPerformanceCounter (&litmp);
     gStarts[iModel] = litmp.QuadPart;
}
其中參數iModel表示當前計時的模塊序號, 0<=iModel<=BENCHMARK_MAX_COUNT;為了簡化調用代碼,我們給出一個宏定 義如下:#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. 結束語

本文實現了一組計時函數的封裝,並給 出幾種特殊情況下的測試實例,實驗表明該組函數可以滿足各種復雜情況下的計 時,能夠很方便地應用的實際的測試工作中。當然,還可以進一步封裝成一個計 時類,留給讀者們自己去做。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved