web-dev-qa-db-ja.com

Linuxパフォーマンスレポート出力を理解する

ほとんどの結果を直感的に取得できますが、特にコールグラフに関係するものについては、_perf report_コマンドの出力を完全に理解するのに苦労しているため、この問題を解決するための愚かなテストを一度作成しました。すべて。

愚かなテスト

私は以下をまとめました:

_gcc -Wall -pedantic -lm perf-test.c -o perf-test
_

インライン化などを回避するための積極的な最適化はありません。

_#include <math.h>

#define N 10000000UL

#define USELESSNESS(n)                          \
    do {                                        \
        unsigned long i;                        \
        double x = 42;                          \
        for (i = 0; i < (n); i++) x = sin(x);   \
    } while (0)

void baz()
{
    USELESSNESS(N);
}

void bar()
{
    USELESSNESS(2 * N);
    baz();
}

void foo()
{
    USELESSNESS(3 * N);
    bar();
    baz();
}

int main()
{
    foo();
    return 0;
}
_

フラットプロファイリング

_perf record ./perf-test
perf report
_

これらで私は得る:

_  94,44%  perf-test  libm-2.19.so       [.] __sin_sse2
   2,09%  perf-test  perf-test          [.] sin@plt
   1,24%  perf-test  perf-test          [.] foo
   0,85%  perf-test  perf-test          [.] baz
   0,83%  perf-test  perf-test          [.] bar
_

重い作業は実際には___sin_sse2_によって実行され、_sin@plt_はおそらく単なるラッパーであるため、これは合理的に聞こえますが、私の関数のオーバーヘッドは、全体としてループのみを考慮に入れています:_3*N_の反復foo、_2*N_他の2つ。

階層プロファイリング

_perf record -g ./perf-test
perf report -G
perf report
_

これで、取得するオーバーヘッド列は2つになります。Children(出力はデフォルトでこれによってソートされます)とSelf(フラットプロファイルの同じオーバーヘッド)です。

ここで、何かが恋しいと感じ始めます。_-G_を使用しているかどうかに関係なく、「xがyを呼び出す」または「yはxによって呼び出される」という観点から階層を説明できません。例:

  • _-G_なし( "yはxによって呼び出されます"):

    _-   94,34%    94,06%  perf-test  libm-2.19.so       [.] __sin_sse2
       - __sin_sse2
          + 43,67% foo
          + 41,45% main
          + 14,88% bar
    -   37,73%     0,00%  perf-test  perf-test          [.] main
         main
         __libc_start_main
    -   23,41%     1,35%  perf-test  perf-test          [.] foo
         foo
         main
         __libc_start_main
    -    6,43%     0,83%  perf-test  perf-test          [.] bar
         bar
         foo
         main
         __libc_start_main
    -    0,98%     0,98%  perf-test  perf-test          [.] baz
       - baz
          + 54,71% foo
          + 45,29% bar
    _
    1. なぜ___sin_sse2_はmain(間接的に?)、foo、およびbarによって呼び出されますが、bazによって呼び出されないのですか?
    2. 関数にパーセントと階層が付加されている場合(たとえば、bazの最後のインスタンス)とそうでない場合がある(たとえば、barの最後のインスタンス)のはなぜですか?
  • _-G_( "xはyを呼び出す"):

    _-   94,34%    94,06%  perf-test  libm-2.19.so       [.] __sin_sse2
       + __sin_sse2
       + __libc_start_main
       + main
    -   37,73%     0,00%  perf-test  perf-test          [.] main
       - main
          + 62,05% foo
          + 35,73% __sin_sse2
            2,23% sin@plt
    -   23,41%     1,35%  perf-test  perf-test          [.] foo
       - foo
          + 64,40% __sin_sse2
          + 29,18% bar
          + 3,98% sin@plt
            2,44% baz
         __libc_start_main
         main
         foo
    _
    1. ___sin_sse2_の下の最初の3つのエントリをどのように解釈する必要がありますか?
    2. mainfooを呼び出しますが、それは問題ありませんが、___sin_sse2_および_sin@plt_(間接的に?)を呼び出す場合、barおよびbazも呼び出さないのはなぜですか?
    3. ___libc_start_main_とmainfooの下に表示されるのはなぜですか?そして、なぜfooが2回表示されるのですか?

