對代碼進行持續性開發和有意義的基準測試是一個複雜的任務。雖然測試工具自己(Intel® VTune™ Amplifier, SmartBear AQTime, Valgrind)與應用程序沒有相關性,可是它們在某些時候對一些小團隊,或者說是一些繁瑣的工做來講仍是很重要的。這個Celero項目,主要是要建倉一個小型的程序庫,使它能夠在加入 C++ 工程和對代碼進行基準測試時可以很是容易地去重建,分享,並容許在獨立的運行進程、開發者或者是工程間進行比較。Celero 使用一個與 GoogleTest 類似的構架,使得他的 API 很容易地使用,並融入一個工程中。當你在開發過程當中進行自動測試時,自動化基準將會扮演舉足輕重的做用。c++
一般,編寫基準的目的是爲了測量一段代碼的性能。基準有助於比較解決同一問題的不一樣方案並選擇其中最合適的一個。其餘時候,基準能以一種頗有意義的方式突出設計或算法改變後的性能影響。
經過測量代碼的性能,能夠爲性能消除那些你覺得是正確方案的錯誤。只有經過測量,你才能肯定好比用一張查找表比計算一個值更快。這種傳聞(一般是重複的)可能致使糟糕的設計決策,最終生成效率更慢的代碼。
編寫好的基準的目的是爲了消除全部噪音和開銷,而且只測量被測代碼。在測量中,噪音源包括時鐘分辨率噪音、操做系統後臺操做、測試設置/清除、框架開銷以及其餘不相干的系統行爲。
理論上,咱們想測量被測代碼的執行時間「t」。實際上,咱們測到的是「t」與全部噪音的和。git
這些影響咱們測量的「t」的不相干因素會隨時間變化而波動。所以,咱們想試圖將「t」隔離出來。實現這樣的方法是經過屢次測量,但只保留最小的總時間。這個最小的總時間必然是受噪音干擾最小且最接近時間「t」的值。
一旦獲得這個測量值,在進行隔離就沒什麼意義了。創建一個基線測試做比較很重要。基線一般應該是一個基於你正要測量出一個解決方案的問題的「經典」或「純粹」的方案。一旦有了一個基線,你花時間去比較你的算法就有意義。簡單地說,你中意的排序算法(fSort)不能在10毫秒內自動排序100萬元素。然而,相對於一個經典的排序算法基線如快速排序(qSort),你能夠說,fSort處理100萬元素比去Sort快50%。這是個頗有意義且強大的測量。github
Celero大量使用Visual C++和GCC4.7都支持的C++11特性。這對使代碼整潔、便攜大有幫助。爲了使代碼更容易採用,用戶所需的全部定義都放在一個命名空間爲celero的單一頭文件:Celero.h裏。
Celero.h裏包含將每一個用戶基準用例轉換爲獨特的、具備與之相關測試固件的類(若是有的話),而後登記測試用例到一個工廠。這些宏自動將基線測試用例與相關的測試基準關聯起來,這樣,在運行時基準相關的數據就能夠被算出。這一關聯由測試向量來維護。
測試向量利用PImpl慣語來隱藏實現而且保持包含Celero.h的開銷降到最小。
Celero將結果輸出到命令行。由於顏色是有用的(可能有助於主觀因素/結果的可讀性),因此std::cout調用了自身以外的一些東西。Console.h定義了一個簡單的顏色函數,SetConsoleColor,它能夠被celero::print命名空間中的函數用來格式化程序的輸出。
測量基準的執行時間位於TestFixturebase類中,並且全部基準的寫法都是以此爲基礎派生出來的。首先,測試夾具(譯註:test fixture 是檢測被測試系統時所須要的全部東西)的建立代碼被執行。接着,測試的開始時間被獲取到並以毫秒的形式用一個unsigned long保存起來。這麼作是爲了減小浮點指針錯誤。再下一步,指定次數的操做(迭代)被執行。結束時,結束時間被抓取到,測試夾具銷燬,本次執行的測量時間被返回,而且這個結果會被保存下來。
不管指定多少個樣本,這個循環就像這樣重複。若是樣本沒有指定(爲零),那麼測試將會重複運行直到一秒鐘,或者至少採集了30個樣本。當寫到代碼的這個特定部分時,顯然這裏存在有一種「if-else」的關係。無論怎麼說,大量的代碼都是如此重複着「if」與"else"的片斷。這裏可使用一個老式的函數,可是利用std::function來定義一個 匿名函數(lambda)再天然不過,這樣就能夠調用它並使全部的代碼整潔。(c++ 11真是一個奇妙的東西)最後,結果被打印到屏幕。算法
Celero 使用 CMake 去提供跨平臺構件。因爲它使用得是 C++ 11,於是須要請求一個如今流行的編譯器(Visual C++ 2012 or GCC 4.7+)。
一旦你的項目中加入了 Celero,你可以建立專門的基準項目和源文件。爲了方便,一個頭文件和aCELERO_MAINmacro 能提供給 main() ,來幫助你的基準項目自動去執行全部的基準測試。
下面有一個簡單的 Celero 基準的例子:segmentfault
#include <celero/Celero.h> CELERO_MAIN; // 運行一個自動的基線。 // Celero 保證能提供足夠的採樣來獲得一個合理的測量結果 BASELINE(CeleroBenchTest, Baseline, 0, 7100000) { celero::DoNotOptimizeAway(static_cast<float>(sin(3.14159265))); } // 運行一個自動測試。 // Celero 保證能提供足夠的採樣來獲得一個合理的測量結果 BENCHMARK(CeleroBenchTest, Complex1, 0, 7100000) { celero::DoNotOptimizeAway(static_cast<float>(sin(fmod(rand(), 3.14159265)))); } // 運行一個手動測試。這是對一個樣本進行每秒 7100000 次操做。 // Celero 保證能提供足夠的採樣來獲得一個合理的測量結果。 BENCHMARK(CeleroBenchTest, Complex2, 1, 7100000) { celero::DoNotOptimizeAway(static_cast<float>(sin(fmod(rand(), 3.14159265)))); } // 運行一個手動測試。這是對 60 個樣本進行每秒 7100000 次操做。 // Celero 保證能提供足夠的採樣來獲得一個合理的測量結果。 BENCHMARK(CeleroBenchTest, Complex3, 60, 7100000) { celero::DoNotOptimizeAway(static_cast<float>(sin(fmod(rand(), 3.14159265)))); }
這段代碼中咱們作的第一件事情,就是定義一個BASELINE測試用例。這個模版有四個參數:
BASELINE(GroupName, BaselineName, Samples, Operations)
* GroupName- 基準組的名字。它是用來將運行與結果和它們相應的基線測量收集到一塊兒。
* BaselineName- 爲了報表目的的基線的名字。
* Samples- 對測試代碼進行給定次數的操做,對這些操做的總的循環運行次數。
* Operations- 你但願對每一個樣本運行測試代碼的次數。框架
這裏的樣本與操做是用來測量很是快的代碼的。例如,若是你知道基準測試中的代碼運行時間小於100毫秒,那麼在進行一次測量以前,這個操做次數將會指定代碼運行"operations"次。而Samples則定義了要作多少次測量。
Celero容許指定零樣本也有助於此。零樣本將告訴Celero,基於完成指定次數操做所須要的時間,採集一些具備統計學意義數量的樣本。這些數字將會在運行時給出。
celero::DoNotOptimizeAway模版是用來保證優化編譯器不會消除你的函數或代碼。因爲這個功能被用於全部的基準樣本以及它們的基線,因此相比較起來其中的額外時間消耗能夠忽略不計。
在基線被定義以後,接着被定義的是各類各樣的基準。BENCHMARK宏的語法與普通宏的語法徹底相同。函數
示例項目被設置爲一旦成功編譯就自動執行基準測試代碼。在個人PC上運行這個基準測試獲得的是如下的輸出:工具
[ CELERO ] [==========] [ STAGE ] Baselining [==========] [ RUN ] CeleroBenchTest.Baseline -- Auto Run, 7100000 calls per run. [ AUTO ] CeleroBenchTest.Baseline -- 30 samples, 7100000 calls per run. [ DONE ] CeleroBenchTest.Baseline (0.517049 sec) [7100000 calls in 517049 usec] [0.072824 us/call] [13731773.971132 calls/sec] [==========] [ STAGE ] Benchmarking [==========] [ RUN ] CeleroBenchTest.Complex1 -- Auto Run, 7100000 calls per run. [ AUTO ] CeleroBenchTest.Complex1 -- 30 samples, 7100000 calls per run. [ DONE ] CeleroBenchTest.Complex1 (2.192290 sec) [7100000 calls in 2192290 usec] [0.308773 us/call] [3238622.627481 calls/sec] [ BASELINE ] CeleroBenchTest.Complex1 4.240004 [ RUN ] CeleroBenchTest.Complex2 -- 1 run, 7100000 calls per run. [ DONE ] CeleroBenchTest.Complex2 (2.199197 sec) [7100000 calls in 2199197 usec] [0.309746 us/call] [3228451.111929 calls/sec] [ BASELINE ] CeleroBenchTest.Complex2 4.253363 [ RUN ] CeleroBenchTest.Complex3 -- 60 samples, 7100000 calls per run. [ DONE ] CeleroBenchTest.Complex3 (2.192378 sec) [7100000 calls in 2192378 usec] [0.308786 us/call] [3238492.632201 calls/sec] [ BASELINE ] CeleroBenchTest.Complex3 4.240175 [==========] [ STAGE ] Completed. 4 tests complete. [==========]
首次運行的測試將做爲整個組測試的基線。這個基線顯示出它是一個「自動測試」,暗示着由Celero來衡量並決策運行測試代碼的次數。這個案例中,在咱們的測試裏它要運行7100000次代碼迭代總共30次。(每調用7100000次測量一次,這樣30次之後取最小的時間。)整個測量須要0.517049秒。基於這個結果,能夠測量出每次對基準代碼的調用須要0.072824毫秒。
在基線測試結束以後,將運行每一個單獨的測試。每一個測試的運行與測量方式都是相同的,不過有一個額外的度量報告:基線。將它運行基準測試代碼所須要的時間與基線進行比較。這裏的數據顯示,CeleroBenchTest.Complex1比基線運行須要的時間多了4.240004倍。性能
原文:Celero - A C++ Benchmark Authoring Library
轉載自:開源中國社區--super0555, 竟悟, jimmyjmh測試