当服务器 CPU 莫名其妙被打满、接口延迟忽高忽低,你第一时间可能想到 top、htop 看进程,但真正要回答「到底是哪一行代码、哪一个函数吃掉了 CPU」,就得靠更底层的工具。Linux perf 是内核自带的性能分析利器,基于硬件性能计数器(PMC)与软件事件采样,能直接告诉你热点函数、缓存命中率、分支预测失败率,甚至把调用栈画成火焰图。本文从环境准备讲到火焰图落地,带你用 perf 把 CPU 瓶颈一锤定音。
一、perf 是什么:内核级采样器
perf 全称 Performance Counters for Linux,它直接对接 CPU 的 perf_event 子系统。和纯用户态的 Profiler 不同,perf 能同时采集:
- 硬件事件:CPU 周期(cycles)、指令数(instructions)、缓存未命中(cache-misses)、分支预测失败(branch-misses)。
- 软件事件:上下文切换、页错误(page-faults)、CPU 迁移。
- 追踪点(tracepoint):内核调度、系统调用、块设备等内核埋点。
它最大的优势是「零侵入」——不需要改代码、不需要重新编译,一条命令就能对正在运行的进程采样。对于数据库、网关这类不便重启的服务尤其友好(关于数据库侧的慢查询分析思路,可参考 PostgreSQL 慢查询实战)。
二、环境准备与权限
大多数发行版自带 perf,若没有可通过 linux-tools-common 安装。采样需要读取性能计数器,受内核参数 perf_event_paranoid 限制:
# 查看当前限制(数值越小权限越宽松,默认常为 2 或 3)
cat /proc/sys/kernel/perf_event_paranoid
# 临时放开,允许普通用户采样(生产环境按需,分析完改回)
sudo sysctl kernel.perf_event_paranoid=1
# 或仅本次会话
echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid
# 内核符号需要 root 才能读取,建议直接用 sudo 运行 perf
sudo perf --version
在容器里跑 perf 更麻烦:需要 --privileged 或挂载 /sys/kernel/debug、/proc,并赋予 CAP_SYS_ADMIN。这正好和容器运行时的权限模型相关,详见 Docker 实战 中关于特权容器与 capabilities 的部分。
三、perf top:实时看热点函数
perf top 类似 top,但展示的是「当前最耗 CPU 的函数/符号」,适合快速定位热点:
# 按 CPU 占用实时排序展示内核+用户态符号
sudo perf top
# 只看某个进程
sudo perf top -p 1234
# 按指定事件(如缓存未命中)排序
sudo perf top -e cache-misses
输出里 Symbol 列带 [k] 表示内核函数、[.] 表示用户态。如果看到某个业务函数常驻榜首,方向就找到了。
四、perf stat:用硬件计数器算 CPI
想知道「指令执行为什么慢」,perf stat 能给出宏观指标。其中 CPI(Cycles Per Instruction,每指令周期数)是关键:数值越高说明单条指令等得越久(多因缓存未命中或访存延迟)。
# 运行一次命令并统计默认事件
sudo perf stat ls
# 统计指定事件,重复 10 次取均值
sudo perf stat -e cycles,instructions,cache-misses,branch-misses -r 10 ./app
# 输出示例
# 1,234,567,890 cycles
# 980,123,456 instructions # 0.79 insn per cycle (CPI≈1.27)
# 12,345,678 cache-misses
CPI 偏高(如 >1.5)通常意味着大量时间花在等内存/缓存,而不是计算本身——这类问题靠「加 CPU」是没用的,要从数据局部性、数据结构下手。
五、perf record + report:离线采样
perf top 是实时快照,perf record 则把采样落盘,后续用 perf report 离线分析,还能保留调用栈:
# -g 采集调用栈;-F 99 表示每秒采样 99 次;-p 指定进程;sleep 30 采样 30 秒
sudo perf record -g -F 99 -p 1234 -o perf.data sleep 30
# 生成带符号的报告(TUI 交互)
sudo perf report -i perf.data
# 纯文本摘要,便于 CI/脚本里记录
sudo perf report -i perf.data --stdio | head -n 40
采样频率别设太高(如 99 比 999 更稳),否则采样开销本身会干扰业务;生产环境建议在低峰期、短时采样。
六、火焰图:把调用栈画出来
文本报告不够直观,火焰图(Flame Graph)把调用栈按「谁调用谁、各自占比多少」横向铺开:横向宽度 = 采样占比,纵向 = 调用深度。最宽的栈顶就是热点。
# 1. 用 perf script 把采样转成可解析的文本
sudo perf script -i perf.data > out.perf
# 2. 折叠调用栈(需要 FlameGraph 工具集,git clone 官方仓库)
git clone https://github.com/brendangregg/FlameGraph
./FlameGraph/stackcollapse-perf.pl out.perf > out.folded
# 3. 生成 SVG 火焰图
./FlameGraph/flamegraph.pl out.folded > cpu-flame.svg
# 一条命令流水线版
sudo perf script -i perf.data | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl > cpu-flame.svg
打开 cpu-flame.svg 后,点击任意方块可下钻、悬停看精确占比。看到「平顶山」(顶层很宽且很平)往往意味着采样频率不足或符号缺失;看到某个具体业务函数占满顶部,那就是优化切入点。
七、Off-CPU 分析:等待也算瓶颈
CPU 火焰图只统计「在 CPU 上跑」的时间。但很多延迟来自「等」——等锁、等 IO、等网络。这类时间 perf 默认不采,需要用 off-CPU 分析(抓取调度事件,统计线程被阻塞的时长):
# 抓取调度器唤醒/切换事件,记录 off-CPU 时间
sudo perf record -e sched:sched_switch -a -g -F 99 -o offcpu.data sleep 30
sudo perf script -i offcpu.data | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl --colors=io --title="Off-CPU" > offcpu-flame.svg
把 On-CPU 和 Off-CPU 两张图结合看,才能完整回答「时间到底花哪了」。一个典型的误导是:CPU 利用率不高,但延迟很高——八成是 Off-CPU 在等锁或等下游。
八、常见瓶颈对照表
| 现象 | 优先 perf 命令 | 可能根因 |
|---|---|---|
| CPU 100% 但业务没跑多少 | perf top / perf record -g | 某函数死循环、正则回溯、序列化开销 |
| CPU 不高但延迟高 | off-CPU(sched_switch) | 锁竞争、下游 IO、GC 停顿 |
| 加了核也没变快 | perf stat -e cache-misses | 缓存未命中、伪共享、内存带宽瓶颈 |
| 吞吐量上不去 | perf stat -e branch-misses | 分支预测失败、热路径分支抖动 |
九、与 top/iostat 的配合
perf 不是万能钥匙,它该用在「已经定位到资源瓶颈、需要进一步下钻到函数」的阶段。推荐的分析顺序:
- 先用
top/htop看是不是 CPU 瓶颈; - 再用
iostat/vmstat区分是计算还是 IO; - 确认是 CPU 后,上
perf下钻到具体函数。
如果你还在「CPU/内存/IO 到底哪个是瓶颈」的体检阶段,建议先读 Linux 性能调优实战:CPU、内存与 I/O 瓶颈定位,把 perf 当作那篇文章的进阶下钻工具。
十、调试符号缺失:让火焰图显示函数名
新手最常遇到的坑是火焰图里满屏 unknown 或只有十六进制地址,看不到函数名。原因通常是二进制被 strip 掉了符号表,或采样的是未安装调试信息的系统库。排查顺序很固定:
# 检查二进制是否带符号
file ./app
nm ./app | head
# Debian/Ubuntu 安装带符号的库(以 libc 为例)
sudo apt-get install libc6-dbg
# 部分发行版可用 perf 自带脚本下载内核调试符号
sudo perf buildid-cache --add /usr/lib/debug/vmlinux
# 用 --no-children 只看函数自身开销,避免被调用方占比掩盖
sudo perf top --no-children
对于已经上线的容器镜像,建议在构建阶段保留带符号的版本专门用于排查,或用 perf probe 动态打点——它能在不重新编译的前提下,往指定函数入口插入一个临时追踪点,非常适合确认「到底有没有走到这个函数」。符号问题解决了,火焰图才真正有意义。
十一、落地清单
- 临时放开
perf_event_paranoid,用sudo运行 perf。 - 低峰期短时采样(
-F 99 -g,30 秒足够初判)。 perf top快速看热点,perf stat看 CPI/缓存指标。- 生成 On-CPU 火焰图定位计算热点,必要时补 Off-CPU 图。
- 容器环境确认特权与
perf_event可用性,避免采不到符号。
perf 的门槛不在命令多,而在「先问对问题」:到底是算得慢、等得久、还是取不到数据。把这套采样→火焰图→下钻的流程跑顺,绝大多数 CPU 类性能问题都能在半小时内定位到函数级根因。




