第3章 Perf工具基础:事件类型与基本用法
做嵌入式性能优化,手里没个趁手的工具可不行。我个人最常用的就是 Perf,这玩意儿在 Linux 内核里直接集成,几乎零开销就能拿到硬件级别的性能数据。今天咱们就把 Perf 的核心事件和两个最常用的命令——perf stat 和 perf record/report——彻底讲透。
3.1 Perf 事件类型:你究竟在测量什么?
Perf 能监控的事件分两大类:硬件事件和软件事件。硬件事件直接来自 CPU 内部的性能监控单元(PMU),精度极高;软件事件则由内核跟踪点提供,适合分析系统行为。
我刚开始用 Perf 时,面对几十种事件一头雾水。后来发现,日常优化盯住三个核心事件就够了:cycles、instructions、cache-misses。它们能回答三个关键问题:程序跑了多久?干了多少活?内存访问效率如何?
| 事件名称 | 含义 | 典型用途 |
|---|---|---|
| cycles | CPU 时钟周期数 | 衡量程序执行时间,越低越好 |
| instructions | 执行的指令条数 | 反映代码量,结合 cycles 算 CPI |
| cache-misses | 缓存未命中次数 | 定位内存访问瓶颈,优化数据布局 |
| branch-misses | 分支预测失败次数 | 发现分支混乱的代码段 |
| context-switches | 上下文切换次数 | 判断是否频繁切换进程/线程 |
perf list 可以查看当前系统支持的所有事件。不同 CPU 型号支持的事件略有差异,ARM 和 x86 的 PMU 事件名也不完全一样。
3.2 perf stat:快速获取程序性能概览
perf stat 就像程序的体检报告——跑一次就能看到关键指标。它的用法极其简单:
# 统计 ls 命令的性能数据
perf stat ls
# 统计正在运行的进程(PID 1234)
perf stat -p 1234
# 统计整个程序运行期间,每 100ms 采样一次
perf stat -I 100 ./my_program
输出示例:
Performance counter stats for 'ls':
132,456 cycles # 0.345 GHz
89,234 instructions # 0.67 insn per cycle
2,345 cache-misses # 12.3% of all cache refs
123 branch-misses # 3.4% of all branches
0.000384567 seconds time elapsed
这里有个关键指标——IPC(Instructions Per Cycle)。IPC 越高,说明 CPU 利用率越好。我在项目中见过 IPC 只有 0.2 的代码,优化后提到 1.8,性能直接翻了好几倍。说白了,IPC 就是你的代码在 CPU 眼里「顺不顺」的度量。
perf stat -e context-switches 一看,每秒切换上千次。后来绑核运行,IPC 立刻恢复正常。
3.3 perf record/report:深入定位热点函数
perf stat 只能看全局数据,想知道具体哪个函数拖慢了程序,就得用 perf record 配合 perf report。它俩的工作流程是:先采样记录,再分析报告。
3.3.1 采样与记录
# 以 99Hz 的频率采样,记录到 perf.data
perf record -F 99 ./my_program
# 只采样指定事件(比如只看 cache-misses)
perf record -e cache-misses -F 99 ./my_program
# 采样正在运行的进程
perf record -p 1234 -F 99 --sleep 10
-F 99 表示每秒采样 99 次。为什么是 99 而不是 100?这是为了避免与系统定时器产生共振,导致采样偏差。嗯,这里要注意,采样频率太高会影响程序本身的行为,嵌入式设备上建议用 49 或 99。
3.3.2 生成报告
# 查看采样结果
perf report
# 输出到文件,方便离线分析
perf report --stdio > report.txt
报告会按采样命中次数排序,最耗时的函数排在最前面。你想想看,这就像给程序拍了一张「热力图」,哪里红就优化哪里。
-F 999 提高频率(但会增加开销)。
3.4 实战案例:用 Perf 定位缓存问题
咱们来看一个真实场景。假设有一段处理图像数据的代码,运行很慢。用 perf stat 先扫一眼:
perf stat ./image_process
# 输出:
# 1,234,567,890 cycles
# 987,654,321 instructions
# 456,789,012 cache-misses # 缓存未命中率高达 37%!
37% 的缓存未命中率,明显不正常。接下来用 perf record 定位具体函数:
perf record -e cache-misses -F 99 ./image_process
perf report
报告显示,process_pixel() 函数占了 72% 的缓存未命中。我一看代码,发现它按列遍历图像数据,而图像在内存中是按行存储的。说白了,就是访问模式与存储布局不匹配,导致缓存行频繁失效。
优化方案很简单:把遍历顺序改成按行访问。改完后缓存未命中率降到 8%,程序快了 3 倍。
3.5 Perf 事件与命令速查表
| 命令 | 用途 | 常用参数 |
|---|---|---|
perf list |
列出所有可用事件 | hw 只看硬件事件,sw 只看软件事件 |
perf stat |
统计程序整体性能 | -e 指定事件,-I 间隔输出,-p 指定进程 |
perf record |
采样记录性能数据 | -F 采样频率,-e 指定事件,-g 记录调用栈 |
perf report |
分析采样结果 | --stdio 文本输出,-i 指定输入文件 |
perf annotate |
查看热点函数的汇编指令 | --stdio 文本输出 |
3.6 知识体系:Perf 工具全景图
下面这张 SVG 图把 Perf 的核心事件、命令和优化流程串起来了。建议你保存下来,以后做性能分析时对照着看。
这张图把 Perf 的核心要素都串起来了。左边是事件,中间是命令,右边是优化目标。你从 perf stat 开始,发现瓶颈后切换到 perf record 精确定位,最后用 perf report 分析结果。整个过程就像剥洋葱,一层层找到问题根源。