ほとんどの結果を直感的に取得できますが、特にコールグラフに関係するものについては、_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
_
__sin_sse2
_はmain
(間接的に?)、foo
、およびbar
によって呼び出されますが、baz
によって呼び出されないのですか?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
_
__sin_sse2
_の下の最初の3つのエントリをどのように解釈する必要がありますか?main
はfoo
を呼び出しますが、それは問題ありませんが、___sin_sse2
_および_sin@plt
_(間接的に?)を呼び出す場合、bar
およびbaz
も呼び出さないのはなぜですか?__libc_start_main
_とmain
がfoo
の下に表示されるのはなぜですか?そして、なぜfoo
が2回表示されるのですか?この階層には2つのレベルがあり、2番目のレベルは実際には「x呼び出しy」/「yはxによって呼び出される」セマンティクスを表していると思われますが、推測するのに疲れているので、ここで質問します。そして、ドキュメントは役に立たないようです。
長い投稿で申し訳ありませんが、このすべてのコンテキストが他の誰かの参照として役立つか、機能することを願っています。
さて、一時的に発信者と着信者のコールグラフの違いを無視しましょう。主に、マシンでこれら2つのオプションの結果を比較すると、kernel.kallsyms
DSO内の効果しか表示されないためです。理解してください-これは私自身にとって比較的新しいことです。
あなたの例では、ツリー全体を読む方が少し簡単であることがわかりました。したがって、--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%の場合、sin
はfoo
から呼び出されます。 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