2015-05-10 42 views
37

我花了一年時間在C++中開發一個記錄庫,並且考慮到了性能。爲了評估性能,我開發了a set of benchmarks以將我的代碼與其他庫進行比較,包括根本不執行任何日誌記錄的基本情況。爲什麼我的日誌記錄庫會導致性能測試運行速度更快?

在我最近的一個基準測試中,我測量了一個CPU密集型任務的總運行時間,當日志處於活動狀態時以及不活動時。然後,我可以比較時間來確定我的庫有多少開銷。這個條形圖顯示了與我的非測井基礎案例相比的差異。

performance chart

正如你看到的,我的圖書館( 「魯莽」)將負開銷(除非所有4個CPU核心繁忙)。當啓用日誌功能時,程序運行速度比禁用時快大約半秒。

我知道我應該嘗試將其分解爲一個更簡單的情況,而不是詢問4000行程序。但是有很多地方需要刪除,如果沒有任何假設,我會在嘗試隔離它時讓問題消失。我可能會花一年時間來做這件事。我希望堆棧溢出的集體專業知識會使這個問題更加淺薄,或者對於比我有更多經驗的人來說,這個原因是顯而易見的。

我的庫中的一些事實和基準:

  • 庫由前端API是推動日誌參數到無鎖隊列(Boost.Lockless),並執行後端線字符串格式化並將日誌條目寫入磁盤。
  • 時間基於在程序開始和結束時簡單呼叫std::chrono::steady_clock::now()並打印差異。
  • 該基準測試是在4核英特爾CPU(i7-3770K)上運行的。
  • 基準程序計算一個1024x1024 Mandelbrot分形並記錄有關每個像素的統計信息,即它會寫入大約一百萬條日誌條目。
  • 單個工作線程的總運行時間約爲35秒。所以速度增加約1.5%。
  • 基準測試產生一個包含生成的Mandelbrot分形的輸出文件(這不是定時代碼的一部分)。我已驗證日誌記錄打開和關閉時會生成相同的輸出。
  • 基準測試運行了100次(所有基準測試庫都需要大約10個小時)。條形圖顯示平均時間,誤差棒顯示四分位間距。
  • Source code for the Mandelbrot computation
  • Source code for the benchmark
  • Root of the code repository and documentation

我的問題是,我怎樣才能解釋當我的日誌記錄庫啓用時明顯的速度增加?

編輯:這是在嘗試了評論中給出的建議後解決的。我的日誌對象是在line 24 of the benchmark test上創建的。顯然,當LOG_INIT()觸及日誌對象時,它會觸發頁面錯誤,導致圖像緩衝區的部分或全部頁面被映射到物理內存。我仍然不確定爲什麼這會將性能提高近半秒;即使沒有日誌對象,mandelbrot_thread()函數中發生的第一件事是寫入圖像緩衝區的底部,這應該有類似的效果。但是,在任何情況下,在啓動基準測試之前用memset()清除緩衝區會使一切變得更加健全。目前的基準是here

,我試過其他的事情是:

  • 與OProfile的探查器運行。即使在將作業放大以使其運行約10分鐘之後,我仍然無法在鎖中註冊任何時間。幾乎所有的時間都在Mandelbrot計算的內部循環中。但是,也許我現在能夠以不同的方式解讀它們,因爲我知道頁面錯誤。我沒想過要檢查圖像寫入是否花費了不成比例的時間。
  • 卸下鎖。這確實對性能有重大影響,但結果仍然很奇怪,無論如何我無法對任何多線程變體進行更改。
  • 比較生成的彙編代碼。雖然存在差異,但伐木構建顯然做了更多的事情。沒有什麼比顯而易見的表現殺手更突出。
+16

首先,您是否嘗試過對不同的測試進行分析並查看差異可能實際存在的位置?這是我的第一個建議。我的第二個是可以接受的猜測,但在啓動計時器之前,很簡單:'memset()''sample_buffer'。我在第一次訪問未初始化的內存時看到頁面錯誤的實例會產生時間問題。 –

+0

通常情況下,我會首先建議您在遇到令人困惑的性能結果時尋找更真實的案例,但渲染mandelbrot集已經非常現實,因此您在那裏很棒。我曾經和一個在發現使用'std :: vector :: insert'使他的代碼看起來一直比使用'std :: vector :: push_back'的速度一致的人感到興奮,只有這樣纔會興奮和偶爾應用'插入'無處不在,並放慢速度... –

+0

隨着性能的提高,各種各樣的動態因素可能會導致一些性能高峯。正如Andrew所指出的,最常見的一種是頁面錯誤。有時候,當你重構你的代碼時,優化器可能會做出奇怪的事情,甚至有點不同。要深入瞭解這些事情,總是有必要在你手中安裝一個分析器,並瞭解如何解釋反彙編的基本知識。 –

回答

2

當首次訪問未初始化的內存時,頁面錯誤將會影響計時。

因此,在第一次調用std::chrono::steady_clock::now()之前,請通過在sample_buffer上運行memset()來初始化內存。

相關問題