他にどのようなプログラムがgprofと同じことをしますか?
7 に答える
gprof (論文を読む)は歴史的な理由で存在します。パフォーマンスの問題を見つけるのに役立つと思われる場合は、そのように宣伝されることはありませんでした。論文の内容は次のとおりです。
このプロファイルを使用して、さまざまな実装のコストを比較および評価できます。
評価するさまざまな実装を特定するために使用できるとは言えませんが、特別な状況下で使用できることを意味します。
特に、プログラムのごく一部が実行時間を支配していることがわかった場合。
それほどローカライズされていない問題はどうですか?それらは関係ありませんか?決して主張されなかったgprofに期待をかけないでください。これは単なる測定ツールであり、CPUバウンド操作のみです。
代わりにこれを試してください。
これが44倍のスピードアップの例です。
これが730倍のスピードアップです。
これが8分間のビデオデモンストレーションです。
これが統計の説明です。
これが批評への答えです。
プログラムについての簡単な観察があります。特定の実行では、すべての命令が全体の時間の一部(特にcall
命令)を担当します。つまり、そこにない場合は時間が費やされないという意味です。その間、命令はスタック**にあります。それが理解されると、あなたはそれを見ることができます-
gprofは、パフォーマンスに関する特定の神話を具体化しています。
そのプログラムカウンターサンプリングは便利です。
これは、スカラー値の大きな配列のようなバブルソートなど、不要なホットスポットのボトルネックがある場合にのみ役立ちます。たとえば、string-compareを使用してソートに変更するとすぐにボトルネックになりますが、ホットスポットがstring-compareにあるため、プログラムカウンターサンプリングでは認識されません。一方、拡張プログラムカウンター(呼び出しスタック)をサンプリングする場合は、文字列比較が呼び出されるポイントであるソートループが明確に表示されます。実際、gprofは、PCのみのサンプリングの制限を修正する試みでした。そのタイミング関数は、時間のかかるコード行をキャプチャするよりも重要です。
その神話の理由は、gprofがスタックサンプルをキャプチャできなかったため、代わりに関数の時間を計測し、それらの呼び出しをカウントし、コールグラフをキャプチャしようとするためです。ただし、コストのかかる関数が特定された後でも、その関数の内部を調べて、時間の原因となる行を探す必要があります。調べる必要のないスタックサンプルがある場合、それらの行はサンプル上にあります。(一般的な関数には100〜1000の命令が含まれる場合があります。関数呼び出しは1命令であるため、コストのかかる呼び出しを見つけるものは2〜3桁正確です。)コールグラフが重要であること。
プログラムについて知っておく必要があるのは、プログラムがどこで時間を費やすかではなく、その理由です。。関数に時間を費やしているとき、スタック上のコードのすべての行は、それがそこにある理由の推論のチェーンの1つのリンクを提供します。スタックの一部しか見えない場合は、理由の一部しか見えないので、その時間が実際に必要かどうかはわかりません。コールグラフは何を教えてくれますか?各アークは、ある関数Aがある関数Bを呼び出している途中であったことを示しています。AにBを呼び出すそのようなコード行が1つしかない場合でも、その行は理由のごく一部しか示していません。運が良ければ、その行には理由がないかもしれません。通常、それがそこにある場合、あなたは悪い理由を見つけるために複数の同時行を見る必要があります。Aが複数の場所でBに電話をかけた場合、それはあなたにさらに少ないことを伝えます。その再帰はトリッキーな紛らわしい問題です。
これは、gprofおよびその他のプロファイラーが、コールグラフを生成してから時間をノードに割り当てる必要があると認識しているためです。スタックのサンプルがある場合、サンプルに表示されるコードの各行の時間コストは非常に単純な数値であり、サンプルの割合です。再帰がある場合、特定の行がサンプルに複数回表示される可能性があります。 どんなに。Nミリ秒ごとにサンプルが取得され、そのF%に線が表示されるとします(単独かどうかは関係ありません)。その行に時間がかからないようにできる場合(削除したり、その周りを分岐したりするなど)、それらのサンプルは消え、時間がF%短縮されます。時間測定の精度(したがって多数のサンプル)が重要です。
ちょっと考えてみてください。コードの行が5つのうち3つのサンプルにある場合、電球のようにそれを発射できれば、使用される時間は約60%短縮されます。これで、別の5つのサンプルを取得した場合、2回、つまり4回しか表示されなかった可能性があることがわかります。したがって、60%の測定値は、40%から80%の一般的な範囲に似ています。40%しかない場合、問題を修正する価値はないと思いますか?では、問題を見つけることが本当に必要なのは、時点の精度とは何でしょうか。500または5000のサンプルでは、問題をより正確に測定できたはずですが、それ以上正確に問題を見つけることはできませんでした。ステートメントまたは関数の呼び出しを数えることは有用です。
関数が1000回呼び出されたことを知っているとします。そのことから、時間の何分の1の費用がかかるかわかりますか?また、平均して実行にかかる時間を知り、それをカウントで乗算し、合計時間で割る必要があります。平均呼び出し時間はナノ秒から秒まで変化する可能性があるため、カウントだけではあまりわかりません。スタックサンプルがある場合、ルーチンまたはステートメントのコストは、それが存在するサンプルのほんの一部です。その時間の一部は、ルーチンまたはステートメントに時間がかからないようにすることができれば、原則として全体的に節約できるものです。したがって、パフォーマンスと最も直接的な関係があります。ブロックされたときにサンプルを取得する必要がないという
この神話の理由は2つあります。1)プログラムが待機しているときはPCサンプリングが無意味である、2)タイミングの正確さへのこだわり。ただし、(1)の場合、プログラムは、ファイルI / Oなど、知っておく必要のある、スタックサンプルが明らかにする何かを待っている可能性があります。(明らかに、ユーザー入力を待っている間はサンプルを除外したいです。)(2)の場合、プログラムが他のプロセスとの競合のために単に待機している場合、それはおそらく実行中にかなりランダムな方法で発生します。したがって、プログラムには時間がかかる場合がありますが、重要な統計には大きな影響はありませんが、ステートメントがスタックにある時間の割合です。その「自己時間」が重要
自己時間は、ラインレベルではなく関数レベルで測定している場合にのみ意味があり、関数時間が純粋にローカル計算に入るのか、呼び出されるルーチンに入るのかを見極めるのに役立つと思います。ラインレベルで要約する場合、ラインはスタックの最後にある場合は自己時間を表し、そうでない場合は包括的時間を表します。いずれにせよ、コストはスタックサンプルのパーセンテージであるため、どちらの場合でもそれを見つけることができます。サンプルは高頻度で取得する必要が
あるということこれは、パフォーマンスの問題は即効性がある可能性があり、それに当たるにはサンプルを頻繁に取得する必要があるという考えに基づいています。ただし、問題のコストが20%である場合、たとえば、合計実行時間10秒(またはその他)のうち、問題が発生したかどうかに関係なく、その合計時間の各サンプルは20%の確率で問題に到達します。このような単一のピース
.....XXXXXXXX...........................
.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^
(20サンプル、4ヒット)
またはこのような多くの小さなピース
X...X...X.X..X.........X.....X....X.....
.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^
(20サンプル、3ヒット)
いずれにせよ、ヒット数は、サンプルの数に関係なく、平均して5分の1になります。なんて少ない。(平均= 20 *0.2=4。標準偏差=+/-sqrt(20 * 0.2 * 0.8)= 1.8。)ボトルネック
を1つしかないかのように見つけようとしていること。次の実行タイムラインを検討してください。vxvWvzvWvxvWvYvWvxvWv.vWvxvWvYvW
これは、で表される実際に役立つ作業で構成されてい.
ます。vWxYz
それぞれ1/2、1 / 4、1 / 8、1 / 16、1/32の時間でパフォーマンスの問題が発生します。サンプリングはv
簡単に見つかります。これは削除され、そのままに
xWzWxWYWxW.WxWYW
なります。プログラムの実行にW
かかる時間は半分になり、時間も半分になり、簡単に見つけることができます。削除されたままに
xzxYx.xY
なりますこのプロセスは、削除するものが見つからなくなるまで、パフォーマンスの最大の問題をパーセンテージで削除するたびに続行されます。.
現在実行されるのは、元のプログラムで使用された時間の1/32で実行される唯一のものです。これが拡大効果です、これにより、分母が減少するため、問題を削除すると、余りがパーセントで大きくなります。
もう1つの重要なポイントは、すべての問題を見つける必要があるということです。5つを見逃すことはありません。問題が見つからず、修正されないと、最終的なスピードアップ率が大幅に低下します。すべてではありませんが、いくつかを見つけるだけでは「十分」ではありません。
追加: gprofが人気のある理由の1つを指摘したいと思います。おそらく、無料で教えやすく、長い間使用されてきたためです。グーグルですばやく検索すると、それを教えている(またはそう思われる)いくつかの学術機関が見つかります。
berkeley bu clemson colorado duke earlham fsu indiana mit msu ncsa.illinois ncsu nyu ou princeton psu stanford ucsd umd umich utah utexas utk wustl
**作業を要求する他の方法を除いて、メッセージの投稿など、理由を示す痕跡を残さないでください。
Valgrindには、 KCacheGrindと呼ばれる非常に優れたビジュアライザーを備えた命令カウントプロファイラーがあります。Mike Dunlaveyが推奨するように、Valgrindは、プロシージャがスタック上で実行されている命令の割合をカウントしますが、相互再帰の存在下で混乱しているように見えると申し訳ありません。しかし、ビジュアライザーは非常に素晴らしく、光年先を進んでgprof
います。
perf
Linuxでカーネルとユーザーアプリケーションをプロファイリングするための比較的新しいツールについてはここでは何も見当たらなかったので、この情報を追加することにしました。
まず第一に-これはLinuxプロファイリングについてのチュートリアルですperf
perf
Linuxカーネルが2.6.32より大きい場合、またはそれより古い場合に使用できますoprofile
。どちらのプログラムも、プログラムをインストルメント化する必要はありません(gprof
requiresのように)。ただし、コールグラフを正しく取得するには、perf
を使用してプログラムを作成する必要があります-fno-omit-frame-pointer
。例:g++ -fno-omit-frame-pointer -O2 main.cpp
。
アプリケーションの「ライブ」分析は、次のコマンドで確認できperf top
ます。
sudo perf top -p `pidof a.out` -K
または、実行中のアプリケーションのパフォーマンスデータを記録し、その後分析することもできます。
1)パフォーマンスデータを記録するには:
perf record -p `pidof a.out`
または10秒間録音するには:
perf record -p `pidof a.out` sleep 10
またはコールグラフで記録する()
perf record -g -p `pidof a.out`
2)記録されたデータを分析する
perf report --stdio
perf report --stdio --sort=dso -g none
perf report --stdio -g none
perf report --stdio -g
または、この方法でアプリケーションを起動して終了するのを待つだけで、アプリケーションのパフォーマンスデータを記録し、その後分析することができます。
perf record ./a.out
これは、テストプログラムのプロファイリングの例です。
テストプログラムはファイルmain.cppにあります(メッセージの下部にmain.cppを配置します)。
私はそれをこのようにコンパイルします:
g++ -m64 -fno-omit-frame-pointer -g main.cpp -L. -ltcmalloc_minimal -o my_test
libc mallocはこのオプションなしでコンパイルされているようlibmalloc_minimial.so
ですが、でコンパイルされているので使用します。-fno-omit-frame-pointer
次に、テストプログラムを実行します
./my_test 100000000
次に、実行中のプロセスのパフォーマンスデータを記録します。
perf record -g -p `pidof my_test` -o ./my_test.perf.data sleep 30
次に、モジュールごとの負荷を分析します。
perf report --stdio -g none --sort comm、dso -i ./my_test.perf.data
# Overhead Command Shared Object
# ........ ....... ............................
#
70.06% my_test my_test
28.33% my_test libtcmalloc_minimal.so.0.1.0
1.61% my_test [kernel.kallsyms]
次に、関数ごとの負荷が分析されます。
perfレポート--stdio-gnone -i ./my_test.perf.data | c ++ filt
# Overhead Command Shared Object Symbol
# ........ ....... ............................ ...........................
#
29.30% my_test my_test [.] f2(long)
29.14% my_test my_test [.] f1(long)
15.17% my_test libtcmalloc_minimal.so.0.1.0 [.] operator new(unsigned long)
13.16% my_test libtcmalloc_minimal.so.0.1.0 [.] operator delete(void*)
9.44% my_test my_test [.] process_request(long)
1.01% my_test my_test [.] operator delete(void*)@plt
0.97% my_test my_test [.] operator new(unsigned long)@plt
0.20% my_test my_test [.] main
0.19% my_test [kernel.kallsyms] [k] apic_timer_interrupt
0.16% my_test [kernel.kallsyms] [k] _spin_lock
0.13% my_test [kernel.kallsyms] [k] native_write_msr_safe
and so on ...
次に、コールチェーンが分析されます。
perfレポート--stdio-gグラフ-i./my_test.perf.data| c ++ filt
# Overhead Command Shared Object Symbol
# ........ ....... ............................ ...........................
#
29.30% my_test my_test [.] f2(long)
|
--- f2(long)
|
--29.01%-- process_request(long)
main
__libc_start_main
29.14% my_test my_test [.] f1(long)
|
--- f1(long)
|
|--15.05%-- process_request(long)
| main
| __libc_start_main
|
--13.79%-- f2(long)
process_request(long)
main
__libc_start_main
15.17% my_test libtcmalloc_minimal.so.0.1.0 [.] operator new(unsigned long)
|
--- operator new(unsigned long)
|
|--11.44%-- f1(long)
| |
| |--5.75%-- process_request(long)
| | main
| | __libc_start_main
| |
| --5.69%-- f2(long)
| process_request(long)
| main
| __libc_start_main
|
--3.01%-- process_request(long)
main
__libc_start_main
13.16% my_test libtcmalloc_minimal.so.0.1.0 [.] operator delete(void*)
|
--- operator delete(void*)
|
|--9.13%-- f1(long)
| |
| |--4.63%-- f2(long)
| | process_request(long)
| | main
| | __libc_start_main
| |
| --4.51%-- process_request(long)
| main
| __libc_start_main
|
|--3.05%-- process_request(long)
| main
| __libc_start_main
|
--0.80%-- f2(long)
process_request(long)
main
__libc_start_main
9.44% my_test my_test [.] process_request(long)
|
--- process_request(long)
|
--9.39%-- main
__libc_start_main
1.01% my_test my_test [.] operator delete(void*)@plt
|
--- operator delete(void*)@plt
0.97% my_test my_test [.] operator new(unsigned long)@plt
|
--- operator new(unsigned long)@plt
0.20% my_test my_test [.] main
0.19% my_test [kernel.kallsyms] [k] apic_timer_interrupt
0.16% my_test [kernel.kallsyms] [k] _spin_lock
and so on ...
したがって、この時点で、プログラムがどこで時間を費やしているかがわかります。
そして、これはテストのmain.cppです。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
time_t f1(time_t time_value)
{
for (int j =0; j < 10; ++j) {
++time_value;
if (j%5 == 0) {
double *p = new double;
delete p;
}
}
return time_value;
}
time_t f2(time_t time_value)
{
for (int j =0; j < 40; ++j) {
++time_value;
}
time_value=f1(time_value);
return time_value;
}
time_t process_request(time_t time_value)
{
for (int j =0; j < 10; ++j) {
int *p = new int;
delete p;
for (int m =0; m < 10; ++m) {
++time_value;
}
}
for (int i =0; i < 10; ++i) {
time_value=f1(time_value);
time_value=f2(time_value);
}
return time_value;
}
int main(int argc, char* argv2[])
{
int number_loops = argc > 1 ? atoi(argv2[1]) : 1;
time_t time_value = time(0);
printf("number loops %d\n", number_loops);
printf("time_value: %d\n", time_value );
for (int i =0; i < number_loops; ++i) {
time_value = process_request(time_value);
}
printf("time_value: %ld\n", time_value );
return 0;
}
OProfileを試してください。これは、コードをプロファイリングするためのはるかに優れたツールです。IntelVTuneもお勧めします。
上記の2つのツールを使用すると、特定のコード行に費やす時間を絞り込み、コードに注釈を付け、アセンブリを表示し、特定の命令にかかる時間を表示できます。時間メトリックのほかに、特定のカウンター、つまりキャッシュヒットなどを照会することもできます。
gprofとは異なり、2つのいずれかを使用して、システムで実行されている任意のプロセス/バイナリをプロファイリングできます。
Googleのパフォーマンスツールには、使いやすいプロファイラーが含まれています。CPUとヒーププロファイラーが利用可能です。
Sysprofを見てください。
あなたのディストリビューションはすでにそれを持っているかもしれません。
高性能トレーサーが必要な場合はhttp://lttng.org/