Linuxで実行しているC ++アプリケーションがあります。これは最適化の最中です。コードのどの領域の実行が遅いのかを特定するにはどうすればよいですか?
code
プロファイラーです。ただし、優先順位の逆転、キャッシュのエイリアシング、リソースの競合などはすべて、最適化とパフォーマンスの要因となる可能性があります。人々は私の遅いコードに情報を読んだと思います。FAQがこのスレッドを参照しています。
Linuxで実行しているC ++アプリケーションがあります。これは最適化の最中です。コードのどの領域の実行が遅いのかを特定するにはどうすればよいですか?
code
プロファイラーです。ただし、優先順位の逆転、キャッシュのエイリアシング、リソースの競合などはすべて、最適化とパフォーマンスの要因となる可能性があります。人々は私の遅いコードに情報を読んだと思います。FAQがこのスレッドを参照しています。
回答:
プロファイラーを使用することが目的の場合は、推奨されているプロファイラーのいずれかを使用してください。
ただし、急いでいて、主観的に遅いときにデバッガーの下でプログラムを手動で中断できる場合は、パフォーマンスの問題を見つける簡単な方法があります。
数回停止するだけで、毎回コールスタックを確認できます。時間の何パーセントか、20%または50%などを浪費しているコードがある場合、それは各サンプルの動作でそれをキャッチする確率です。だから、それはあなたがそれを見るサンプルのおよそのパーセンテージです。教育を受けた当て推量は必要ありません。問題が何であるかについて推測がある場合、これはそれを証明または反証します。
サイズの異なる複数のパフォーマンスの問題がある場合があります。それらのいずれかをクリーンアップすると、残りのパスのパーセンテージが大きくなり、後続のパスで見つけやすくなります。この拡大効果は、複数の問題にまたがって組み合わされると、非常に大きなスピードアップ要因につながる可能性があります。
警告:プログラマは、自分で使用しない限り、この手法に懐疑的です。プロファイラーからこの情報が得られると言われますが、コールスタック全体をサンプリングし、ランダムなサンプルのセットを調べられる場合にのみ当てはまります。(要約は洞察が失われる場所です。)コールグラフは同じ情報を提供しません。
彼らはまた、実際にはどのプログラムでも機能する場合、おもちゃのプログラムでのみ機能することを示します。また、より大きなプログラムでより効果的に機能するようです。彼らはそれが問題ではないものを見つけることが時々あると言いますが、それはあなたが一度何かを見た場合にのみ当てはまります。複数のサンプルに問題がある場合、それは本当です。
PSこれは、Javaのように、ある時点でスレッドプールのコールスタックサンプルを収集する方法がある場合、マルチスレッドプログラムでも実行できます。
PPSおおまかな一般性として、ソフトウェアに抽象化の層が多ければ多いほど、それがパフォーマンスの問題の原因であることに気づく可能性が高くなります(そして高速化する機会が得られます)。
追加されました:明白ではないかもしれませんが、スタックサンプリングテクニックは、再帰が存在する場合でも同様に機能します。その理由は、命令の削除によって節約される時間は、サンプル内で発生する可能性のある回数に関係なく、命令を含むサンプルの割合によって概算されるためです。
私がよく聞く別の異論は、「どこかでランダムに停止し、本当の問題を見逃す」です。これは、実際の問題が何であるかについての事前の概念を持っていることに由来します。パフォーマンスの問題の主要な特性は、期待に反することです。サンプリングは何かが問題であることを示し、あなたの最初の反応は不信です。それは当たり前のことですが、それが本当の問題を見つけた場合は確信が持て、逆もまた同様です。
追加:それがどのように機能するかのベイジアン説明をさせてください。I
呼び出しスタックf
にある時間の一部の命令(呼び出しまたはその他)があるとします(したがって、その分コストがかかります)。簡単にするために、私たちは何を知らないと仮定しますf
は、0.1、0.2、0.3、... 0.9、1.0のいずれかであり、これらの各可能性の事前確率は0.1であるため、これらのすべてのコストは等しく可能性がありますアプリオリ。
次に、2つのスタックサンプルのみを取得I
し、両方のサンプルについての説明をObservationと表示するとしo=2/2
ます。これは私たちに、周波数の新しい推計与えf
のを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 | o=2/2)
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以上の、以前の仮定である60%から92%であることを示しています。
以前の仮定が異なると仮定します。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 | o=2/2)
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)
、以前の想定であった0.6%から26%増加しています。ベイズでは、の推定コストの見積もりを更新できますI
。データの量が少ない場合、コストは正確にはわかりません。修正するだけの十分な大きさがあるということだけです。
それを見る別の方法は、継承のルールと呼ばれます。コインを2回裏返して、両方とも表に出た場合、コインの予想される重み付けについて何がわかりますか?尊敬される答えは、それが平均値を持つベータ分布であると言うことです(number of hits + 1) / (number of tries + 2) = (2+1)/(2+2) = 75%
。
(重要なのは、I
2回以上表示されることです。1回しか表示されない場合、f
> 0 以外はあまりわかりません。)
そのため、ごく少数のサンプルでも、表示される指示のコストについて多くを知ることができます。(そして、それは場合。そのコストに比例し、平均で、頻度でそれらを見ることができますn
サンプルが取られ、そしてf
コストで、その後I
に表示されますnf+/-sqrt(nf(1-f))
サンプル。例、n=10
、f=0.3
、つまり3+/-1.4
、サンプル)。
追加:測定とランダムスタックサンプリングの違いを直感的に理解できるようにするために:実
時間でさえ、スタックをサンプリングするプロファイラーがありますが、出てくるのは測定(またはホットパス、またはホットスポット)です「ボトルネック」は簡単に隠すことができます)。彼らがあなたに見せていない(そして彼らは簡単にそうすることができた)のは実際のサンプルそのものです。そして、あなたの目標がボトルネックを見つけることであるならば、あなたが見る必要があるそれらの数は、平均して、それを要する時間の割合で割った2です。したがって、30%の時間がかかる場合、平均して2 / .3 = 6.7サンプルで表示され、20サンプルで表示される可能性は99.2%です。
これは、測定値の調査とスタックサンプルの調査の違いを示した、わかりやすいイラストです。ボトルネックは、このような1つの大きなblobでも、多数の小さなblobでもかまいません。違いはありません。
測定は水平です。特定のルーチンにかかる時間の割合がわかります。サンプリングは垂直です。その時点でプログラム全体が実行していることを回避する方法があり、2番目のサンプルでそれを確認した場合、ボトルネックが見つかりました。それが違いを生む理由です。どれだけ時間がかかっているのかではなく、費やされた時間の全体的な理由を見ることです。
次のオプションでValgrindを使用できます
valgrind --tool=callgrind ./(Your binary)
というファイルが生成されcallgrind.out.x
ます。その後、kcachegrind
ツールを使用してこのファイルを読み取ることができます。それはあなたに物事のグラフィカルな分析を与え、どのラインがどれくらいの費用がかかるかのような結果をもたらします。
gprof2dot
is now here:github.com/jrfonseca/gprof2dot
GCCを使用していると思います。標準的な解決策は、gprofでプロファイルすることです。
-pg
プロファイリングの前に必ずコンパイルに追加してください。
cc -o myprog myprog.c utils.c -g -pg
まだ試していませんが、google-perftoolsについて良いことは聞いています。それは間違いなく試してみる価値があります。
関連質問はこちら。
新しいカーネル(たとえば、最新のUbuntuカーネル)には、新しい「perf」ツール(apt-get install linux-tools
)、別名perf_eventsが付属しています。
これらには、クラシックなサンプリングプロファイラー(マンページ)と素晴らしいタイムチャートが付属しています。
重要なことは、これらのツールはプロセスプロファイリングだけでなく、システムプロファイリングにもなり得ます。これらは、スレッド、プロセス、およびカーネル間の相互作用を示し、プロセス間のスケジューリングとI / Oの依存関係を理解させます。
perf report
呼び出しの親を持つ関数名を私に与えているようです...(それは一種の逆バタフライビューです)
gprof2dot
とperf script
。とても良いツールです!
perf
に存在するarchive.li/9r927#selection-767.126-767.271 (SOの神々は、SOの知識ベースからそのページを削除することを決めたのはなぜ....私を超えている)
私はプロファイリングツールスイートのベースとしてValgrindとCallgrindを使用します。知っておくべき重要なことは、Valgrindは基本的に仮想マシンであることです。
(wikipedia)Valgrindは本質的に、動的再コンパイルを含むジャストインタイム(JIT)コンパイル技術を使用する仮想マシンです。元のプログラムから何もホストプロセッサで直接実行されることはありません。代わりに、Valgrindは最初にプログラムを、中間表現(IR)と呼ばれる一時的で単純な形式に変換します。これは、プロセッサに依存しない、SSAベースの形式です。変換後、ValgrindがIRをマシンコードに変換して戻し、ホストプロセッサで実行できるようになる前に、ツール(以下を参照)はIRで必要な変換を自由に実行できます。
Callgrindは、その上に構築されたプロファイラーです。主な利点は、信頼できる結果を得るために何時間もアプリケーションを実行する必要がないことです。Callgrindは非プロービングプロファイラーであるため、1秒の実行でさえ、確実で信頼できる結果を得るには十分です。
Valgrindに基づくもう1つのツールはMassifです。ヒープメモリ使用量のプロファイルに使用します。それは素晴らしい働きをします。それが行うことは、メモリ使用量のスナップショットを提供することです-詳細情報WHATはメモリのWHATパーセンテージを保持し、WHOはそこに配置しました。このような情報は、アプリケーション実行のさまざまな時点で利用できます。
実行する答え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()」が最も時間がかかるタスクであることを示しています。「自己」は、各機能自体が依存関係と一緒にではなく、どれだけの時間を費やしたかを示します。
CALLGRIND_TOGGLE_COLLECT
プログラムで収集を有効/無効にすることもできます。stackoverflow.com/a/13700817/288875を
これは、NazgobのGprof回答に対する応答です。
私は過去2日間にGprofを使用しており、すでに3つの重要な制限が見つかりました。
回避策を使用しない限り、マルチスレッドコードでは正しく機能しません。
呼び出しグラフは、関数ポインターによって混乱します。例:私は、multithread()
指定された配列(両方とも引数として渡されます)に対して指定された関数をマルチスレッド化できるようにする関数を呼び出しました。ただし、Gprofは、multithread()
子供たちの時間を計算するために、すべての呼び出しを同等と見なしています。一部の関数は他の関数multithread()
よりも時間がかかるため、コールグラフはほとんど役に立ちません。(ここでスレッド化が問題かどうか疑問に思う人には、いいえ、multithread()
オプションで可能で、この場合は、呼び出しスレッドでのみすべてを順番に実行します)。
それは言うここで、「サンプリングしていない、...数の-通話数値は計数により派生しています。彼らは完全に正確である...」ということ。それでも、私のコールグラフでは、5345859132 + 784984078が私の最も呼び出された関数の呼び出し統計として得られます。最初の番号は直接呼び出しで、2番目の再帰呼び出し(すべてそれ自体)です。これはバグがあることを意味していたので、コードに長い(64ビット)カウンターを挿入し、同じようにもう一度実行しました。私のカウント:5345859132直接、および78094395406自己再帰呼び出し。桁数が多いので、測定した再帰呼び出しが78bnであるのに対し、Gprofからの784mは100倍異なることを指摘しておきます。どちらの実行も、1つはコンパイルさ-g
れ、もう1つはシングルスレッドの最適化されていないコード-pg
でした。
これは、64ビットのDebian Lennyで動作するGNU Gprof(GNU Binutils for Debian)2.18.0.20080103でした。
Valgrind、callgrind、kcachegrindを使用します。
valgrind --tool=callgrind ./(Your binary)
callgrind.out.xを生成します。kcachegrindを使用して読んでください。
gprofを使用(-pgを追加):
cc -o myprog myprog.c utils.c -g -pg
(マルチスレッド、関数ポインターにはあまり適していません)
google-perftoolsを使用します。
時間サンプリングを使用して、I / OとCPUのボトルネックが明らかになりました。
インテルVTuneは最高です(教育目的で無料)。
その他: AMD Codeanalyst(AMD CodeXLに置き換えられたため)、OProfile、 'perf'ツール(apt-get install linux-tools)
C ++プロファイリング技術の調査
この回答では、いくつかの異なるツールを使用して、いくつかの非常に単純なテストプログラムを分析し、それらのツールの動作を具体的に比較します。
次のテストプログラムは非常に単純で、次のことを行います。
main
呼び出しfast
とmaybe_slow
3回、maybe_slow
呼び出しの1つが遅い
の遅い呼び出しmaybe_slow
は10倍長く、子関数の呼び出しを考慮するとランタイムを支配しますcommon
。理想的には、プロファイリングツールが特定の遅い呼び出しを示すことができるようになります。
プログラム実行の大部分を占めるfast
とmaybe_slow
呼び出しの両方common
プログラムインターフェイスは次のとおりです。
./main.out [n [seed]]
そしてプログラムはO(n^2)
合計でループします。seed
ランタイムに影響を与えずに異なる出力を取得するだけです。
main.c
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
uint64_t __attribute__ ((noinline)) common(uint64_t n, uint64_t seed) {
for (uint64_t i = 0; i < n; ++i) {
seed = (seed * seed) - (3 * seed) + 1;
}
return seed;
}
uint64_t __attribute__ ((noinline)) fast(uint64_t n, uint64_t seed) {
uint64_t max = (n / 10) + 1;
for (uint64_t i = 0; i < max; ++i) {
seed = common(n, (seed * seed) - (3 * seed) + 1);
}
return seed;
}
uint64_t __attribute__ ((noinline)) maybe_slow(uint64_t n, uint64_t seed, int is_slow) {
uint64_t max = n;
if (is_slow) {
max *= 10;
}
for (uint64_t i = 0; i < max; ++i) {
seed = common(n, (seed * seed) - (3 * seed) + 1);
}
return seed;
}
int main(int argc, char **argv) {
uint64_t n, seed;
if (argc > 1) {
n = strtoll(argv[1], NULL, 0);
} else {
n = 1;
}
if (argc > 2) {
seed = strtoll(argv[2], NULL, 0);
} else {
seed = 0;
}
seed += maybe_slow(n, seed, 0);
seed += fast(n, seed);
seed += maybe_slow(n, seed, 1);
seed += fast(n, seed);
seed += maybe_slow(n, seed, 0);
seed += fast(n, seed);
printf("%" PRIX64 "\n", seed);
return EXIT_SUCCESS;
}
gprof
gprofは、ソフトウェアを計装で再コンパイルする必要があり、また、その計装とともにサンプリングアプローチを使用します。したがって、精度(サンプリングは常に完全に正確であるとは限らず、関数をスキップできる)と実行速度の低下(計装とサンプリングは実行速度をあまり低下させない比較的高速な手法)の間でバランスを取ります。
gprofはGCC / binutilsに組み込まれ-pg
ているため、gprofを有効にするオプションを指定してコンパイルするだけで済みます。次に、通常は数秒(10000
)の妥当な期間の実行を生成するサイズCLIパラメーターを使用してプログラムを実行します。
gcc -pg -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time ./main.out 10000
教育上の理由から、最適化を有効にせずに実行も行います。通常、最適化されたプログラムのパフォーマンスの最適化のみに関心があるため、これは実際には役に立たないことに注意してください。
gcc -pg -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out 10000
最初に、time
実行時間と実行時間-pg
は同じだったと言っています。ただし、たとえばこのチケットに示されているように、複雑なソフトウェアの2倍から3倍の速度低下の説明を見たことがある。
でコンパイルしたため-pg
、プログラムを実行すると、gmon.out
プロファイリングデータを含むファイルファイルが生成されます。
次のgprof2dot
質問で、そのファイルをグラフィカルに観察できます。gprofの結果をグラフィカルに表示することはできますか?
sudo apt install graphviz
python3 -m pip install --user gprof2dot
gprof main.out > main.gprof
gprof2dot < main.gprof | dot -Tsvg -o output.svg
ここで、gprof
ツールはgmon.out
トレース情報を読み取り、人間が読めるレポートをで生成します。レポートはmain.gprof
、gprof2dot
グラフを生成するために読み取られます。
gprof2dotのソース:https : //github.com/jrfonseca/gprof2dot
-O0
実行時に次のことが観察されます。
そして-O3
実行のために:
-O0
出力はかなり自明です。たとえば、3つのmaybe_slow
呼び出しとその子呼び出しが合計実行時間の97.56%を占めることを示していますが、子maybe_slow
なしでそれ自体を実行すると、合計実行時間の0.00%を占めます。つまり、その関数で費やされた時間のほとんどすべてが子が呼び出します。
TODO:GDBで見ることができるのにmain
、なぜ-O3
出力から欠落しているのbt
ですか?GProfの出力から関数が欠落しているのは、gprofがコンパイルされたインストルメンテーションに加えてサンプリングベースであるためだと思い-O3
main
ます。高速すぎてサンプルがありません。
PNGではなくSVG出力を選択します。SVGはCtrl + Fで検索可能であり、ファイルサイズは約10倍小さくできるためです。また、生成された画像の幅と高さは、複雑なソフトウェアでは数万ピクセルeog
と非常に不自然で、PNGの場合はGNOME 3.28.1でバグが発生しますが、SVGはブラウザーによって自動的に開かれます。ただし、gimp 2.8はうまく機能しました。以下も参照してください。
しかし、それでも、画像をたくさんドラッグして必要なものを見つけます。たとえば、このチケットから取得した「実際の」ソフトウェアの例からこの画像を参照してください。
並べ替えられていない小さなすべてのスパゲッティラインが互いに重なり合うことで、最も重要なコールスタックを簡単に見つけることができますか?dot
確かにもっと良いオプションがあるかもしれませんが、今はそこに行きたくありません。私たちが本当に必要としているのは、そのための適切な専用ビューアですが、まだ見つかりません。
ただし、カラーマップを使用して、これらの問題を少し軽減することができます。たとえば、前の巨大な画像で、緑が赤の後に続き、最後に濃い青が続くという見事な演繹を行ったとき、私はようやく左側のクリティカルパスを見つけることができました。
または、gprof
以前に保存した組み込みのbinutilsツールのテキスト出力を確認することもできます。
cat main.gprof
デフォルトでは、これにより、出力データの意味を説明する非常に詳細な出力が生成されます。それ以上は説明できないので、自分で読んでもらいましょう。
データ出力形式を理解したら、冗長性を減らして、-b
オプションなしのチュートリアルなしでデータのみを表示できます。
gprof -b main.out
この例では、出力は次のものでした-O0
。
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
100.35 3.67 3.67 123003 0.00 0.00 common
0.00 3.67 0.00 3 0.00 0.03 fast
0.00 3.67 0.00 3 0.00 1.19 maybe_slow
Call graph
granularity: each sample hit covers 2 byte(s) for 0.27% of 3.67 seconds
index % time self children called name
0.09 0.00 3003/123003 fast [4]
3.58 0.00 120000/123003 maybe_slow [3]
[1] 100.0 3.67 0.00 123003 common [1]
-----------------------------------------------
<spontaneous>
[2] 100.0 0.00 3.67 main [2]
0.00 3.58 3/3 maybe_slow [3]
0.00 0.09 3/3 fast [4]
-----------------------------------------------
0.00 3.58 3/3 main [2]
[3] 97.6 0.00 3.58 3 maybe_slow [3]
3.58 0.00 120000/123003 common [1]
-----------------------------------------------
0.00 0.09 3/3 main [2]
[4] 2.4 0.00 0.09 3 fast [4]
0.09 0.00 3003/123003 common [1]
-----------------------------------------------
Index by function name
[1] common [4] fast [3] maybe_slow
そしてのために-O3
:
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls us/call us/call name
100.52 1.84 1.84 123003 14.96 14.96 common
Call graph
granularity: each sample hit covers 2 byte(s) for 0.54% of 1.84 seconds
index % time self children called name
0.04 0.00 3003/123003 fast [3]
1.79 0.00 120000/123003 maybe_slow [2]
[1] 100.0 1.84 0.00 123003 common [1]
-----------------------------------------------
<spontaneous>
[2] 97.6 0.00 1.79 maybe_slow [2]
1.79 0.00 120000/123003 common [1]
-----------------------------------------------
<spontaneous>
[3] 2.4 0.00 0.04 fast [3]
0.04 0.00 3003/123003 common [1]
-----------------------------------------------
Index by function name
[1] common
各セクションの非常に簡単な要約として:
0.00 3.58 3/3 main [2]
[3] 97.6 0.00 3.58 3 maybe_slow [3]
3.58 0.00 120000/123003 common [1]
インデントされたままの関数を中心に配置します(maybe_flow
)。[3]
その関数のIDです。関数の上に呼び出し元があり、その下に呼び出し先があります。
については-O3
、ここではグラフィカル出力のように、既知の親がないことmaybe_slow
を参照してくださいfast
。これは、ドキュメントに書かれているとおりです<spontaneous>
。
gprofで行ごとのプロファイリングを行う良い方法があるかどうかはわかりません:特定のコード行で費やされた `gprof`時間
valgrind callgrind
valgrindは、valgrind仮想マシンを介してプログラムを実行します。これにより、プロファイリングは非常に正確になりますが、プログラムの速度が非常に遅くなります。また、以前にkcachegrindについて言及しました:コードの絵の関数呼び出しグラフを取得するツール
callgrindは、コードをプロファイルするvalgrindのツールであり、kcachegrindは、cachegrind出力を視覚化できるKDEプログラムです。
最初に-pg
、通常のコンパイルに戻るにはフラグを削除する必要がProfiling timer expired
あります。そうしないと、実行は実際にで失敗します。そうです、これは非常に一般的であり、スタックオーバーフローの質問がありました。
したがって、次のようにコンパイルして実行します。
sudo apt install kcachegrind valgrind
gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time valgrind --tool=callgrind valgrind --dump-instr=yes \
--collect-jumps=yes ./main.out 10000
--dump-instr=yes --collect-jumps=yes
これは、比較的小さな追加のオーバーヘッドコストで、アセンブリラインごとのパフォーマンスの内訳を表示できる情報もダンプするので、有効にします。
バット、time
プログラムを実行するために29.5秒かかったので、私たちは、この例の15倍程度の減速を持っていたことを教えてくれる。明らかに、このスローダウンは、より大きなワークロードにとって深刻な制限となるでしょう。ここで言及した「実際のソフトウェアの例」では、80倍の速度低下が見られました。
実行すると、callgrind.out.<pid>
たとえばcallgrind.out.8554
私の場合はという名前のプロファイルデータファイルが生成されます。そのファイルは次のように表示されます。
kcachegrind callgrind.out.8554
これは、テキストのgprof出力に類似したデータを含むGUIを示しています。
また、右下の「コールグラフ」タブに移動すると、右クリックしてエクスポートできるコールグラフが表示され、次の画像に無理な量の白い境界線が表示されます:-)
fast
kcachegrindは視覚化を単純化する必要があるため、そのグラフに表示されていないと思います。これは、呼び出しにかかる時間が少なすぎるためです。これは、実際のプログラムで必要な動作になる可能性があります。右クリックメニューには、そのようなノードをカリングするタイミングを制御するためのいくつかの設定がありますが、すばやく試行した後、そのような短い呼び出しを表示することができませんでした。fast
左側のウィンドウをクリックすると、で呼び出しグラフが表示されるfast
ため、スタックが実際にキャプチャされました。完全なグラフ呼び出しグラフを表示する方法はまだ誰も見つけていませんでした:callgrindにすべての関数呼び出しをkcachegrind呼び出しグラフに表示させる
複雑なC ++ソフトウェアでTODOを実行すると、typeのエントリがいくつか表示されます<cycle N>
。たとえば<cycle 11>
、関数名を予期している場合、それはどういう意味ですか?オンとオフを切り替える「サイクル検出」ボタンがあることに気づきましたが、どういう意味ですか?
perf
から linux-tools
perf
Linuxカーネルサンプリングメカニズムのみを使用しているようです。これにより、セットアップが非常に簡単になりますが、完全に正確ではありません。
sudo apt install linux-tools
time perf record -g ./main.out 10000
これにより、実行に0.2秒が追加されたので、時間的には問題ありませんcommon
が、キーボードの右矢印でノードを展開した後は、あまり関心がありません。
Samples: 7K of event 'cycles:uppp', Event count (approx.): 6228527608
Children Self Command Shared Object Symbol
- 99.98% 99.88% main.out main.out [.] common
common
0.11% 0.11% main.out [kernel] [k] 0xffffffff8a6009e7
0.01% 0.01% main.out [kernel] [k] 0xffffffff8a600158
0.01% 0.00% main.out [unknown] [k] 0x0000000000000040
0.01% 0.00% main.out ld-2.27.so [.] _dl_sysdep_start
0.01% 0.00% main.out ld-2.27.so [.] dl_main
0.01% 0.00% main.out ld-2.27.so [.] mprotect
0.01% 0.00% main.out ld-2.27.so [.] _dl_map_object
0.01% 0.00% main.out ld-2.27.so [.] _xstat
0.00% 0.00% main.out ld-2.27.so [.] __GI___tunables_init
0.00% 0.00% main.out [unknown] [.] 0x2f3d4f4944555453
0.00% 0.00% main.out [unknown] [.] 0x00007fff3cfc57ac
0.00% 0.00% main.out ld-2.27.so [.] _start
それで、-O0
プログラムをベンチマークして、それが何かを示しているかどうかを確認しようとしましたが、ついに、ようやくコールグラフが表示されるようになりました。
Samples: 15K of event 'cycles:uppp', Event count (approx.): 12438962281
Children Self Command Shared Object Symbol
+ 99.99% 0.00% main.out [unknown] [.] 0x04be258d4c544155
+ 99.99% 0.00% main.out libc-2.27.so [.] __libc_start_main
- 99.99% 0.00% main.out main.out [.] main
- main
- 97.54% maybe_slow
common
- 2.45% fast
common
+ 99.96% 99.85% main.out main.out [.] common
+ 97.54% 0.03% main.out main.out [.] maybe_slow
+ 2.45% 0.00% main.out main.out [.] fast
0.11% 0.11% main.out [kernel] [k] 0xffffffff8a6009e7
0.00% 0.00% main.out [unknown] [k] 0x0000000000000040
0.00% 0.00% main.out ld-2.27.so [.] _dl_sysdep_start
0.00% 0.00% main.out ld-2.27.so [.] dl_main
0.00% 0.00% main.out ld-2.27.so [.] _dl_lookup_symbol_x
0.00% 0.00% main.out [kernel] [k] 0xffffffff8a600158
0.00% 0.00% main.out ld-2.27.so [.] mmap64
0.00% 0.00% main.out ld-2.27.so [.] _dl_map_object
0.00% 0.00% main.out ld-2.27.so [.] __GI___tunables_init
0.00% 0.00% main.out [unknown] [.] 0x552e53555f6e653d
0.00% 0.00% main.out [unknown] [.] 0x00007ffe1cf20fdb
0.00% 0.00% main.out ld-2.27.so [.] _start
TODO:-O3
実行時に何が起こりましたか?それは単にそれでmaybe_slow
あり、fast
速すぎてサンプルを取得しませんでしたか?-O3
実行に時間がかかる大きなプログラムでもうまく機能しますか?一部のCLIオプションを見逃しましたか?-F
ヘルツでサンプル周波数を制御しようとしましたが、デフォルトで許可されている最大値-F 39500
(で増加可能sudo
)まで上げましたが、明確な呼び出しが表示されません。
perf
すばらしい点の1つは、Brendan GreggのFlameGraphツールです。これにより、コールスタックのタイミングが非常にきちんと表示され、大きなコールをすばやく確認できます。このツールは、https://github.com/brendangregg/FlameGraphで入手できます。また、彼のperfチュートリアルでも言及されています。http://www.brendangregg.com/perf.html#FlameGraphs perf
なしで実行するsudo
とERROR: No stack counts found
、今私はそれでやりますsudo
:
git clone https://github.com/brendangregg/FlameGraph
sudo perf record -F 99 -g -o perf_with_stack.data ./main.out 10000
sudo perf script -i perf_with_stack.data | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flamegraph.svg
しかし、このような単純なプログラムでは、グラフmaybe_slow
もfast
グラフも簡単に確認できないため、出力を理解するのは簡単ではありません。
より複雑な例では、グラフの意味が明らかになります。
TODO [unknown]
その例には関数のログがありますが、それはなぜですか?
価値があると思われる別のパフォーマンスGUIインターフェイスには、次のものがあります。
Eclipse Trace Compassプラグイン:https : //www.eclipse.org/tracecompass/
ただし、これには、最初にデータをCommon Trace Formatに変換する必要があるという欠点があります。これは、で実行できますがperf data --to-ctf
、ビルド時に有効にする必要があります/ perf
十分に新しくする必要があります。どちらも、 Ubuntu 18.04
https://github.com/KDAB/hotspot
これの欠点は、Ubuntuパッケージがないように見えることであり、Ubuntu 18.04がQt 5.9であるのに、ビルドにはQt 5.10が必要です。
gperftools
以前は「Google Performance Tools」と呼ばれていました。ソース:https : //github.com/gperftools/gperftoolsサンプルベース。
最初にgperftoolsをインストールします:
sudo apt install google-perftools
次に、実行時とビルド時の2つの方法でgperftools CPUプロファイラーを有効にできます。
実行時に、をLD_PRELOAD
指すようにsetを渡すlibprofiler.so
必要があります。これはlocate libprofiler.so
、たとえば、私のシステムで、で見つけることができます。
gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libprofiler.so \
CPUPROFILE=prof.out ./main.out 10000
または、リンク時にライブラリをビルドして、LD_PRELOAD
実行時にパスをディスペンスすることもできます。
gcc -Wl,--no-as-needed,-lprofiler,--as-needed -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
CPUPROFILE=prof.out ./main.out 10000
参照:gperftools-ダンプされないプロファイルファイル
これまでに見つけたこのデータを表示する最も良い方法は、pprofの出力をkcachegrindが入力として取るのと同じ形式(そう、Valgrind-project-viewer-tool)にし、kcachegrindを使用してそれを表示することです。
google-pprof --callgrind main.out prof.out > callgrind.out
kcachegrind callgrind.out
これらの方法のいずれかで実行した後prof.out
、出力としてプロファイルデータファイルを取得します。以下のようにして、そのファイルをSVGとしてグラフィカルに表示できます。
google-pprof --web main.out prof.out
これは、他のツールと同じように、おなじみのコールグラフとして提供されますが、秒数ではなくサンプル数の不格好な単位を使用しています。
あるいは、次のようにしてテキストデータを取得することもできます。
google-pprof --text main.out prof.out
それは与える:
Using local file main.out.
Using local file prof.out.
Total: 187 samples
187 100.0% 100.0% 187 100.0% common
0 0.0% 100.0% 187 100.0% __libc_start_main
0 0.0% 100.0% 187 100.0% _start
0 0.0% 100.0% 4 2.1% fast
0 0.0% 100.0% 187 100.0% main
0 0.0% 100.0% 183 97.9% maybe_slow
Ubuntu 18.04、gprof2dot 2019.11.30、valgrind 3.13.0、perf 4.15.18、Linuxカーネル4.15.0、FLameGraph 1a0dc6985aad06e76857cf2a354bd5ba0c9ce96b、gperftools 2.5-2でテスト済み。
-fno-omit-frame-pointer
フラグを付けてコンパイルするか、別の代替案を使用することです。シナリオに応じて、--call-graph "dwarf"
または--call-graph "lbr"
シナリオに応じて記録します。
シングルスレッドプログラムの場合は、igprof、The Ignominous Profiler:https ://igprof.org/を使用できます。
これはサンプリングプロファイラーであり、...長い...マイクダンラベイによる回答です。これは、結果をブラウズ可能なコールスタックツリーにギフトラップし、累積または各関数で費やされた時間またはメモリで注釈が付けられます関数ごと。
また言及する価値があります
私はHPCToolkitとVTuneを使用しましたが、テントの長い極を見つけるのに非常に効果的で、コードを再コンパイルする必要はありません(意味のある出力を取得するために、CMakeで-g -OまたはRelWithDebInfoタイプのビルドを使用する必要がある場合を除く) 。TAUの機能は似ていると聞きました。
これらは、コードを高速化するために使用する2つの方法です。
CPUバウンドアプリケーションの場合:
I / Oバウンドアプリケーションの場合:
NB
プロファイラーがない場合は、貧しい人のプロファイラーを使用してください。アプリケーションのデバッグ中に一時停止を押します。ほとんどの開発者スイートは、コメント付きの行番号でアセンブリに分割されます。CPUサイクルの大部分を消費している領域に統計的に着陸する可能性があります。
CPUの場合、デバッグモードでプロファイリングを行う理由は、RELEASEモードでプロファイリングを試みた場合、コンパイラーが数学を削減し、ループをインライン化し、コードを組み立てたときにマップできない混乱にコードをインライン化する傾向があるためです。マップできない混乱は、アセンブリが最適化中のソースコードに対応していない可能性があるため、プロファイラーがそれほど時間がかかっていることを明確に識別できないことを意味します。RELEASEモードのパフォーマンス(たとえば、タイミング依存)が必要な場合は、使用可能なパフォーマンスを維持するために、必要に応じてデバッガー機能を無効にします。
I / Oバウンドの場合でも、プロファイラはRELEASEモードのI / O操作を識別できます。これは、I / O操作が共有ライブラリに外部リンクされている(ほとんどの場合)か、最悪の場合、結果としてsys-割り込みベクトルを呼び出します(これもプロファイラーによって簡単に識別できます)。
iprofライブラリを使用できます。
https://gitlab.com/Neurochrom/iprof
https://github.com/Neurochrom/iprof
これはクロスプラットフォームであり、アプリケーションのパフォーマンスをリアルタイムでも測定できないようにします。ライブグラフと組み合わせることもできます。完全な免責事項:私は著者です。
次のようなロギングフレームワークを使用できloguru
ます。これには、プロファイリングに適切に使用できるタイムスタンプと合計稼働時間が含まれています。
職場には、スケジューリングに関して必要なものを監視するのに役立つ非常に優れたツールがあります。これは何度も役に立ちました。
これはC ++で提供されており、ニーズに合わせてカスタマイズする必要があります。残念ながら、コードを共有することはできません。概念だけです。volatile
タイムスタンプとイベントIDを含む「大きな」バッファーを使用します。これは、事後分析またはロギングシステムの停止後にダンプできます(これをファイルにダンプするなど)。
すべてのデータを含むいわゆる大きなバッファーを取得し、小さなインターフェイスがそれを解析して、オシロスコープが色(.hpp
ファイルで構成されている)で行うように、名前(up / down + value)でイベントを表示します。
生成するイベントの量をカスタマイズして、必要なものだけに焦点を当てます。1秒あたりのログに記録されたイベントの量に基づいて必要なCPUの量を消費しながら、スケジュールの問題を解決するのに大いに役立ちました。
3つのファイルが必要です。
toolname.hpp // interface
toolname.cpp // code
tool_events_id.hpp // Events ID
コンセプトはtool_events_id.hpp
そのようにイベントを定義することです:
// EVENT_NAME ID BEGIN_END BG_COLOR NAME
#define SOCK_PDU_RECV_D 0x0301 //@D00301 BGEEAAAA # TX_PDU_Recv
#define SOCK_PDU_RECV_F 0x0302 //@F00301 BGEEAAAA # TX_PDU_Recv
また、いくつかの関数を定義しますtoolname.hpp
。
#define LOG_LEVEL_ERROR 0
#define LOG_LEVEL_WARN 1
// ...
void init(void);
void probe(id,payload);
// etc
コードのどこにでも使用できます:
toolname<LOG_LEVEL>::log(EVENT_NAME,VALUE);
このprobe
関数は、いくつかのアセンブリラインを使用してクロックタイムスタンプをできるだけ早く取得し、エントリをバッファに設定します。ログイベントを格納するインデックスを安全に見つけるためのアトミックな増分もあります。もちろんバッファは循環しています。
アイデアがサンプルコードの欠如によって難読化されていないことを願っています。
実際には少し驚いて、多くのgoogle / benchmarkについて言及していませんが、特にコードベースが少し大きい場合、コードの特定の領域を固定するのは少し面倒ですが、これをcallgrind
ボトルネックを引き起こしている部分を特定する私見は、ここでの鍵です。ただし、まず以下の質問に答えて、それに基づいてツールを選択します
valgrind
組み合わせでcallrind
とkcachegrind
上記の点にまともな推定を提供する必要があり、コードのいくつかの部分に問題があることを確立していますと、私はマイクロベンチマークを行うことをお勧めしたいことはgoogle benchmark
開始するには良い場所です。
-pg
コードをコンパイルおよびリンクするときにフラグを使用し、実行可能ファイルを実行します。このプログラムの実行中、プロファイリングデータはファイルa.outに収集されます。
2つの異なるタイプのプロファイリングがあります。
1-フラットプロファイリング:
コマンドgprog --flat-profile a.out
を実行すると、次のデータが得られます
-関数に費やされた全体の時間のパーセンテージ、
- 関数に費やされた秒
数- サブ関数の呼び出しを含め、除外して、-通話、
- 通話あたりの平均時間。
2-グラフプロファイリングを使用し
て、gprof --graph a.out
各関数について次のデータを取得するコマンドを含めます
。各セクションでは、1つの関数にインデックス番号が付いています。
-関数の上に、関数を呼び出す関数のリストがあります。
-関数の下に、関数によって呼び出される関数のリストがあります。
詳細については、https://sourceware.org/binutils/docs-2.32/gprof/をご覧ください。
デバッグソフトウェア を使用して、コードの実行速度が遅い場所を特定する方法
動いている間に障害物があると思うだけで速度が落ちます
そのような不要な再割り当てのループ、バッファオーバーフロー、検索、メモリリークなどの操作は、より多くの実行能力を消費し、コードのパフォーマンスに悪影響を及ぼします。プロファイリングの前に-pgをコンパイルに追加してください。
g++ your_prg.cpp -pg
またはcc my_program.cpp -g -pg
コンパイラごと
まだ試していませんが、google-perftoolsについて良いことを聞いています。それは間違いなく試してみる価値があります。
valgrind --tool=callgrind ./(Your binary)
gmon.outまたはcallgrind.out.xというファイルが生成されます。その後、kcachegrindまたはデバッガーツールを使用して、このファイルを読み取ることができます。それはあなたに物事のグラフィカルな分析を与え、どのラインがどれくらいの費用がかかるかのような結果をもたらします。
私はそう思う