gprofの代替案[終了]


166

他のどのプログラムがgprofと同じことをしますか?


2
どのプラットフォームに興味がありますか?
osgx 2010年

2
Linuxに興味があります。
ニューロマンサー2010


13
@Gregory-私は同意する傾向があります、そしておそらく彼は彼自身の答え229対6で貢献するべきです。それらの答えの6つすべては彼自身の質問に対するものです...
Jean-Bernard Pellerin

5
この質問が建設的にならないのはなぜですか?
JohnTortugo 2013

回答:


73

Valgrindには、KCacheGrindと呼ばれる非常に優れたビジュアライザーを備えた命令カウントプロファイラーがあります。Mike Dunlaveyが推奨しているように、Valgrindは、プロシージャがスタック上でライブになっている命令の割合をカウントしますが、相互再帰が存在すると混乱するように見えます。しかし、ビジュアライザーは非常に素晴らしく、何年も先を行っていgprofます。


2
@Norman:++再帰についてのその混乱は、グラフ内のノード間で時間を伝播するという概念を持つシステムに固有のもののようです。また、壁時計時間は一般にCPU命令時間よりも便利で、コード行(呼び出し命令)はプロシージャよりも便利だと思います。ランダムな実時間でスタックサンプルが取得される場合、ライン(または手順、またはその他の記述)の端数コストは、それを示すサンプルの端数によって単純に推定されます。
Mike Dunlavey、2009

1
...通話の指示を強調していますが、それはすべての指示に適用されます。数値の大きな配列のバブルソートなど、正直なところにホットスポットのボトルネックがある場合、内部ループの比較/ジャンプ/スワップ/インクリメントの指示は、ほぼすべてのスタックサンプルの上部/下部になります。 。しかし(特にソフトウェアが大きくなり、ルーチンに「自己」の時間はほとんどないため)、多くの問題は実際には呼び出し命令であり、作業の要求であり、それがどれだけのコストがかかるかが明らかであれば、実際に実行する必要はありませ
Mike Dunlavey、2009

3
...これをチェックしてください。私は彼らがほぼ正しい方向に進んで
Mike Dunlavey 2009

195

gprof (論文を読む)は歴史的な理由で存在しています。パフォーマンスの問題を見つけるのに役立つと思われる場合は、そのように宣伝されていません。これは紙が言うことです:

プロファイルは、さまざまな実装のコストを比較および評価するために使用できます。

それは、評価されるさまざまな実装を識別するために使用できるとは言いませんが、特別な状況下では可能であることを意味します:

特に、プログラムの小さな部分が実行時間を支配していることが判明した場合。

ローカライズされていない問題についてはどうですか?それらは関係ありませんか?要求されたことのないgprofに期待しないでください。これは単なる測定ツールであり、CPUにバインドされた操作のみです。

代わりにこれを試してください。
これは、44倍のスピードアップの例です。
これは730倍の高速化です。
こちらが8分間のビデオデモです。
統計の説明は次のとおりです。
これが批評に対する答えです。

プログラムについての簡単な観察があります。特定の実行では、すべての命令が全体の時間の一部(特にcall命令)に関与します。つまり、存在しない場合、時間は費やされないという意味です。その間、命令はスタック**にあります。それが理解されると、あなたはそれを見ることができます-

