CPU效能分析工具原理

趙宗晟發表於2020-06-15
轉載請保留以下宣告
  作者:趙宗晟
  出處:https://www.cnblogs.com/zhao-zongsheng/p/13067733.html

很多軟體都要做效能分析和效能優化。很多語言都會有他的效能分析工具,例如如果優化C++的效能,我們可以用Visual Studio自帶的效能探測器,或者使用Intel VTune Profiler。瞭解效能分析工具的原理有助於瞭解工具給出的資料與結果,也能幫助我們在遇到異常結果時排查哪裡出了問題。這篇部落格簡單總結一下常見的效能分析工具原理。

效能分析器原理分類

效能分析工具大部分都可以分為下面幾類

  1. 基於取樣(Sampling)
  2. 基於插裝(Instrumentation)
  3. 基於事件(Event-based)

1. 基於取樣

基於取樣的分析器會每隔一個固定時間間隔暫停所有執行緒的執行,然後分析有哪些執行緒正在執行,那些執行緒處於就緒狀態。對於這類執行緒,我們記錄每個執行緒的呼叫堆疊,以及其他需要的資訊。我們稱這個行為為一次取樣,記錄下來的每個堆疊為一個樣本。然後在結束效能分析的時候我們統計記錄下載的堆疊,看每個堆疊被記錄了多少次,或者每個函式被記錄了多少次。統計學告訴我們,那些執行時間比較長的函式、堆疊,被記錄的次數會更多。如果堆疊A被記錄了200次,堆疊B被記錄了100次,那麼堆疊B的執行時間是堆疊A的2倍。我們可以計算某個堆疊樣本的數量佔總樣本數的比例,這個比例就是堆疊執行時間的佔比。用Visual Studio的效能探測器我們看到的百分比和數字就是值樣本的佔比(也就是時間佔比)和樣本次數。

很多效能分析工具都是基於取樣的方式。執行效能分析器是會影響被測程式的效能的,而基於取樣的有點是對效能影響比較小,不需要重新編譯程式,非常方便。

2.基於插裝

插裝是指通過修改程式,往裡面加入效能分析相關程式碼,來收集、監控相關效能指標。例如我們可以在一個函式的開頭寫下計數程式碼,我們就可以知道在執行中這個函式被執行了多少次。一般來說基於插裝的效能分析更為準確,但是對效能影響比較大。因為需要修改程式碼,所以也不是很方便。另外,基於插裝的分析也可能會引起海森堡bug(heisenbug)。海森堡bug是指當你再執行程式的時候能遇到這個bug,但是試圖定位這個bug時卻遇不到這個bug。這個bug往往是因為在定位bug時修改了軟體的執行環境/流程,導致軟體執行和生產時不一樣,於是就遇不到這個bug了。這個命名的來源很有意思,海森堡是量子力學的著名科學家,他提出了不確定性原理,以及觀察者理論。這個理論認為,觀察會影響例子的狀態,導致觀察粒子和不觀察粒子會導致不同的結果,這個和海森堡bug的情形非常相似。關於觀察者理論,有興趣的人可以再瞭解一下。

回到正題,基於插裝也可以再進行劃分:

  • 人手修改原始碼:這個是我們非常常用的效能分析方法。我們做效能分析有時候就會直接修改原始碼,計算某一段程式碼的執行時長,或者計算函式呼叫次數,分析哪段程式碼耗時。
  • 工具自動修改原始碼
  • 工具自動修改彙編/中間碼
  • 執行時注入
  • ......

3.基於事件

在軟體執行過程中,可能會丟擲某些事件。我們通過統計事件出現的次數,事件出現的時機,可以得到軟體的某些效能指標。事件又可以分為軟體事件和硬體事件。軟體事件比如Java可以在class-load的時候丟擲事件。硬體事件則是使用專門的效能分析硬體。現在很多CPU裡面都有用於分析效能的效能監控單元(PMU),PMU本身是一個暫存器,在某個事件發生時暫存器裡面的值會+1。例如我們可以設定為當執行中發生memory cache miss的時候PMU暫存器+1,這樣我們就知道一段時間內發生了多少次memory cache miss。效能分析器使用PMU時,會給PMU設定需要統計的事件型別,以及Sample After Value (SAV)。SAV是指暫存器達到什麼值之後出發硬體中斷。例如,我們可以給PMU設定SAV為2000,統計事件為memory cache miss。那麼當cache miss發生2000次時,發生硬體中斷。這個時候效能分析器就可以收集CPU的IP,呼叫堆疊,程式ID等等資訊,分析器結束時進行統計分析。

