第四章: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 次),太高会影响性能,太低又不够精确。
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
- 颜色:随机分配,没有特殊含义(别被颜色误导)
怎么看?我总结了三步法:
- 找“平顶山”:顶部宽大的函数就是热点,鼠标悬停能看到具体函数名和占比
- 看调用链:从顶部往下看,找到是谁调用了这个热点函数
- 识别“烟囱”:如果某个调用链特别高,说明递归或深层调用有问题
std::unordered_map::find 占了 35% 的 CPU。顺着调用链往下看,发现是订单查找逻辑用了哈希表,而哈希冲突严重。后来改成 std::vector + 二分查找,CPU 占用降到了 8%。
4.5 知识体系图
下面这张 SVG 图展示了 perf 工具链的核心逻辑和火焰图的关系:
4.6 实战避坑指南
最后,分享几个我踩过的坑:
-F 99 指定频率,避免 perf 成为新的热点。
还有一点,火焰图不是万能的。它只能告诉你 CPU 在哪消耗,但无法告诉你为什么消耗。比如,一个函数 CPU 占用高,可能是因为算法复杂,也可能是因为锁等待。这时候,你需要结合源码和 perf annotate 来进一步分析。
好了,perf 工具链的核心用法就这些。记住,工具是死的,思路是活的。多练几次,你也能成为性能分析的老手。