4. perf 高级:perf top、perf annotate、perf script、perf timechart 的使用场景与实战
好,咱们继续深入。前面聊的都是 perf 的基础用法,说白了就是「看个大概」。但真正到了线上排查,或者做极致优化的时候,那些基础命令就不够用了。你需要更锋利的刀。
这一章,我带你看看 perf 工具链里四个非常实用的高级模块:perf top、perf annotate、perf script 和 perf timechart。它们各自解决不同的问题,但组合起来,几乎能覆盖你 90% 的性能分析场景。
4.1 perf top:实时热点,一眼看穿
先说说 perf top。这玩意儿,你可以把它想象成 top 命令的「性能版」。它不监控 CPU 和内存占用,而是直接告诉你:当前系统里,哪个函数最耗 CPU。
我个人习惯在压测的时候,开一个终端跑 perf top。看着那些百分比跳来跳去,心里就有数了。比如你发现某个函数占了 80% 的 CPU,那优化它就对了。
# 最简单的用法,直接看全局热点
perf top
# 只看某个进程的热点
perf top -p <PID>
# 只看内核态的热点
perf top -k
# 指定采样频率,默认 4000 Hz,可以调高
perf top -F 10000
perf top -p <PID> 最安全。别直接跑全局的,万一采样频率太高,把机器搞挂了,那就尴尬了。我曾经在 8 核机器上跑默认的 perf top,结果 CPU 飙升到 30%,吓得我赶紧停了。
你可能会问:「这跟 top 看 CPU 占用有什么区别?」区别大了。top 告诉你哪个进程吃 CPU,但 perf top 告诉你这个进程里哪个函数在吃 CPU。一个是「谁在吃」,一个是「怎么吃」。
4.2 perf annotate:反汇编,揪出罪魁祸首
好,现在你用 perf top 找到了热点函数。然后呢?你得知道这个函数里哪一行代码最慢。这时候 perf annotate 就登场了。
它的原理很简单:perf 在采样时,不仅记录了函数名,还记录了指令地址。通过反汇编,它能告诉你每条指令被命中了多少次。说白了,就是「哪行汇编代码最烫手」。
# 先采样
perf record -a -g -- sleep 10
# 然后反汇编查看热点函数
perf annotate -s <函数名>
举个例子,假设你发现 memcpy 是热点。跑 perf annotate -s memcpy,你会看到类似这样的输出:
Percent | Source code & Disassembly of libc-2.31.so
-----------------------------------------------
:
: Disassembly of section .text:
:
: 0000000000015b30 <__memcpy_avx_unaligned>:
0.00 : 15b30: mov %rdi,%rax
0.00 : 15b33: cmp $0x20,%rdx
0.00 : 15b37: ja 15b60 <__memcpy_avx_unaligned+0x30>
12.50 : 15b39: vmovdqu (%rsi),%xmm0
25.00 : 15b3d: vmovdqu -0x10(%rsi,%rdx,1),%xmm1
12.50 : 15b43: vmovdqu %xmm0,(%rdi)
25.00 : 15b47: vmovdqu %xmm1,-0x10(%rdi,%rdx,1)
0.00 : 15b4d: retq
看到左边那列百分比了吗?那就是每条指令的采样命中率。哪行百分比高,哪行就是瓶颈。比如上面,vmovdqu 指令占了 25%,说明内存拷贝操作是热点。
perf annotate 显示的是汇编代码,不是 C 源码。如果你编译时加了 -g 选项,它也能显示源码行号,但汇编级别的分析更精确。我建议你两个都看,互相印证。
4.3 perf script:导出数据,自己玩
perf script 是个「翻译官」。它把 perf record 采集的二进制数据,转换成人类可读的文本格式。你可以用它来导出采样事件,然后自己写脚本分析。
为什么要这么做?因为 perf report 和 perf annotate 虽然方便,但不够灵活。比如你想统计某个线程的调用链,或者按时间戳过滤事件,这些工具就做不到了。这时候,perf script 就是你的瑞士军刀。
# 导出所有采样事件
perf script
# 只导出某个 CPU 的事件
perf script --cpu 0
# 导出指定时间范围的事件
perf script --time 1000,2000
# 导出调用链信息
perf script -g
输出格式大概是这样的:
swapper 0 [000] 12345.678901: cycles:
ffffffff81234567 native_write_msr+0x17 ([kernel.kallsyms])
ffffffff81012345 do_idle+0x1a5 ([kernel.kallsyms])
ffffffff81012345 cpu_startup_entry+0x1b ([kernel.kallsyms])
每一行都包含:进程名、PID、CPU 编号、时间戳、事件类型、调用栈。你可以用 awk、Python 或者任何你喜欢的工具来处理这些数据。
perf record 采集了 30 秒数据,然后用 perf script 导出,写了个 Python 脚本统计每个时间窗口内的调度延迟。最后发现是某个驱动在特定条件下触发了长时间的自旋锁。如果没有 perf script,这种问题几乎没法定位。
4.4 perf timechart:可视化,一目了然
最后说说 perf timechart。这个工具能把采样数据生成一张 SVG 图片,展示 CPU 和进程在时间轴上的状态。说白了,就是「性能的甘特图」。
它特别适合分析这些问题:
- CPU 利用率为什么忽高忽低?
- 进程为什么被频繁调度?
- 某个时间段内,系统在忙什么?
# 采集数据,需要加上 --delay 和 --duration 参数
perf timechart record -- sleep 10
# 生成 SVG 图片
perf timechart
# 指定输出文件名
perf timechart -o mychart.svg
生成的 SVG 图片里,横轴是时间,纵轴是 CPU 或进程。不同颜色代表不同状态:
- 绿色:进程正在运行
- 蓝色:进程在等待(比如 I/O)
- 红色:进程被中断
- 灰色:空闲
perf timechart 来验证优化效果。比如优化前,某个进程的等待时间占比很高;优化后,等待时间明显减少。一张图胜过千言万语,给老板汇报的时候特别好用。
不过要注意,perf timechart 生成的 SVG 文件可能很大。如果采样时间太长,图片会变得密密麻麻,反而看不清。我建议采样时间控制在 10-30 秒,够用就好。
实战:组合拳打一个
光说不练假把式。咱们来个完整的实战流程,看看这四个工具怎么配合使用。
场景: 一个 Web 服务器响应变慢,怀疑是某个函数太耗 CPU。
- 第一步:用
perf top看全局热点
发现perf top -p $(pgrep nginx)ngx_http_process_request占了 60% 的 CPU。 - 第二步:用
perf record采样
采集 10 秒的数据。perf record -p $(pgrep nginx) -g -- sleep 10 - 第三步:用
perf annotate深入分析
发现perf annotate -s ngx_http_process_requestngx_http_parse_request_line函数里的一个字符串比较操作占了 40% 的采样。 - 第四步:用
perf script导出调用链
确认这个函数被哪些请求路径调用。perf script -g | grep ngx_http_parse_request_line - 第五步:用
perf timechart看整体调度
打开 SVG 图片,确认 CPU 利用率是否均匀,有没有明显的空闲或等待。perf timechart record -- sleep 10 perf timechart -o nginx_chart.svg
你看,这一套组合拳下来,问题基本就定位清楚了。剩下的就是改代码、重新编译、再验证。
-g 参数,结果 perf annotate 只能看到函数名,看不到调用链。后来排查了半天才发现是采样数据不完整。所以,采样时一定要加 -g,除非你明确不需要调用链信息。
好了,这一章的内容就到这里。perf 的高级用法其实还有很多,但这四个工具已经能帮你解决大部分性能问题了。记住,工具是死的,思路是活的。多练、多用,你也能成为性能优化高手。