私はC++アプリケーションを持っていて、それはLinux上で走っていて、それは最適化の過程にあります。コードのどの部分がゆっくり実行されているのかを特定するにはどうすればよいですか。
プロファイラーを使用することが目標の場合は、提案されているもののいずれかを使用してください。
ただし、急いでいて、デバッガーの下でプログラムが主観的に遅いときに手動でプログラムを中断できる場合は、パフォーマンスの問題を見つける簡単な方法があります。
数回停止するだけで、毎回コールスタックを確認します。 20%または50%など、何らかの割合で時間を浪費しているコードがある場合、それは各サンプルの行為でそれをキャッチする可能性です。だから、それはおおよそあなたがそれを見るサンプルの割合です。教育的な当て推量は必要ありません。問題が何であるかについて推測がある場合、これはそれを証明または反証します。
サイズの異なる複数のパフォーマンスの問題がある場合があります。これらのいずれかを削除すると、残りのパスの割合が大きくなり、後続のパスで見つけやすくなります。この拡大効果は、複数の問題で複合すると、本当に大きなスピードアップ要因になります。
警告:プログラマーは、自分で使用しない限り、この手法に懐疑的です。プロファイラーはこの情報を提供すると言いますが、それはコールスタック全体をサンプリングし、ランダムなサンプルセットを調べる場合にのみ当てはまります。 (要約は、洞察が失われる場所です。)コールグラフは同じ情報を提供しません。
また、実際にはどのプログラムでも動作しますが、おもちゃのプログラムでのみ動作し、大きなプログラムではより多くの問題が発生する傾向があるため、うまく動作するようです。彼らは時々問題ではないものを見つけると言うでしょうが、それは何かonceを見た場合にのみ真実です。複数のサンプルで問題が発生した場合、それは現実のものです。
追伸Javaのように、ある時点でスレッドプールの呼び出しスタックサンプルを収集する方法がある場合、これはマルチスレッドプログラムでも実行できます。
P.P.Sおおまかな一般性として、ソフトウェアの抽象化の層が多いほど、それがパフォーマンスの問題(および高速化の機会)の原因であることを発見する可能性が高くなります。
追加:明らかではないかもしれませんが、再帰が存在する場合でもスタックサンプリング手法は同様に機能します。その理由は、命令を削除することで節約される時間は、サンプル内で発生する回数に関係なく、その命令を含むサンプルの割合で概算されるためです。
私がよく耳にする別の反対意見は、「ランダムに停止し、実際の問題を見逃すことです」です。これは、実際の問題が何であるかという事前の概念を持っていることに由来します。パフォーマンスの問題の重要な特性は、期待に反することです。サンプリングは何か問題があることを伝え、最初の反応は信じられないことです。それは自然なことですが、問題が見つかった場合はそれが現実のものであり、その逆も同様です。
追加:どのように機能するかをベイジアンで説明させてください。コールスタック上にある時間のI
の一部(したがってコストがかかる)の命令f
(呼び出しまたはそれ以外)があるとします。簡単にするために、f
が何であるかわからないが、0.1、0.2、0.3、... 0.9、1.0のいずれかであり、これらの可能性のそれぞれの事前確率が0.1であると仮定すると、これらのコストはすべて等しくなりますおそらくアプリオリ。
次に、2つのスタックサンプルのみを取得し、両方のサンプルで命令I
が観察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)は26%であり、前の0.6%の仮定から上昇しています。したがって、Bayesでは、I
の推定コストの推定値を更新できます。データ量が少ない場合、コストが何であるかを正確に伝えることはできず、修正するだけの大きさがあるということだけです。
さらに別の見方をすると Rule Of Succession と呼ばれます。コインを2回裏返し、両方の場合に頭に浮かんだ場合、コインの可能性のある重みについて何がわかりますか?尊敬される答えは、平均値(ヒット数+ 1)/(試行数+ 2)=(2 + 1)/(2 + 2)= 75%のベータ分布であると言うことです。
(重要なのは、I
が複数回表示されることです。一度しか表示されない場合、f
> 0以外はあまりわかりません。)
そのため、ごく少数のサンプルでも、表示される命令のコストについて多くを知ることができます。 (そして、平均して、それらのコストに比例した頻度で表示されます。n
サンプルが取得され、f
がコストである場合、I
はnf+/-sqrt(nf(1-f))
サンプルに表示されます。例、n=10
、f=0.3
、つまり3+/-1.4
サンプルです。)
追加され、測定とランダムスタックサンプリングの違いを直感的に把握できます。
現在、実時間でもスタックをサンプリングするプロファイラーがありますが、出てくるのは測定値(またはホットパス、または「ボトルネック」が簡単に隠れることができるホットスポット)。彼らがあなたに見せない(そして簡単にできる)のは、実際のサンプルそのものです。そして、目標がfindボトルネックである場合、あなたが見る必要があるそれらの数は、平均で、2を時間の割合で割った値。したがって、30%の時間がかかる場合、平均で2/.3 = 6.7サンプルが表示され、20サンプルが表示される可能性は99.2%です。
これは、測定値の検査とスタックサンプルの検査の違いを、すぐにわかるように示したものです。ボトルネックは、このような1つの大きな塊であっても、多数の小さな塊であっても、違いはありません。
測定は水平です。特定のルーチンにかかる時間の割合がわかります。サンプリングは垂直です。その時点でプログラム全体が何をしているのかを回避する方法があれば、2番目のサンプルでそれを見ると、ボトルネック。それが違いを生むのです-どれだけの時間ではなく、費やされた時間の全体的な理由を見ること。
Valgrind を次のオプションと一緒に使用できます。
valgrind --tool=callgrind ./(Your binary)
callgrind.out.x
というファイルが生成されます。それからkcachegrind
ツールを使ってこのファイルを読むことができます。それはあなたに物事のグラフィカルな分析を与えて、どの行がいくらかかるかのような結果をもたらします。
新しいカーネル(例えば最新のUbuntuカーネル)には新しい 'perf'ツール(apt-get install linux-tools
)AKA perf_events が付属しています。
これらは古典的なサンプリングプロファイラ( man-page )と素晴らしい timechart !が付属しています。
重要なことは、これらのツールはプロセスプロファイリングだけでなくシステムプロファイリングでもいいということです - それらはスレッド、プロセス、カーネル間の相互作用を示し、プロセス間のスケジューリングとI/O依存性を理解させます。
プロファイリングツールスイートのベースとしてValgrindとCallgrindを使用します。知っておくべき重要なことは、Valgrindは基本的に仮想マシンであるということです。
(wikipedia)Valgrindは本質的には、動的再コンパイルを含む、ジャストインタイム(JIT)コンパイル技法を使用した仮想マシンです。 から元のプログラムがホストプロセッサ上で直接実行されることはありません代わりに、Valgrindは最初にプログラムを一時的で単純な形式に変換しますこれは、中間表現(IR)と呼ばれ、プロセッサに依存しない、 SSAベースの形式です。変換後、 Valgrindが IRに変換する前に、ツールを使用して IRに任意の変換を自由に行うことができます。マシンコードを入力し、ホストプロセッサに実行させます。
Callgrindはそれを基にしたプロファイラーです。主な利点は、信頼できる結果を得るために何時間もあなたのアプリケーションを実行する必要がないということです。 Callgrindは non-probing profilerなので、たった1秒の実行で十分で信頼できる結果を得ることができます。
Valgrindの上に構築された別のツールはMassifです。私はそれを使ってヒープメモリの使用状況をプロファイルします。それは素晴らしい仕事です。それがすることはそれがあなたにメモリ使用量のスナップショットを与えるということです - 詳細な情報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()"が最も時間のかかる作業であることを示しています。 「自己」は、各関数自体がどれだけ時間をかけたかを示します。
これは Nazgob's Gprof answer への回答です。
私はここ2、3日の間Gprofを使っていて、すでに3つの重大な制限を発見しました。
回避策 を使用しない限り、マルチスレッドコードでは正しく動作しません。
呼び出しグラフは関数ポインタによって混乱します。例:multithread()
という名前の関数があります。これにより、指定された関数を指定された配列上でマルチスレッド化できます(両方とも引数として渡されます)。しかし、Gprofは、子供に費やされる時間を計算するために、multithread()
へのすべての呼び出しを同等のものと見なします。私がmultithread()
に渡す関数の中には他のものよりはるかに長い時間がかかるので、私の呼び出しグラフはほとんど役に立ちません。 (スレッド化が問題であるかどうか疑問に思う人達には:いいえ、multithread()
はオプションとして、そしてこの場合は呼び出したスレッドだけですべてを順番に実行することができます)。
ここ は、「...コール数の数字は、サンプリングではなくカウントによって導き出されるものです。これらは完全に正確です...」それでも私の呼び出しグラフは、私の最もよく呼ばれる関数への呼び出し統計として5345859132 + 784984078を与えています。最初の番号は直接呼び出しで、2番目の再帰呼び出しはそれ自体です。これは私がバグを抱えていることを暗示していたので、私は長い(64ビット)カウンターをコードに入れて、同じ実行を再び行いました。私のカウント:5345859132ダイレクト、および78094395406自己再帰呼び出し。そこにたくさんの数字があるので、私が測定する再帰呼び出しが78億であることを指摘するつもりです、Gprofからの784メートル:100の異なる要因。どちらの実行もシングルスレッドで最適化されていないコードで、一方は-g
、もう一方は-pg
です。
これはGNU Gprof (GNU Binutils for Debian)2.18.0.20080103で、64ビットのDebian Lennyのもとで動作していれば、だれにでも役立ちます。
Valgrind、callgrind、およびkcachegrindを使用します。
valgrind --tool=callgrind ./(Your binary)
callgrind.out.xを生成します。 kcachegrindを使って読んでください。
gprof(add -pg)を使用します。
cc -o myprog myprog.c utils.c -g -pg
(マルチスレッド、関数ポインタにはあまり良くありません)
google-perftoolsを使用します。
タイムサンプリングを使用し、I/OとCPUのボトルネックを明らかにします。
Intel VTuneが最高です(教育目的のために無料です)。
その他: AMD Codeanalyst(以降AMD CodeXLに置き換えられました)、OProfile、 'perf'ツール(apt-get install linux-tools)
シングルスレッドプログラムの場合は、 igprof 、The Ignominous Profiler: https://igprof.org/ を使用できます。
Mike Dunlaveyによる... long ...回答の行に沿ったサンプリングプロファイラで、結果をブラウズ可能なコールスタックツリーにギフトラップし、各関数で費やされた時間またはメモリ、累積または機能ごと。
これらは、コードを高速化するために使用する2つの方法です。
CPUにバインドされたアプリケーションの場合:
I/Oバウンドアプリケーションの場合:
N.B.
プロファイラーがない場合は、貧乏人のプロファイラーを使用してください。アプリケーションのデバッグ中に一時停止します。ほとんどの開発者スイートは、コメント付きの行番号を使用してAssemblyに侵入します。統計的に、CPUサイクルの大部分を消費している地域に着地する可能性があります。
CPUの場合、DEBUGモードでプロファイリングを行う理由は、RELEASEモードでプロファイリングを試みた場合、コンパイラが数学、ベクトル化ループ、およびインライン関数を削減するためです。これは、組み立てられたときにあなたのコードをマッピング不可能な混乱に陥れがちです。 マッピング不可能な混乱は、アセンブリが最適化中のソースコードに対応していない可能性があるため、プロファイラがそれほど時間がかかるものを明確に識別できないことを意味します。 RELEASEモードのパフォーマンス(タイミングに敏感な場合など)が必要な場合は、使用可能なパフォーマンスを維持するために必要に応じてデバッガ機能を無効にします。
入出力操作の場合、プロファイラーはRELEASEモードで入出力操作を識別することができます(入出力操作はほとんどの場合)外部ライブラリーにリンクされているか、最悪の場合です。場合は、sys-call割り込みベクトルになります(これはプロファイラによって簡単に識別可能です)。
言及する価値がある
私はHPCToolkitとVTuneを使用しました。それらはテントの中で長いポールを見つけるのに非常に効果的であり、あなたのコードを再コンパイルする必要はありません。 TAUの機能は似ていると聞いたことがあります。
loguru
のようなロギングフレームワークを使用することができます。それはプロファイリングにうまく使用できるタイムスタンプと合計稼働時間を含んでいるからです。
あなたはiprofライブラリを使うことができます:
https://gitlab.com/Neurochrom/iprof
https://github.com/Neurochrom/iprof
これはクロスプラットフォームであり、アプリケーションのパフォーマンスをリアルタイムで測定しないようにできます。あなたはそれをライブグラフと組み合わせることさえできます。
仕事で私達は私達が私達がスケジューリングに関して欲しいものをモニターするのを助ける本当に素晴らしいツールを持っています。これは何度も役に立ちました。
これはC++で作成されており、ニーズに合わせてカスタマイズする必要があります。残念ながら私はコードを共有することはできません、概念だけです。あなたはタイムスタンプとイベントIDを含んだ "大きな" volatile
バッファを使います。これはあなたが死後のデータをダンプしたりロギングシステムを停止した後にダンプすることができます(そして例えばこれをファイルにダンプします)。
オシロスコープが.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
関数は、いくつかのAssembly行を使用してクロックタイムスタンプをできるだけ早く取得してから、バッファーにエントリーを設定します。ログイベントを格納するインデックスを安全に見つけるために、アトミックインクリメントもあります。もちろんバッファは循環型です。
サンプルコードがないことでアイデアが難読化されないことを願っています。
Arm MAPについては誰も言及していなかったので、個人的にMapを使用してC++科学プログラムのプロファイルを作成したので追加します。
Arm MAPは、並列、マルチスレッド、またはシングルスレッドのC、C++、Fortran、F90コードのプロファイラーです。詳細な分析と、ソース行のボトルネックの特定を提供します。ほとんどのプロファイラーとは異なり、並列スレッドおよびスレッドコードのpthread、OpenMP、またはMPIをプロファイリングできるように設計されています。
MAPは商用ソフトウェアです。