この階層には2つのレベルがあり、2番目のレベルは実際には「x呼び出しy」/「yはxによって呼び出される」セマンティクスを表していると思われますが、推測するのに疲れているので、ここで質問します。そして、ドキュメントは役に立たないようです。


長い投稿で申し訳ありませんが、このすべてのコンテキストが他の誰かの参照として役立つか、機能することを願っています。

16
cYrus

さて、一時的に発信者と着信者のコールグラフの違いを無視しましょう。主に、マシンでこれら2つのオプションの結果を比較すると、kernel.kallsymsDSO内の効果しか表示されないためです。理解してください-これは私自身にとって比較的新しいことです。

あなたの例では、ツリー全体を読む方が少し簡単であることがわかりました。したがって、--stdioを使用して、ツリー全体で__sin_sse2を見てみましょう。

# Overhead    Command      Shared Object                  Symbol
# ........  .........  .................  ......................
#
    94.72%  perf-test  libm-2.19.so       [.] __sin_sse2
            |
            --- __sin_sse2
               |
               |--44.20%-- foo
               |          |
               |           --100.00%-- main
               |                     __libc_start_main
               |                     _start
               |                     0x0
               |
               |--27.95%-- baz
               |          |
               |          |--51.78%-- bar
               |          |          foo
               |          |          main
               |          |          __libc_start_main
               |          |          _start
               |          |          0x0
               |          |
               |           --48.22%-- foo
               |                     main
               |                     __libc_start_main
               |                     _start
               |                     0x0
               |
                --27.84%-- bar
                          |
                           --100.00%-- foo
                                     main
                                     __libc_start_main
                                     _start
                                     0x0

したがって、私がこれを読む方法は次のとおりです。44%の場合、sinfooから呼び出されます。 27%はbazから呼び出され、27%はbarから呼び出されます。

-gのドキュメントは参考になります。

 -g [type,min[,limit],order[,key]], --call-graph
       Display call chains using type, min percent threshold, optional print limit and order. type can be either:

       ·   flat: single column, linear exposure of call chains.

       ·   graph: use a graph tree, displaying absolute overhead rates.

       ·   fractal: like graph, but displays relative rates. Each branch of the tree is considered as a new profiled object.

               order can be either:
               - callee: callee based call graph.
               - caller: inverted caller based call graph.

               key can be:
               - function: compare on functions
               - address: compare on individual code addresses

               Default: fractal,0.5,callee,function.

ここで重要なのは、デフォルトがフラクタルであり、フラクタルモードでは各ブランチが新しいオブジェクトであるということです。

したがって、bazが呼び出される時間の50%は、barから呼び出され、残りの50%はfooから呼び出されることがわかります。

これが常に最も有用な手段であるとは限らないため、-g graphを使用して結果を確認することをお勧めします。

94.72%  perf-test  libm-2.19.so       [.] __sin_sse2
        |
        --- __sin_sse2
           |
           |--41.87%-- foo
           |          |
           |           --41.48%-- main
           |                     __libc_start_main
           |                     _start
           |                     0x0
           |
           |--26.48%-- baz
           |          |
           |          |--13.50%-- bar
           |          |          foo
           |          |          main
           |          |          __libc_start_main
           |          |          _start
           |          |          0x0
           |          |
           |           --12.57%-- foo
           |                     main
           |                     __libc_start_main
           |                     _start
           |                     0x0
           |
            --26.38%-- bar
                      |
                       --26.17%-- foo
                                 main
                                 __libc_start_main
                                 _start
                                 0x0

これは絶対パーセンテージの使用に変わり、そのコールチェーンの時間の各パーセンテージが報告されます。つまり、foo->barは合計ティックの26%であり(これはbazを呼び出します)、foo->baz(直接)は総ティックの12%です。

__sin_sse2の観点からは、呼び出し先グラフと呼び出し元グラフの間に違いが見られない理由はまだわかりません。

更新

コマンドラインから変更したことの1つは、コールグラフの収集方法です。 Linux perfは、デフォルトで、コールスタックを再構築するためにフレームポインタメソッドを使用します。これは、コンパイラが-fomit-frame-pointerデフォルト として使用する場合に問題になる可能性があります。だから私は使用しました

perf record --call-graph dwarf ./perf-test
7
Matthew G.