奇想齋

歸檔 鏈接

一些C/C++優化工具

2021-11-13

性能數據收集

perf

Linux下收集性能數據通常用perf。關於perf的使用可以參考Brendan Gregg的網站

其中最常用的命令是perf recordperf report,而常用的指標有cycles、branch miss、cache miss等。

然後用FlameGraph工具可以生成火焰圖,直觀顯示性能消耗在何處。

perf的原理

收集cycles、branch miss、cache miss這些數據,主要應用到了CPU當中的Performance Monitoring Unit(簡稱PMU)。PMU其實就是CPU當中的一個計數器,當發生特定事件的時候加一,並可以向內核報告寄存器狀態,尤其是Program Counter寄存器的狀態,這樣就可以知道branch miss、cache miss發生在何處。

因爲會帶來性能損耗,所以使用PMU的時候一般採用採樣模式。例如,每發生一萬次cache miss纔會報告一次。

此外,Intel的CPU還有一項功能,叫Least Recent Branch(簡稱LRB),可以記錄控制流,也被perf用於發現程序中的熱點。

關於PMU和LRB的原理,參見:

手工優化

關於怎麼優化程序,有一些放之四海而皆準的老生常談,比如如何優化系統調用、如何用鎖等等、使用-O3編譯選項等等。不過,也有一些更精妙的東西。

其一是likely/unlikely宏,而這些宏依賴了GCC中的__builtin_expect函數。如下:

#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)

這個宏可以告訴編譯器在分支處更容易跳轉到哪裏。編譯器根據這些提示,可以做出相應優化,防止發生太長的跳轉,進而提高分支預測的成功率,並減少程序加載時出現page fault的可能性。

從C++20開始,likely和unlikely以attribute的形式進入了C++標準。使用方法如下:

double pow(double x, long long n) {
    if (n > 0) [[likely]]
        return x * pow(x, n - 1);
    else [[unlikely]]
        return 1;
}

此外,也有一些builtin功能可以提示編譯器對緩存做更精確的控制,例如__builtin_prefetch

參見:

自動優化

上面的手工優化,實際操作起來往往很繁瑣,所以只能在少量熱點處使用。如果要對程序進行大量優化,免不了要使用自動化工具。

LTO

LTO全稱是Link-Time Optimization,即「鏈接期優化」。LTO可以對程序進行全局優化,而不僅僅着眼於局部,故可以執行一些更激進的優化策略。但是缺點是優化方法比較複雜,且內存消耗大;針對這些缺點又有了Thin LTO。LTO使用起來也很簡單,利用clang編譯,然後在編譯和鏈接時加上-flto-flto=thin即可。

參見:

PGO

PGO,全名Profile-Guided Optimazation,即通過編譯的時候,構建一個運行模型,通過Profile數據來指導編譯器優化。目前GCC已經提供了該功能。但是缺點是這個過程通常非常耗時,而且不好收集數據集,構建模型也很複雜。針對此,谷歌做了一些PGO自動化相關的工作,開源了AutoFDO,全名Automatic Feedback-Directed Optimization,通過採集生產機器上的數據來做反饋優化。不過,谷歌的AutoFDO雖然開源了,但是Github上似乎並沒有詳細的使用說明,只能夠找到一些零散的資料。

參見:

BOLT

BOLT由Facebook開發(現在改名meta了),也是一種反饋優化,全名是Binary Optimization and Layout Tool。正如其名,BOLT在優化時不需要源代碼,可以直接操作二進制進行,通過重排代碼、優化控制流,以提高指令緩存的效率。如果程序依賴了一些沒有源代碼的二進制庫,那麼BOLT在這種場景下就有很大優勢。根據Facebook的數據,BOLT給渠們的Hack語言(一種php類似物)虛擬機HHVM帶來了8%的性能提升。

參見:


Powered by Pandoc ©️ 2017-2022 奈卜拉