5、Perf高级技巧:perf top实时监控,perf annotate源码级分析,perf script自定义脚本

说实话,很多工程师用 perf 还停留在「perf stat 看看命中率」的阶段。但真正遇到棘手的性能问题——比如某个函数突然飙高、或者一段内联代码拖垮了整个系统——这时候就需要更锋利的工具了。

我个人习惯把 perf 分成三个层次:看趋势、挖细节、写脚本。perf top 解决「现在谁在吃 CPU」,perf annotate 解决「到底是哪一行代码在作妖」,perf script 解决「我要定制化分析逻辑」。今天咱们就把这三个工具挨个讲透。

核心思路:实时监控找热点 → 源码级定位瓶颈行 → 脚本化批量分析。这三板斧下去,90% 的性能问题都能现原形。

5.1 perf top:实时监控的「任务管理器」Pro 版

perf top 和 top 命令长得有点像,但本质完全不同。top 看的是进程级别的 CPU 占用,perf top 看的是函数级别的采样热点。你想想看,top 告诉你进程 A 吃了 80% CPU,然后呢?你还要用 gdb 去 attach、去猜。perf top 直接告诉你:是进程 A 里的 foo() 函数吃了 60%,bar() 吃了 20%。

我在项目中遇到过这样一个场景:线上服务 CPU 突然飙到 90%,用 top 看到是 nginx worker 进程在忙。但 nginx 那么大,到底是哪个模块的问题?我直接跑:

sudo perf top -p $(pgrep nginx) -g

几秒钟后,热点函数就浮出来了——居然是某个第三方模块里的字符串处理函数。后来发现是日志级别配置错误,导致每条请求都在做无意义的格式化。perf top 帮我省了至少半天排查时间。

几个常用参数,我列个表方便你查阅:

参数 作用 我的使用习惯
-p PID 指定进程 排查单进程问题时必加
-g 开启调用链 默认关闭,但建议一直开着
-e event 指定事件 缺省是 cycles,查内存用 cache-misses
-K 不显示内核函数 用户态分析时加,减少干扰
小技巧:perf top 默认按当前采样占比排序,按「k」键可以切换是否显示内核符号。按「1」可以展开每个 CPU 核心的单独视图——多核负载不均的问题一眼就能看出来。

5.2 perf annotate:源码级「慢动作回放」

perf top 告诉你是哪个函数有问题,perf annotate 则告诉你这个函数里哪一行汇编代码最慢。说白了,就是带着采样数据反汇编,然后标注每条指令的采样命中率。

我曾经接手过一个图像处理库,性能始终上不去。用 perf record 采集数据后,perf annotate 打开一看——好家伙,一个 memcpy 操作占了 40% 的采样。但仔细看汇编,发现编译器把一个大循环里的内存拷贝优化成了 rep movsb,而那块内存是非对齐的,导致 CPU 走了微码辅助路径,慢了好几倍。

操作步骤很简单:

# 第一步:采集数据
perf record -g ./my_program

# 第二步:生成源码级分析
perf annotate -s hot_function

输出界面里,左边是采样百分比,右边是对应的汇编代码。你会看到类似这样的布局:

Percent |  Disassembly of hot_function
----------------------------------------
        |  push   %rbp
  0.02  |  mov    %rsp,%rbp
 12.50  |  mov    (%rdi),%eax     ← 这里最热
  0.05  |  add    $0x1,%eax
  8.30  |  mov    %eax,(%rdi)     ← 这里也热
  0.01  |  pop    %rbp
        |  retq

看到百分比高的行,就是 CPU 花时间最多的地方。我一般会重点关注超过 5% 的指令,如果某条指令超过 20%,那基本就是瓶颈所在。

注意:perf annotate 需要调试符号。编译时记得加 -g 选项,并且不要 strip 掉符号表。如果看不到源码行号,检查一下是不是用了 -fomit-frame-pointer,这个选项会破坏调用链。

5.3 perf script:写脚本,让 perf 按你的逻辑跑

perf top 和 perf annotate 都是 perf 内置的展示方式。但有时候,你需要自定义分析逻辑——比如只统计某个时间窗口内的数据,或者把采样结果导出成 JSON 喂给其他工具。这时候 perf script 就派上用场了。

perf script 的本质是:把 perf.data 里的原始事件记录,按你指定的格式输出。你可以用 Python 或 Perl 写脚本,对每一条采样记录做处理。

