第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 眼里「顺不顺」的度量。

避坑指南:我曾经在调试一个网络协议栈时,发现 IPC 始终很低。排查了半天,原来是开启了内核抢占导致频繁上下文切换。用 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

报告会按采样命中次数排序,最耗时的函数排在最前面。你想想看,这就像给程序拍了一张「热力图」,哪里红就优化哪里。

注意事项:采样是统计性的,不是精确的。如果采样次数不够,结果可能不准确。建议采样时间至少 5 秒以上,或者用 -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 倍。

我的经验:遇到性能问题,先看 cache-misses。很多嵌入式程序慢,不是 CPU 不够快,而是内存访问太慢。优化数据布局往往比优化算法更有效。

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 工具知识体系 核心硬件事件 cycles instructions cache-misses branch-misses context-switches 核心命令 perf list perf stat perf record perf report perf annotate 优化目标 提高 IPC 降低 cache-misses 减少分支预测失败 降低上下文切换 减少 cycles 优化流程:perf stat 全局扫描 → perf record 定位热点 → perf report 分析 → 针对性优化 关键指标速算: • CPI = cycles / instructions(越小越好,理想值 < 1.0) • IPC = instructions / cycles(越大越好,现代 CPU 可达 2-4) • 缓存未命中率 = cache-misses / cache-references × 100% • 分支预测准确率 = 1 - (branch-misses / branches)

这张图把 Perf 的核心要素都串起来了。左边是事件,中间是命令,右边是优化目标。你从 perf stat 开始,发现瓶颈后切换到 perf record 精确定位,最后用 perf report 分析结果。整个过程就像剥洋葱,一层层找到问题根源。

核心要点:Perf 不是万能的,但它能帮你把「感觉慢」变成「数据证明慢」。我见过太多人凭直觉优化代码,结果越优化越慢。用数据说话,才是工程师该有的习惯。