tool - 我如何分析在Linux中運行的C++代碼?




profiling tool (7)

使用Valgrind,callgrind和kcachegrind:

valgrind --tool=callgrind ./(Your binary)

生成callgrind.out.x。 使用kcachegrind閱讀它。

使用gprof(加上-pg):

cc -o myprog myprog.c utils.c -g -pg 

(對於多線程,函數指針不太好)

使用谷歌perftools:

使用時間採樣,揭示I / O和CPU瓶頸。

英特爾VTune是最好的(免費用於教育目的)。

其他: AMD Codeanalyst,OProfile,'perf'工具(apt-get install linux-tools)

https://code.i-harness.com

我有一個C ++應用程序,運行在Linux上,我正在優化。 我怎樣才能確定我的代碼的哪些區域運行緩慢?


如果您的目標是使用分析器,請使用其中一個建議的分析器。

但是,如果您很匆忙,而且在主調慢的情況下可以在調試器中手動中斷程序,則有一種簡單的方法可以找到性能問題。

只需暫停幾次,每次看看調用堆棧。 如果有一些代碼浪費了一定比例的時間,20%或50%,或者其他什麼,那就是你在每個樣本上的行為中可以捕捉到的概率。 所以這大致就是您將看到它的樣本的百分比。 沒有必要的猜測。 如果你猜猜問題是什麼,這將證明或反駁它。

您可能有多個不同大小的性能問題。 如果你清理掉其中的任何一個,其餘的將佔用更大的比例,並且在隨後的傳球中更容易被發現。 這種放大效應在復合多個問題時會導致真正巨大的加速因子。

警告:程序員往往對這種技術持懷疑態度,除非他們自己使用它。 他們會說分析器會給你這些信息,但是只有當他們對整個調用堆棧進行採樣,然後讓你檢查一組隨機抽樣時才是如此。 (摘要是洞察力丟失的地方。)調用圖不會給你相同的信息,因為

  1. 他們沒有在指導層面進行總結,而且
  2. 他們在遞歸的情況下給出了令人困惑的總結。

他們也會說它只適用於玩具程序,實際上它適用於任何程序,而且它似乎對更大的程序更好,因為它們往往會遇到更多問題。 他們會說它有時會發現不是問題的東西,但只有當你看到某種東西時才是如此。 如果您在多個樣本上看到問題,則是真實的。

PS如果有一種方法可以在某個時間點收集線程池的調用棧樣本,那麼也可以在多線程程序中完成此操作,就像在Java中一樣。

PPS簡單來說,軟件中抽象層次越多,發現性能問題的原因越多(有機會得到加速),就越有可能發生這種情況。

補充:它可能並不明顯,但堆棧抽樣技術在遞歸存在的情況下工作得很好。 原因是,通過刪除指令可以節省的時間大概是包含它的樣本的一小部分,而不管樣本中可能發生多少次。

我經常聽到的另一個反對意見是:“ 它會隨機停止某個地方,它會錯過真正的問題 ”。 這是由於事先有一個真正的問題是什麼概念。 性能問題的一個重要特徵是他們違背了期望。 抽樣告訴你有什麼問題,你的第一反應是不相信。 這很自然,但你可以肯定,如果它發現它是真實的問題,反之亦然。

增加:讓我做一個貝葉斯解釋它是如何工作的。 假設有一些指令I (呼叫或其他)在調用堆棧的一小部分時間(並因此花費那麼多)。 為了簡單起見,假設我們不知道f是什麼,但假設它是0.1,0.2,0.3,... 0.9,1.0,並且每個這些可能性的先驗概率都是0.1,所以所有這些成本是平等的可能是先驗的。

那麼假設我們只取兩堆樣本,並且我們在兩個樣本上看到指令I ,指定的觀測值o=2/2 。 這給了我們對I的頻率f的新估計,根據這個:

Prior                                    
P(f=x) x  P(o=2/2|f=x) P(o=2/2&&f=x)  P(o=2/2&&f >= x)  P(f >= x)

0.1    1     1             0.1          0.1            0.25974026
0.1    0.9   0.81          0.081        0.181          0.47012987
0.1    0.8   0.64          0.064        0.245          0.636363636
0.1    0.7   0.49          0.049        0.294          0.763636364
0.1    0.6   0.36          0.036        0.33           0.857142857
0.1    0.5   0.25          0.025        0.355          0.922077922
0.1    0.4   0.16          0.016        0.371          0.963636364
0.1    0.3   0.09          0.009        0.38           0.987012987
0.1    0.2   0.04          0.004        0.384          0.997402597
0.1    0.1   0.01          0.001        0.385          1

                  P(o=2/2) 0.385                

最後一列說,例如, f > = 0.5的概率是92%,高於之前的60%的假設。

假設先前的假設是不同的。 假設我們假設P(f = 0.1)為.991(幾乎可以肯定),並且所有其他可能性幾乎不可能(0.001)。 換句話說,我們以前的確定性是I便宜。 然後我們得到:

Prior                                    
P(f=x) x  P(o=2/2|f=x) P(o=2/2&& f=x)  P(o=2/2&&f >= x)  P(f >= x)

0.001  1    1              0.001        0.001          0.072727273
0.001  0.9  0.81           0.00081      0.00181        0.131636364
0.001  0.8  0.64           0.00064      0.00245        0.178181818
0.001  0.7  0.49           0.00049      0.00294        0.213818182
0.001  0.6  0.36           0.00036      0.0033         0.24
0.001  0.5  0.25           0.00025      0.00355        0.258181818
0.001  0.4  0.16           0.00016      0.00371        0.269818182
0.001  0.3  0.09           0.00009      0.0038         0.276363636
0.001  0.2  0.04           0.00004      0.00384        0.279272727
0.991  0.1  0.01           0.00991      0.01375        1

                  P(o=2/2) 0.01375                

現在它說P(f> = 0.5)是26%,高於前面的0.6%。 所以貝葉斯允許我們更新我對可能成本的估計。 如果數據量很小,它不會準確告訴我們成本是多少,只是它足夠大才值得修復。

另一種看待它的方式被稱為繼承規則 。 如果您將硬幣翻轉兩次,並且兩次都會出現,那麼可以告訴您硬幣的可能重量是什麼? 推薦的答案是說它是一個Beta分佈,平均值(命中數+ 1)/(嘗試次數+2)=(2 + 1)/(2 + 2)= 75%。

(關鍵是我們不止一次看到我們,如果我們只看到它一次,那除了f > 0之外並沒有多大意義)

因此,即使只有極少數的樣本可以告訴我們很多關於它所看到的指令的成本。 (平均來看,它們的頻率與它們的成本成正比,如果取n樣本, f是成本,那麼I將出現在nf+/-sqrt(nf(1-f))樣本上。 , n=10f=0.3 ,即3+/-1.4樣品。)

ADDED,為測量和隨機堆棧採樣之間的差異提供直觀的感受:
即使在掛鐘時間,現在還有一些探測器可以對棧進行採樣,但是出現的是測量結果(或熱路徑或熱點,“瓶頸”很容易隱藏起來)。 他們沒有向你展示(他們很容易就可以)是他們自己的實際樣本。 如果你的目標是找到瓶頸,那麼你需要看到的數量平均為 2,除以所花費的時間。 因此,如果需要30%的時間,2 / .3 = 6.7個樣本平均會顯示出來,而20個樣本顯示的可能性為99.2%。

以下是檢查測量結果與檢查堆棧樣本之間差異的非常規說明。 瓶頸可能是這樣一個大塊,或許多小塊,它沒有什麼區別。