gprofは、次のようなパフォーマンスに関する特定の神話を体現しています。

  1. このプログラムカウンターのサンプリングは便利です。
    これは、スカラー値の大きな配列のバブルソートなど、不要なホットスポットのボトルネックがある場合にのみ役立ちます。たとえば、string-compareを使用して並べ替えに変更しても、それはまだボトルネックですが、ホットスポットがstring-compareにあるため、プログラムカウンターのサンプリングはそれを認識しません。一方、拡張プログラムカウンター(呼び出しスタック)をサンプリングすると、文字列比較が呼び出されるポイントであるソートループが明確に表示されます。実際、gprofは、PCのみのサンプリングの制限を解消する試みでした。

  2. このタイミング関数は、時間のかかるコード行をキャプチャするよりも重要です。
    その神話の理由は、gprofがスタックサンプルをキャプチャできなかったためです。代わりに、関数の時間を計り、それらの呼び出しをカウントし、コールグラフをキャプチャしようとします。ただし、コストのかかる関数が特定された後も、その関数の内部を調べて、時間の原因となっている行を見つける必要があります。調べる必要のないスタックサンプルがある場合、それらの行はサンプル上にあります。(一般的な関数には100〜1000の命令がある場合があります。関数呼び出しは1命令なので、コストのかかる呼び出しを見つけるものは2〜3桁正確です。)

  3. コールグラフが重要です。
    プログラムについて知っておくべきことは、プログラムが時間を費やす場所ではなくその理由です。。関数で時間を費やしているとき、スタック上のすべてのコード行は、それが存在する理由の推論の連鎖に1つのリンクを提供します。スタックの一部しか表示できない場合は、理由の一部しか表示できないため、その時間が実際に必要かどうかはわかりません。コールグラフは何を教えてくれますか?各アークは、一部の関数Aが一部の時間の間、一部の関数Bを呼び出すプロセス中であったことを示しています。AにBを呼び出すコード行が1行しかない場合でも、その行は理由のほんの一部を提供します。運が良ければ、その行には理由がありません。通常、複数の同時線を見て、そこにあるかどうかの理由を見つける必要があります。Aが複数の場所でBに電話をかけると、さらに少ないことがわかります。

  4. その再帰はトリッキーで紛らわしい問題です。
    これは、gprofや他のプロファイラーがコールグラフを生成し、ノードに時間を割り当てる必要性を認識しているためです。スタックのサンプルがある場合、サンプルに表示されるコードの各行の時間コストは非常に単純な数です-それが存在するサンプルの割合。再帰がある場合、指定された行が1つのサンプルに複数回現れることがあります。 どんなに。サンプルがNミリ秒ごとに取得され、ラインがそれらのF%に表示されると仮定します(単独かどうか)。その行が(例えば、それを削除するか、その周りに分岐などによる)は時間を取らないようにすることができる場合、これらのサンプルはなり消滅、及び時間は、F%低減されるであろう。

  5. 時間測定の精度(したがって、多数のサンプル)が重要です。
    少し考えてみてください。コードの行が5つのうち3つのサンプルにある場合、電球のようにそれを発射できれば、使用される時間は約60%短くなります。これで、異なる5つのサンプルを取得した場合、それを2回または4つしか表示しなかった可能性があることがわかります。したがって、60%の測定は、40%から80%の一般的な範囲に似ています。40%しかなかった場合、問題を修正する価値はないと思いますか?それで、あなたが本当に欲しいのが問題見つけることであるとき、正確な時点は何ですか?500または5000個のサンプルで問題をより正確に測定できたはずですが、それ以上正確には検出できなかったでしょう。

  6. ステートメントまたは関数の呼び出しのカウントが役立つこと。
    関数が1000回呼び出されたことがわかっているとします。それから、時間の何分の1かわかりますか?また、平均して実行にかかる時間をカウントで乗算し、合計時間で除算する必要もあります。平均呼び出し時間はナノ秒から秒まで変動する可能性があるため、カウントだけではあまりわかりません。スタックサンプルがある場合、ルーチンまたは任意のステートメントのコストは、それが存在するサンプルの割合にすぎません。その時間の割合は、ルーチンまたはステートメントに時間がかからないようにできる場合、原則として全体的に節約できるものであり、それがパフォーマンスに最も直接的な関係を持っているものです。

  7. ブロックされたときにサンプルを取得する必要がない
    この神話の理由は2つあります。1)プログラムが待機しているときはPCサンプリングは無意味である、2)タイミングの正確さへの専念。ただし、(1)の場合、プログラムは、ファイルI / Oなど、ユーザーが知っおく必要があること、およびどのスタックサンプルが明らかにするなど、プログラムが要求するものを非常に待っている可能性があります。(明らかに、ユーザー入力を待機している間、サンプルを除外します。)(2)の場合、プログラムが他のプロセスとの競合のために単に待機している場合、おそらく実行中にかなりランダムに発生します。そのため、プログラムに時間がかかる可能性がありますが、重要な統計、ステートメントがスタック上にある時間の割合には大きな影響はありません。

  8. 「セルフタイム」が重要であること
    セルフタイムは、行レベルではなく関数レベルで測定している場合にのみ意味があり、関数時間が純粋にローカルな計算と呼び出されたルーチンのどちらに入るのかを見分けるのに助けが必要だと思います。ラインレベルで要約する場合、ラインがスタックの最後にある場合はラインは自己時間を表し、それ以外の場合は包括的時間を表します。どちらの場合も、コストがかかるのは、それが存在するスタックサンプルのパーセンテージです。そのため、どちらの場合でも、それが検索されます。

  9. これは、サンプルを高頻度で取得
    する必要があるということです。これは、パフォーマンスの問題は即効性のあるものであり、ヒットするためには頻繁にサンプルを取得する必要があるという考えに基づいています。ただし、問題のコストが10秒(またはその他)の合計実行時間の20%である場合、問題が発生したかどうかに関係なく、その合計時間の各サンプルは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。)

  10. あなたは見つけようとしているというボトルネックを
    一つだけがあったかのように。次の実行タイムラインを考えてみます。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つを指摘したいと思います。それは、おそらく無料で、教えるのが簡単で、長い間行われているため、教えられているからです。Googleをすばやく検索すると、それを教える(またはそのように見える)いくつかの学術機関が見つかります。

