第四章:CPU性能分析工具:perf stat/top/record/report 实战,火焰图生成与解读

说到CPU性能分析,perf 这套工具链绝对是咱们量化交易系统调优的“手术刀”。我最早接触它是在一次低延迟行情解析的优化中,当时系统延迟总是不稳定,用 perf 一查,立马就找到了罪魁祸首——一个频繁触发的内核态锁。嗯,今天我们就来把这套工具彻底讲透。

4.1 perf stat:快速摸清系统底细

perf stat 就像给系统做一次“体检”。你不需要改代码,直接跑一下就能看到关键指标。

# 统计一个进程的CPU使用情况
perf stat -p <PID> sleep 10

# 统计整个系统的CPU事件
perf stat -a sleep 5

我个人习惯先用 perf stat -a 扫一遍,看看上下文切换(context-switches)和缓存未命中(cache-misses)是不是异常高。如果 context-switches 每秒超过几万次,那大概率是锁竞争或者线程调度出了问题。

关键指标解读:
  • cycles:CPU周期数,越高说明越忙
  • instructions:执行的指令数,和 cycles 对比看 IPC(每周期指令数)
  • cache-misses:缓存未命中,高了说明数据局部性差
  • context-switches:上下文切换,高了说明线程调度频繁

举个例子,我在优化一个订单簿重建模块时,发现 cache-misses 占比高达 30%。后来把数据结构从链表改成数组,cache-misses 直接降到了 5% 以下。说白了,CPU 很多时候都在等数据,而不是算数据。

4.2 perf top:实时定位热点函数

perf top 是实时版的“热点追踪器”。它像 top 命令一样动态刷新,但显示的是函数级别的 CPU 占用。

# 实时查看CPU热点函数
perf top

# 只看用户态函数
perf top -U

# 只看内核态函数
perf top -K

我曾经遇到一个诡异的问题:系统 CPU 跑满,但业务逻辑看起来没问题。用 perf top 一看,发现 __do_softirq 占了 40% 的 CPU。原来是网卡中断处理太频繁,后来调整了中断亲和性(IRQ affinity),问题就解决了。你想想看,如果没有 perf top,这种问题排查起来得有多痛苦。

小技巧:perf top -p <PID> 只监控特定进程,避免被其他进程干扰。

4.3 perf record & report:深度采样分析

perf record 是“黑匣子”,它会记录 CPU 的采样数据。perf report 则是“回放器”,帮你分析采样结果。

# 对进程进行采样,持续10秒
perf record -p <PID> -g -- sleep 10

# 生成报告
perf report -g graph

-g 参数是关键,它开启调用链采样。这样你不仅能看到哪个函数最耗 CPU,还能看到是谁调用了它。我建议采样频率用默认的 4000Hz(每秒 4000 次),太高会影响性能,太低又不够精确。

注意: 采样时间不要太短,至少 5-10 秒,否则样本量不够,分析结果可能不准确。我曾经就吃过这个亏,采样 2 秒得出的热点函数,后来发现是偶然抖动。

perf report 的输出中,Self 列表示函数自身消耗的 CPU 占比,Children 列表示函数及其子函数的总占比。我一般先看 Self 高的函数,这些是真正的“吃 CPU 大户”。

4.4 火焰图生成与解读

火焰图是 Brendan Gregg 的杰作,它把 perf 的采样数据可视化,一眼就能看出 CPU 热点在哪。我个人觉得,火焰图是性能分析中最直观的工具,没有之一。

4.4.1 生成火焰图

生成火焰图需要两个步骤:先用 perf 采集数据,再用 FlameGraph 工具生成 SVG。

# 第一步:采集数据
perf record -p <PID> -g -- sleep 30
perf script > out.perf

# 第二步:生成火焰图
git clone https://github.com/brendangregg/FlameGraph.git
cd FlameGraph
./stackcollapse-perf.pl ../out.perf > out.folded
./flamegraph.pl out.folded > flame.svg

生成的 flame.svg 可以直接用浏览器打开。嗯,这里要注意,采样时间越长,火焰图越准确。我一般至少采 30 秒,对于量化交易系统,我会在交易时段采样,这样能抓到真实的热点。

4.4.2 解读火焰图

火焰图有几个核心概念:

  • X 轴:按字母顺序排列的函数调用栈,不代表时间顺序
  • Y 轴:调用栈深度,越往上越深
  • 宽度:函数在采样中出现的比例,越宽说明越耗 CPU
  • 颜色:随机分配,没有特殊含义(别被颜色误导)

怎么看?我总结了三步法:

  1. 找“平顶山”:顶部宽大的函数就是热点,鼠标悬停能看到具体函数名和占比
  2. 看调用链:从顶部往下看,找到是谁调用了这个热点函数
  3. 识别“烟囱”:如果某个调用链特别高,说明递归或深层调用有问题
实战案例: 我在优化一个高频交易策略时,火焰图显示 std::unordered_map::find 占了 35% 的 CPU。顺着调用链往下看,发现是订单查找逻辑用了哈希表,而哈希冲突严重。后来改成 std::vector + 二分查找,CPU 占用降到了 8%。

4.5 知识体系图

下面这张 SVG 图展示了 perf 工具链的核心逻辑和火焰图的关系:

CPU性能分析工具链核心逻辑 perf stat 快速统计,摸清底细 perf top 实时热点,定位函数 perf record 深度采样,记录调用链 perf report 回放采样,分析热点 perf script 导出数据,供工具处理 FlameGraph 生成火焰图 火焰图(Flame Graph) 可视化展示CPU热点、调用链、瓶颈定位 性能优化:减少热点、优化数据结构、调整调度

4.6 实战避坑指南

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

我曾经... 在虚拟机里用 perf 分析性能,结果发现采样结果全是乱码。后来才意识到,虚拟机里的 perf 无法准确获取硬件计数器。所以,perf 一定要在物理机上跑,或者用支持硬件虚拟化的云实例。
另一个坑: 采样时如果系统负载很高,perf 本身也会消耗 CPU。我建议采样时把采样频率降到 1000Hz,或者用 -F 99 指定频率,避免 perf 成为新的热点。

还有一点,火焰图不是万能的。它只能告诉你 CPU 在哪消耗,但无法告诉你为什么消耗。比如,一个函数 CPU 占用高,可能是因为算法复杂,也可能是因为锁等待。这时候,你需要结合源码和 perf annotate 来进一步分析。

好了,perf 工具链的核心用法就这些。记住,工具是死的,思路是活的。多练几次,你也能成为性能分析的老手。

一句话总结: perf stat 摸底,perf top 定位,perf record 深挖,火焰图可视化。这套组合拳打下来,CPU 性能问题基本无处遁形。

专注资料整理