轉載請保留以下宣告
作者:趙宗晟
出處:https://www.cnblogs.com/zhao-zongsheng/p/13067733.html
很多軟體都要做性能分析和性能優化,很多語言都會有他的性能分析工具,例如如果優化C++的性能,我們可以用Visual Studio自帶的性能探測器,或者使用Intel VTune Profiler,了解性能分析工具的原理有助于了解工具給出的資料與結果,也能幫助我們在遇到例外結果時排查哪里出了問題,這篇博客簡單總結一下常見的性能分析工具原理,
性能分析器原理分類
性能分析工具大部分都可以分為下面幾類
- 基于采樣(Sampling)
- 基于插裝(Instrumentation)
- 基于事件(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
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/9923.html
標籤:C++
上一篇:UE4使用經驗記錄
下一篇:C++ 回傳const物件