バークレーbuクレムソンコロラドデュークアールハムfsuインディアナmit msu ncsa.illinois ncsu nyu ouプリンストンpsuスタンフォードucd umd umich utah utexas utk wustl

**作業の実行を要求する他の方法を除いて、メッセージの投稿など、理由を示す痕跡を残しません。


3
@ノーマン:私はこれに基づいて、DOS用のCで93年頃にプロファイラーを作成しました。私はそれをもう1つのパフォーマンスアナライザーと呼び、IEEEミーティングでデモを行いましたが、それは可能な限りのことです。RotateRightからZoomという製品があり、それほど遠くない製品があります。* nixでは、pstackは手動で実行するのに適しています。私の仕事(Windowsでの薬理学)のto-doリストは1マイルほどの長さで、家族は言うまでもなく、楽しいプロジェクトができません。これは便利かもしれません:stackoverflow.com/questions/1777669/...
マイクDunlavey

6
私は常に、プロファイラーが遅いコードを修正するのにあまり役に立たないことに気付き、代わりにデバッグコードの選択的なビットを使用して、選択したステートメントのグループが取る時間を測定しました。犯人を見つけるのに時間がかかりすぎたことはありませんが、「誰もが」(私が知る限り)ファンシーツールを使用しているときは、「クマの皮と石のナイフ」のアプローチにいつも戸惑っていました。プロファイラーから必要な情報を取得できない理由を教えてくれてありがとう。これは私がSOで見た最も重要なアイデアの1つです。よくやった!
ウェインコンラッド

7
@osgx:何も引き裂くつもりはありません。シンプルで頑丈な昔ながらのお気に入りの自動車のようですが、それができないこともあり、それだけではなく、神話から目を覚ます必要もあります。一部のプラットフォームではスタックのサンプルを取得するのが難しい場合がありますが、gprofがそれを見つけられないような問題がある場合、それが唯一のツールであるという事実は少し快適です。
Mike Dunlavey、2010年