測量是水平的; 它會告訴你具體例程所花的時間是多少。 採樣是垂直的。 如果有什麼方法可以避免整個程序在那個時候正在做什麼, 並且如果你在第二個樣本上看到它 ,你就發現了瓶頸。 這就是造成這種差異的原因 - 看到時間消耗的全部原因,而不僅僅是多少。


您可以使用Valgrind和以下選項

valgrind --tool=callgrind ./(Your binary)

它會生成一個名為callgrind.out.x的文件。 然後可以使用kcachegrind工具讀取該文件。 它會給你一個結果的圖形分析,比如哪條線要花多少錢。


我假設你正在使用GCC。 標準解決方案是使用gprof進行配置。

分析前請確保將-pg添加到編譯中:

cc -o myprog myprog.c utils.c -g -pg

我還沒有嘗試過,但我聽說過有關google-perftoolsgoogle-perftools 。 這絕對值得一試。

相關問題here

如果gprof不為你工作,還有其他一些流行語: Valgrind ,Intel VTune ,Sun DTrace


沒有一些選項,運行valgrind --tool=callgrind的答案並不完整。 我們通常不希望在Valgrind中簡介10分鐘的啟動時間,並且想要在執行某項任務時剖析我們的程序。

所以這是我的建議。 首先運行程序:

valgrind --tool=callgrind --dump-instr=yes -v --instr-atstart=no ./binary > tmp

現在,當它工作並且我們想開始分析時,我們應該在另一個窗口中運行:

callgrind_control -i on

這輪到分析。 要關閉它並停止整個任務,我們可以使用:

callgrind_control -k

現在我們在當前目錄下有一些名為callgrind.out。*的文件。 要查看性能分析結果,請使用:

kcachegrind callgrind.out.*

我建議在下一個窗口中單擊“Self”列標題,否則顯示“main()”是最耗時的任務。 “自我”表明每個功能本身需要花費多少時間,而不是與受撫養人一起。


較新的內核(例如最新的Ubuntu內核)帶有新的'perf'工具( apt-get install linux-tools )AKA perf_events

這些配備了經典的採樣分析器( man-page )以及令人敬畏的timechart

重要的是,這些工具可以是系統分析 ,而不僅僅是進程分析 - 它們可以顯示線程,進程和內核之間的交互,並讓您了解進程之間的調度和I / O依賴關係。


這是對Nazgob的Gprof答案的回應 。

過去幾天我一直在使用Gprof,並且已經發現了三個重要的限制,其中一個我還沒有在其他地方見到過(還沒有):

  1. 它在多線程代碼上無法正常工作,除非您使用workaround

  2. 調用圖被函數指針弄糊塗了。 示例:我有一個名為multithread()的函數,它使我能夠在指定的數組上多線程指定的函數(都作為參數傳遞)。 但是,Gprof將所有對multithread()的調用視為等同於計算在兒童中花費的時間。 由於我傳遞給multithread()的一些函數比其他函數花費的時間要長得多,所以我的調用圖大多是無用的。 (對於那些想知道線程是否存在問題的人來說:no,multithread()可以選擇,在這種情況下,只能在調用線程上按順序運行)。

  3. here說,“...呼叫數字是通過計數而不是取樣得出的,它們是完全準確的......”。 然而,我發現我的通話圖給我5345859132 + 784984078作為我最常用的函數的調用統計,其中第一個數字應該是直接調用,第二個遞歸調用(全部來自它本身)。 由於這意味著我有一個錯誤,所以我在代碼中放入了很長的(64位)計數器,並再次執行相同的操作。 我的計數:5345859132直接和78094395406自我遞歸調用。 這裡有很多數字,所以我會指出我測量的遞歸調用是780億,而Gprof則是784萬:這是100個不同的因子。 兩次運行都是單線程和未優化的代碼,一次編譯-g和另一個-pg。

這是在64位Debian Lenny下運行的GNU Gprof (GNU Binutils for Debian)2.18.0.20080103,如果這有助於任何人。







profiling