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




gnu profiler (9)

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


Answers

我會使用Valgrind和Callgrind作為我的分析工具套件的基礎。 重要的是要知道Valgrind基本上是一台虛擬機:

(wikipedia)Valgrind本質上是一個使用即時(JIT)編譯技術(包括動態重新編譯)的虛擬機。 原始程序中的任何內容都不會直接在主處理器上運行。 相反,Valgrind首先將該程序轉換為臨時更簡單的稱為中間表示(IR)的表單,該表單是一種處理器中立的基於SSA的表單。 轉換之後,在Valgrind將IR轉換回機器碼並讓主機處理器運行之前,一個工具(見下文)可以自由地在IR上進行任何轉換。

Callgrind是一個建立在此基礎上的分析器。 主要好處是您不必為了獲得可靠的結果而運行幾個小時的應用程序。 即使是一秒鐘的運行也足以獲得堅如磐石,可靠的結果,因為Callgrind是一個非探測分析器。

Valgrind的另一個工具是Massif。 我用它來分析堆內存使用情況。 它效果很好。 它的功能是為您提供內存使用的快照 - 詳細信息WHAT包含了內存的百分比,並且WHO已將其放在那裡。 這些信息在應用程序運行的不同時間點可用。


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

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

只需暫停幾次,每次看看調用堆棧。 如果有一些代碼浪費了一定比例的時間,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%。

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

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


對於單線程程序,您可以使用igprof ,不明原因分析器: https://igprof.org/ ://igprof.org/。

它是一個採樣分析器,沿著Mike Dunlavey的... long ...的答案,它會將結果包裝在可瀏覽的調用堆棧樹中,用每個函數花費的時間或內存進行註釋,可以是累積的或每個函數。


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

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

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


我假設你正在使用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()”是最耗時的任務。 “自我”表明每個功能本身需要花費多少時間,而不是與受撫養人一起。


您可以使用Valgrind和以下選項

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

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


這些是我用來加速我的代碼的兩種方法:

對於CPU綁定應用程序:

  1. 在DEBUG模式下使用分析器來識別代碼中可疑的部分
  2. 然後切換到RELEASE模式,並註釋掉代碼中可疑的部分(不加任何東西),直到看到性能發生變化。

對於I / O綁定的應用程序:

  1. 在RELEASE模式下使用探查器來識別代碼中可疑的部分。

NB

如果您沒有探查器,請使用該窮人的探查器。 在調試應用程序時點擊暫停。 大多數開發人員套件將打破帶有註釋行號的程序集。 你在統計上可能降落在吃掉大部分CPU週期的地區。

對於CPU來說,在DEBUG模式下進行性能分析的原因是因為如果您嘗試在RELEASE模式下進行性能分析,編譯器將減少數學運算,向量化循環和內聯函數,這些函數在彙編時會將您的代碼變成不可映射的混亂。 一個不可映射的混亂意味著你的分析器將無法清楚地識別正在花費那麼久的東西,因為程序集可能不符合優化下的源代碼 。 如果您需要RELEASE模式的性能(例如時序敏感),請根據需要禁用調試器功能以保持可用的性能。

對於I / O界限,探查器仍然可以在RELEASE模式下識別I / O操作,因為I / O操作要么在外部鏈接到共享庫(大部分時間),要么在最壞的情況下會導致sys-調用中斷向量(這也很容易被分析器識別)。


這意味著標準現在定義了多線程,並且它定義了在多線程環境中發生的事情。 當然,人們使用不同的實現,但這就像問我們為什麼應該有一個std::string當我們都可以使用一個home-rolled string類時。

當您談論POSIX線程或Windows線程時,實際上您正在討論x86線程時,這有點虛幻,因為它是一個可同時運行的硬件功能。 無論您使用的是x86還是ARM,還是MIPS ,或其他任何您可以想到的,C ++ 0x內存模型都可以保證。





c++ unix profiling