2
@Andrew:... そして、その理由がサンプルのかなりの部分(1以上など)に当てはまる場合、そのアクティビティを排除できるコード行がそれらのサンプルにあります。グラフはこれについてのヒントを与えることができますが、それほど多くないスタックサンプルは単にそれらを表示します。
Mike Dunlavey、2010

2
@Matt:IOパフォーマンスの問題の例は次のようにして見つかりました。1)ログメッセージをファイルまたはコンソールに出力しますが、これは誤って重要ではないと考えられていました。2)数値IOでのテキストとdouble間の変換。3)起動時に国際化された文字列を抽出する地下のIO。判明した文字列は国際化する必要はありませんでした。私はこれらのような例をたくさんヒットしました。
Mike Dunlavey、2010

63

perfLinuxでカーネルお​​よびユーザーアプリケーションをプロファイリングするための比較的新しいツールは何もここに表示されなかったので、この情報を追加することにしました。

まず第一に、これはLinuxプロファイリングに関するチュートリアルですperf

perfLinuxカーネルが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

次に、モジュールごとの負荷を分析します。

パフォーマンスレポート--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]

次に、関数ごとの負荷が分析されます。

パフォーマンスレポート--stdio -g none -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 ...

次に、呼び出しチェーンが分析されます。

パフォーマンスレポート--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;
}

私はあなたの例を実行し、5つのスタックショットを撮りました。彼らが見つけたものは次のとおりです。時間の(ほぼ)40%f1delete時間の(大体)40%process_requestがを呼び出していましたdelete。残りの大部分はで費やされましたnew。測定値は粗いですが、ホットスポットは特定されています。
Mike Dunlavey、2013年

とはstackshot?それはそれでpstack出力?

2
As in my answer, you run it under a debugger and hit ^C at a random time and capture the stack trace。1)顧客のサーバーで実行されているプログラムのパフォーマンスの問題を分析する必要がある場合、この手法は役に立たないと思います。2)さまざまな要求を処理する多数のスレッドを持つプログラムの情報を取得するために、この手法をどのように適用するかわかりません。つまり、全体像がかなり複雑な場合です。

2
#1については。顧客が電話をかけて、プログラムの動作が遅いと言うことがあります。すぐには言えませんthe problem is outside your codeね。あなたの主張を裏付けるためにいくつかの情報が必要になるかもしれないので。この状況では、ある時点でアプリケーションをプロファイルする必要があるかもしれません。顧客にgdbを起動して^ Cを押し、コールスタックを取得するよう依頼することはできません。これが私のポイントでした。これはspielwiese.fontein.de/2012/01/22/…の例です。私にはこの問題があり、プロファイリングは大いに役立ちました。

2
#2については。簡素化は良いアプローチだと私は同意する。時々それは働きます。パフォーマンスの問題が顧客のサーバーでのみ発生し、サーバーで再現できない場合は、プロファイルが役立ちます。

21

OProfileを試してください。コードをプロファイリングするためのはるかに優れたツールです。Intel VTuneもお勧めします。

上記の2つのツールは、特定のコード行で費やされた時間を絞り込み、コードに注釈を付け、アセンブリと特定の命令がどれだけかかるかを示します。時間メトリックのほかに、特定のカウンタ、つまりキャッシュヒットなどをクエリすることもできます。

gprofとは異なり、システムで実行中の任意のプロセス/バイナリを、2つのいずれかを使用してプロファイルできます。


2
valgrindの回答でも述べたように、RotateRightからのズーム(rotateright.com)は、はるかに優れたインターフェイスを提供し、リモートプロファイリングを可能にします。
JanePhanie

oprofileが好きではなかった、それは行き当たりばったりのようだった
Matt Joiner

@マット特定のポイント?
Anycorn

statのオーバーフローを生成する前に、数十回を超える実行に対処できず、出力は特に有用ではなく、ドキュメントは恐ろしいものです。
マットジョイナー

1
@Tho OProfile:ARM、POWER、ia64、...
Anycorn



弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.