虽然我可以直观地得到大部分结果,但我很难完全理解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
可能只是一个包装器,而我的函数的开销只考虑了循环,总的来说:foo
的3*N
次迭代,2*N
另外两个。
分层分析
perf record -g ./perf-test
perf report -G
perf report
现在我得到的开销列有两个: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
下的前三个条目? -
main
调用foo
,这没关系,但是为什么如果它调用__sin_sse2
和sin@plt
(间接?)它不也调用bar
和baz
? - 为什么
__libc_start_main
和main
出现在foo
下?为什么foo
出现两次?
- 我应该如何解释
怀疑是这个层次结构有两个层次,其中第二个实际上表示"x 调用 y"/"y 由 x 调用"语义,但我猜累了,所以我在这里问。而且文档似乎没有帮助。
很抱歉这篇文章很长,但我希望所有这些上下文也可以帮助或作为其他人的参考。
好吧,好吧,让我们暂时忽略调用方和被调用方调用图之间的区别,主要是因为当我在我的机器上比较这两个选项之间的结果时,我只看到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%是从酒吧调用的。
-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
的角度来看,我看不到被调用方和调用方图之间的任何区别。
更新
我从您的命令行更改的一件事是调用图的收集方式。默认情况下,Linux perf 使用帧指针方法来重建调用堆栈。当编译器使用 -fomit-frame-pointer
作为默认值时,这可能是一个问题。所以我用
perf record --call-graph dwarf ./perf-test