4. perf 高级:perf top、perf annotate、perf script、perf timechart 的使用场景与实战

好,咱们继续深入。前面聊的都是 perf 的基础用法,说白了就是「看个大概」。但真正到了线上排查,或者做极致优化的时候,那些基础命令就不够用了。你需要更锋利的刀。

这一章,我带你看看 perf 工具链里四个非常实用的高级模块:perf top、perf annotate、perf script 和 perf timechart。它们各自解决不同的问题,但组合起来,几乎能覆盖你 90% 的性能分析场景。

核心观点: 这四个工具,分别对应「实时热点监控」、「代码级热点定位」、「事件流追溯」和「时间轴可视化」。掌握了它们,你才算真正入了 perf 的门。
perf 高级四件套 perf top 实时热点监控 类似 top,但看函数 perf annotate 代码级热点定位 反汇编看哪行代码慢 perf script 事件流追溯 导出原始采样数据 perf timechart 时间轴可视化 看进程/CPU 调度

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。

  1. 第一步:用 perf top 看全局热点
    perf top -p $(pgrep nginx)
    发现 ngx_http_process_request 占了 60% 的 CPU。
  2. 第二步:用 perf record 采样
    perf record -p $(pgrep nginx) -g -- sleep 10
    采集 10 秒的数据。
  3. 第三步:用 perf annotate 深入分析
    perf annotate -s ngx_http_process_request
    发现 ngx_http_parse_request_line 函数里的一个字符串比较操作占了 40% 的采样。
  4. 第四步:用 perf script 导出调用链
    perf script -g | grep ngx_http_parse_request_line
    确认这个函数被哪些请求路径调用。
  5. 第五步:用 perf timechart 看整体调度
    perf timechart record -- sleep 10
    perf timechart -o nginx_chart.svg
    打开 SVG 图片,确认 CPU 利用率是否均匀,有没有明显的空闲或等待。

你看,这一套组合拳下来,问题基本就定位清楚了。剩下的就是改代码、重新编译、再验证。

避坑指南: 我曾经在采样时忘了加 -g 参数,结果 perf annotate 只能看到函数名,看不到调用链。后来排查了半天才发现是采样数据不完整。所以,采样时一定要加 -g,除非你明确不需要调用链信息。

好了,这一章的内容就到这里。perf 的高级用法其实还有很多,但这四个工具已经能帮你解决大部分性能问题了。记住,工具是死的,思路是活的。多练、多用,你也能成为性能优化高手。