基於硬體事件的優點是,對被測程式的影響非常小,比基於取樣還小,可以使用更短的時間間隔收集資訊,而且可以收集CPU的非常重要的效能指標,例如cache miss,分支預測錯誤,等等。

但是基於硬體事件的分析器也有它的一些問題,導致資料上的誤差:

  • SAV一般會設定成很大的數值:
    像是Intel VTune Profiler一般會把SAV設定成10,000到2,000,000,發生中斷時我們能知道最後一次觸發該事件是哪段程式碼引起的,但是在這之前的9,999到1,999,999次事件我們是不知道的。他會認為所有10,000到2,000,000次事件都是由同一處程式碼引起的。如果發生了很多次中斷,收集了很多次資訊,而某一行程式碼出現了很多次,那麼根據統計學,我們能知道這行程式碼觸發了多少次事件。但是如果某一行程式碼只出現了一兩次,那麼我們很難知道這行程式碼到底出發了多少次時間。
  • CPU一個核只有幾個PMU,但是可以統計的事件有幾十種:
    一個PMU可以統計一個事件,但是我們分析一個軟體可能需要統計幾十種事件。一般的處理方法是多路複用(Multiplexing)。比如說前10ms記錄事件A,後10ms記錄事件B,再後10ms由記錄事件A,……,這樣輪流記錄事件A和事件B,那麼一個PMU就可以同時統計兩個事件。多路複用可以解決少量PMU統計大量事件的問題,但是也會導致每種事件記錄的樣本數減少,倒是最後統計計算的結果誤差更大。
  • Intel® Hyper-Threading Technology導致記錄不準:
    Hyper-Threading技術可以讓一個CPU物理核變成兩個邏輯核,但是這兩個邏輯核會共享很多硬體,包括PMU。這會出現什麼問題呢?例如我們有兩個執行緒再兩個CPU核同時執行。我們覺得實在兩個核上執行,但是實際上是在同一個物理核上。所以PMU會同時統計兩個程式觸發的事件,我們很難區分到底是哪個邏輯核出發了多少事件,所以PMU記錄的資料就會不準確。另外,效能分析器計算效能指標時會使用一些硬體引數,Hyper-Threading技術會讓這些引數不準確。例如一般個CPU核能再一個clock執行4個uop,所以CPI(Cycle Per Instruction,每個clock執行的指令數)是0.25。但是如果使用了Hyper-Threading技術,實際的CPI會是0.5
  • Event Skid(事件打滑)會導致記錄的IP不準確:
    PMU記錄有些事件會出現一定延時,所以在執行分析器的中斷處理程式碼時,可能被測程式已經又執行了好多指令,IP已經向後滑動了很遠了。一般如果我們只是統計函式的話不會太大問題,但是我們想統計指令時就會有很大問題了。比如我們可能會看到某個add指令導致了大量的分支預測錯誤,顯然這個是不可能的。往往這種時候我們可以看看前面一點的指令。
  • Interrupt Masking(中斷遮蔽),導致統計出來的空閒狀態比正常的高
    不同的中斷有不同的優先順序,為了高優先順序的中斷處理程式不被打斷,我們可以選擇遮蔽一部分中斷事件。但是PMU的事件也是一箇中斷,如果系統中有大量的中斷遮蔽,那麼會有PMU的中斷被遮蔽掉一部分,導致統計出來的資料不準確。表現出來的效果就是,統計出來的處於空閒狀態的時間比實際的要高。

總結

這幾個就是比較常見的效能分析方法。我們知道了效能分析的原理,可以更好地理解效能分析器給出的結果,也可以在出現明顯異常的結果時,分析判斷可能的原因,針對性的解決。

參考

https://en.wikipedia.org/wiki/Profiling_(computer_programming)
https://software.intel.com/content/www/us/en/develop/articles/understanding-how-general-exploration-works-in-intel-vtune-amplifier-xe.html
https://software.intel.com/content/www/us/en/develop/documentation/vtune-help/top/analyze-performance/user-mode-sampling-and-tracing-collection.html
https://software.intel.com/content/www/us/en/develop/documentation/vtune-help/top/analyze-performance/hardware-event-based-sampling-collection.html

相關文章