Perf 高级功能:从实时监控到自定义脚本

Perf 这个工具,很多人用了一两年还停留在 perf stat 和 perf record 的层面。说实话,这有点浪费。它的高级功能才是真正能帮你定位问题、吃透代码性能的关键。今天我们就来聊聊三个我工作中几乎天天用的功能:perf top、perf annotate 和 perf script。

perf top:实时监控,一眼看穿热点

你想想看,系统突然卡顿了,或者某个进程 CPU 飙高。这时候你最需要什么?一个能实时告诉你「谁在吃 CPU」的工具。perf top 就是干这个的。

它的用法极其简单:

sudo perf top

跑起来之后,你会看到一个实时刷新的列表,按 CPU 占用率从高到低排列。每一行都告诉你:哪个进程、哪个符号(函数)占了多少 CPU。

我个人习惯在排查性能问题时,先开三个终端:一个跑 perf top,一个跑 htop,一个留着敲命令。perf top 告诉我「哪个函数在忙」,htop 告诉我「哪个进程在忙」,配合起来定位问题非常快。

关键参数速查表

参数 作用 我常用的场景
-p PID 只监控指定进程 某个后台服务异常时,锁定它看
-e event 指定事件类型 比如 -e cache-misses 看缓存失效
-K 只显示内核符号 排查内核模块或驱动问题
-U 只显示用户态符号 应用层优化时用,排除内核干扰

小技巧:如果发现 perf top 里很多符号显示为 [unknown],八成是缺少调试符号。装一下 linux-image-$(uname -r)-dbgsym 或者编译时加 -g 选项就能解决。

perf annotate:源码级分析,精确到每一行

perf top 告诉了你「哪个函数热」,但还不够。你还需要知道「这个函数里哪一行代码最热」。perf annotate 就是干这个的。

用法也很直接:

# 先采样
sudo perf record -a -g -- sleep 10
# 然后反汇编分析
sudo perf annotate -s 热点函数名

跑完之后,你会看到类似这样的输出:

Percent | 源代码 & 汇编
-----------------------------------------------
        |  void foo(int *arr, int n) {
        |      for (int i = 0; i < n; i++) {
 45.23  |          arr[i] = arr[i] * 2 + 1;
        |      }
        |  }

左边那个百分比,就是这条指令占用的 CPU 时间比例。45.23% 意味着将近一半的 CPU 时间都花在这一行上。嗯,这里就是优化的主战场。

我曾经在一个网络协议栈的项目里,用 perf annotate 发现一个看似简单的 memcpy 占了 30% 的 CPU。仔细一看,原来是拷贝的数据量太小,函数调用开销占比太大。后来改成内联 + 循环展开,性能直接提升了 15%。

注意:perf annotate 依赖调试信息。如果你的程序编译时没加 -g,或者 strip 过,那看到的就全是汇编,没有源码对应。我建议在开发板上保留 -g 编译的版本用于分析,发布时再 strip。

perf script:自定义脚本,想怎么分析就怎么分析

perf record 采集的数据是二进制的。perf report 和 perf annotate 帮你做了初步分析。但有时候,你需要更灵活的分析方式——比如只提取某个时间段的采样,或者按线程分组统计。这时候 perf script 就派上用场了。

它的核心能力是:把二进制采样数据转成可读的文本,然后你可以用任何脚本语言(Python、Perl、awk)去处理。

基本用法:

sudo perf script -i perf.data > output.txt

输出的文本长这样:

swapper     0 [000] 12345.678901: cycles: ffffffff81234567 native_safe_halt+0x7
myapp    1234 [001] 12345.678902: cycles: 0x400567 foo+0x23
myapp    1234 [001] 12345.678903: cycles: 0x400589 bar+0x45

每一行都包含:进程名、PID、CPU、时间戳、事件类型、地址、符号。

我个人最常用的一个场景是:按函数名统计调用次数和耗时分布。写个简单的 awk 脚本:

sudo perf script | awk '{print $NF}' | sort | uniq -c | sort -rn | head -20

这样就能快速看到哪些函数被采样到的最多。虽然 perf report 也能做类似的事,但 perf script 的灵活性在于——你可以任意组合字段、过滤条件、做数学运算。

perf script 的典型应用场景

  • 时间片分析:按时间戳切片,看不同时间段的热点变化
  • 线程级分析:按 PID/TID 分组,看每个线程的独立热点
  • 事件过滤:只提取 cache-misses 事件,分析缓存失效模式
  • 自定义统计:计算平均调用深度、函数调用链长度等

三者的关系与实战流程

这三个功能不是孤立的。在实际项目中,我通常按这个流程来用:

  1. perf top:快速扫描,找到最热的进程和函数
  2. perf record + perf annotate:对热点函数做深度采样,定位到具体代码行
  3. perf script:如果标准分析不够用,导出数据做自定义分析

下面这张图可以帮你理清它们的关系:

Perf 高级功能实战流程 perf top 实时监控,定位热点函数 perf record 对热点函数深度采样 perf annotate 源码级分析,定位到代码行 perf script 自定义脚本,灵活分析 三者配合:先粗筛 → 再精确定位 → 最后自定义分析

避坑指南

最后分享几个我踩过的坑:

  • 采样频率别太高:-F 99 通常够用,设成 1000 以上反而可能引入采样开销,干扰结果。
  • 注意内核版本差异:不同内核版本的 perf 事件名可能不同。比如 cache-misses 在某些版本上叫 cache-miss。用 perf list 先查一下。
  • perf script 输出可能很大:采样 10 秒可能产生几百 MB 的文本。建议先 grep 或 head 看看格式,再写脚本处理。
  • 虚拟化环境要小心:在虚拟机里用 perf,某些硬件事件可能不支持或数据不准。我一般在物理机上做最终的性能验证。

好了,perf 的高级功能就聊到这里。这三个工具用熟了,大部分性能问题你都能在几分钟内定位到根因。剩下的就是怎么改代码的问题了。