我举个实际例子。有一次我需要排查一个「间歇性卡顿」问题,现象是每过几分钟服务就会卡几百毫秒。用 perf record 采集了 10 分钟的数据,然后写了个 perf script 脚本,只提取调度事件和周期超过 100ms 的停顿:

# 用 Python 写 perf script
#!/usr/bin/env python
import sys

def process_event(event):
    # event 是一个字典,包含采样信息
    if event['evtype'] == 'sched:sched_switch':
        prev_comm = event['common']['prev_comm']
        next_comm = event['common']['next_comm']
        if 'my_service' in prev_comm or 'my_service' in next_comm:
            print(f"Switch: {prev_comm} -> {next_comm}")

# 注册处理函数
perf_script_context = {
    'process_event': process_event
}

跑起来:

perf script -s my_script.py

结果很快就锁定了问题:某个后台线程每隔 3 分钟会持锁做一次全量缓存清理,导致主线程被阻塞。perf script 让我能精准过滤出我关心的调度事件,而不是在几万条记录里人工翻找。

perf script 的常用输出格式:

  • -F:指定输出字段,比如 -F pid,comm,event 只显示进程ID、进程名和事件类型
  • --time:只分析某个时间区间,比如 --time 10.0-20.0 只取第10秒到第20秒的数据
  • -g:输出调用链信息,配合脚本可以做火焰图的数据预处理
我的经验:perf script 配合 awk 也能做很多事。比如快速统计某个函数被调用了多少次:perf script -F comm,event,ip | awk '/my_func/ {count++} END {print count}'。不需要每次都写 Python 脚本。

5.4 三件套的配合使用场景

这三个工具不是孤立的。我通常的工作流是这样的:

  1. 先用 perf top 扫一遍,看看当前系统里哪些函数最热。这一步花 30 秒就够了。
  2. 锁定热点函数后,用 perf record 采集数据,采样时间根据问题严重程度来——CPU 持续高就采 10 秒,间歇性问题就采几分钟。
  3. perf annotate 打开热点函数,逐行分析汇编代码,找到真正的瓶颈指令。
  4. 如果问题复杂,写 perf script 脚本,做定制化分析,比如只看某个时间窗口、或者过滤掉不关心的进程。

下面这张图概括了这三个工具的关系和典型使用路径:

Perf 三件套工作流 perf top 实时监控 · 函数级热点 perf annotate 源码级 · 逐行分析 perf script 自定义脚本 · 灵活分析 锁定热点 深入分析 输出:瓶颈定位报告 热点函数 + 瓶颈行号 + 调用链 + 自定义分析结果 使用顺序:perf top 快速定位 → perf annotate 源码级分析 → perf script 定制化处理 也可跳过中间步骤,根据问题类型直接选择对应工具

5.5 避坑指南

这几个工具虽然强大,但用不好反而会误导你。我踩过几个坑,分享给你:

  • 采样频率别太高:perf top 默认 4000Hz,如果系统负载已经很高了,建议降到 1000Hz(-F 1000),否则 perf 本身会抢 CPU。
  • 注意内核 vs 用户态:默认 perf 只采样用户态。如果怀疑内核有问题,加 -a 或者指定 -e cycles:uk(用户态+内核态)。
  • perf annotate 看不到源码?检查两点:一是编译时有没有加 -g,二是 perf 有没有找到对应的 debuginfo 包。我一般用 perf buildid-list 确认。
  • perf script 输出太大:如果采样时间很长,perf script 的输出可能几个 GB。建议先用 perf report --sort comm,dso 缩小范围,再用 perf script 导出。
曾经我犯过一个错误:在虚拟机里用 perf top 分析网络性能,结果发现热点函数全是虚拟化相关的。折腾了半天才发现,宿主机和虚拟机之间的 I/O 路径才是瓶颈,perf 在虚拟机里看到的只是表象。所以,perf 最好在裸机或容器内直接跑,虚拟化环境的结果要谨慎解读。

好了,perf 的高级技巧就讲到这里。这三个工具配合起来,基本能覆盖从「系统卡了」到「找到具体哪一行代码有问题」的全过程。下次遇到性能问题,别急着拍脑袋优化,先让 perf 告诉你答案。


公众号:蓝海资料掘金营,微信 deep3321