The*_*mad 2 linux trace performancecounter call-graph perf
L3-misses我使用以下命令在简单的基准测试中提取导致用户级别的回溯evince:
sudo perf record -d --call-graph dwarf -c 10000 -e mem_load_uops_retired.l3_miss:uppp /opt/evince-3.28.4/bin/evince
Run Code Online (Sandbox Code Playgroud)
很明显,采样周期相当大(连续采样之间有 10000 个事件)。对于这个实验, 的输出perf script有一些与此类似的样本:
EvJobScheduler 27529 26441.375932: 10000 mem_load_uops_retired.l3_miss:uppp: 7fffcd5d8ec0 5080022 N/A|SNP N/A|TLB N/A|LCK N/A
7ffff17bec7f bits_image_fetch_separable_convolution_affine+0x2df (inlined)
7ffff17bec7f bits_image_fetch_separable_convolution_affine_pad_x8r8g8b8+0x2df (/usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
7ffff17d1fd1 general_composite_rect+0x301 (/usr/lib/x86_64-linux-gnu/libpixman-1.so.0.34.0)
ffffffffffffffff [unknown] ([unknown])
Run Code Online (Sandbox Code Playgroud)
在回溯的底部,有一个名为 的符号[unknown],看起来没问题。但随后就呼叫了线路general_composite_rect()。这个回溯OK吗?
AFAIK,回溯中的第一个调用者应该是类似_start()或 的东西__GI___clone()。但回溯不是这种形式。怎么了?
有什么办法可以解决这个问题吗?截断的(部分)回溯可靠吗?
TL;DR 如果堆栈中没有保存帧指针或没有矮方法的 CFI 表,性能回溯过程可能会在某些函数处停止。-fno-omit-frame-pointer使用或使用或获取 debuginfo重新编译库-g。对于发布的二进制文件和库,perf 通常会提前停止回溯,而没有机会到达main()或_start或clone()/start_thread()顶级函数。
perfLinux中的分析工具是统计采样分析器(没有二进制仪器):它对软件定时器或事件源或硬件性能监控单元(PMU)进行编程以生成周期性中断。在您的示例中\n-c 10000 -e mem_load_uops_retired.l3_miss:uppp用于在某种PEBS模式下选择x86_64中的硬件PMU(https://easyperf.net/blog/2018/06/08/Advanced-profiling-topics-PEBS-and-LBR)来生成mem_load_uops_retired 10000 后中断(使用 l3_miss 掩码)。生成的中断由 Linux 内核( perf_events 子系统、 kernel/events 和arch/x86/events )处理。在此处理程序中,PMU 重置(重新编程)以在 10000 个以上事件和生成样本后生成下一个中断。样本数据转储通过命令保存到 perf.data 文件中perf report,但工具的每次唤醒可以保存数千个样本;perf script样本可以通过或读取perf script -D。
perf_events 中断处理程序,位于__perf_event_overflowkernel/events/core.c 附近,可以完全访问当前函数的寄存器,并且有一些时间进行额外的数据检索以记录当前时间、pid 等。此类过程的一部分是 https : //en.wikipedia.org/wiki/Call_stack数据收集。但是对于 x86_64 和 -fomit-frame-pointer (通常在 Debian/Ubuntu/其他系统的许多系统库中启用),寄存器或函数堆栈中没有默认位置来存储帧指针:
\n\n\n\n\n
-fomit-frame-pointer\n 不要将帧指针保留在不需要的函数的寄存器中。这避免了保存、设置和恢复帧指针的指令;它还在许多函数中提供了一个额外的寄存器。它还使得在某些机器上无法进行调试。从 GCC 版本 4.6 开始,32 位 Linux x86 和 32 位 Darwin x86\n 目标的默认设置(未优化大小时)已更改为 -fomit-frame-pointer。通过使用 --enable-frame-pointer 配置选项配置 GCC,可以将默认值恢复为 -fno-omit-frame-pointer。
\n
通过将帧指针保存在函数堆栈中,回溯/展开变得很容易。但对于某些函数,现代 gcc(和其他编译器)可能不会生成帧指针。因此,像 perf_events 处理程序中的回溯代码要么会在此类函数处停止回溯,要么需要另一种帧指针恢复方法。选项-g method( --call-graph)perf record选择要使用的方法。它记录在man perf-record http://man7.org/linux/man-pages/man1/perf-record.1.html中:
\n\n\n\n\n
--call-graph设置并启用调用图(堆栈链/回溯)记录,意味着 -g。默认为“fp”。允许指定“fp”(帧指针)或“dwarf”(DWARF 的 CFI -\n 调用帧信息)或“lbr”(硬件最后分支记录\n 设施)作为收集用于显示\n 调用图。
\n\n在某些系统中,使用 gcc --fomit-frame-pointer 构建二进制文件
\n\n
,使用“fp”方法将生成虚假的调用图,如果可用,则使用“dwarf”(链接到 libunwind 或 libdw 的 perf 工具)库)应该改用。使用“lbr”\n 方法不需要任何编译器选项。它将从硬件 LBR 寄存器生成调用图。主要限制是它仅适用于新的英特尔平台,例如 Haswell。它只能获取用户调用链。它不能同时与分支堆栈采样一起使用。当使用“dwarf”记录时,perf 还会在采样时记录(用户)堆栈转储\n。堆栈转储的默认大小为 8192(字节)。用户可以通过在逗号后传递大小来更改大小,例如
\n
“--call-graph dwarf,4096”。
因此,dwarf 方法重用 CFI 表来查找堆栈帧大小并查找调用者的堆栈帧。我不确定 CFI 表是否默认从发布库中删除;但 debuginfo 可能会有它们。LBR 没有帮助,因为它的硬件缓冲区相当短。Dwarf 拆分处理(内核处理程序保存部分堆栈,perf 用户空间工具将使用 libdw+libunwind 解析它)可能会丢失调用堆栈的某些部分,因此还可以尝试使用--call-graph dwarf,10240等来增加 dwarf 堆栈转储。--call-graph dwarf,81920
回溯是在 perf_events 的 arch 相关部分实现的:arch/x86/events/core.c:perf_callchain_user();从kernel/events/callchain.c 调用:get_perf_callchain() <- perf_callchain <- perf_prepare_sample <- \n __perf_event_output <- *(event->overflow_handler)<- READ_ONCE(event->overflow_handler)(event, data, regs);of __perf_event_overflow。
Gregg 确实警告了不完整的 perf 调用堆栈:http://www.brendangregg.com/blog/2014-06-22/perf-cpu-sample.html
\n\n\n\n\n不完整的堆栈通常意味着 -fomit-frame-pointer 使用了 \xe2\x80\x93 编译器优化,在现实世界中几乎没有什么积极的影响,但会破坏堆栈分析器。始终使用 -fno-omit-frame-pointer 进行编译。最近的 perf 有一个 -g dwarf 选项,可以使用替代的 libunwind/dwarf 方法来检索堆栈。
\n
我还写了有关 perf 中的回溯的文章以及一些附加链接:How does linux's perf utility Understanding stack Traces?
\n| 归档时间: |
|
| 查看次数: |
2921 次 |
| 最近记录: |