应用 CPU 跑满,不知道卡在哪个函数?perf 是 Linux 性能分析的"瑞士军刀":
低开销采样,看到 CPU 时间花在每个函数 / 每行指令上。
配合 FlameGraph 出火焰图,一眼看清谁吃 CPU。
1. 装 perf
sudo apt install -y linux-tools-common linux-tools-$(uname -r)
sudo perf --version
CentOS / RHEL:sudo yum install perf。
2. 系统级采样
# 采样全系统 10 秒
sudo perf record -F 99 -a -g -- sleep 10
# -F 99: 每秒 99 次采样(避免和定时任务整数倍重合)
# -a: 全系统
# -g: 抓 call graph
ls perf.data # 几 MB
sudo perf report
perf report TUI 里:
+/-展开 / 折叠调用栈a显示汇编/搜函数名
按 CPU 百分比从高到低排:
17.32% nginx libc-2.31.so [.] __memcpy_avx_unaligned
12.45% nginx nginx [.] ngx_http_parse_header_line
8.21% nginx libssl.so.3 [.] ssl3_read_bytes
...
3. 单进程采样
sudo perf record -F 99 -p <PID> -g -- sleep 30
sudo perf report
sleep 30 是采样时长。-p <PID> 限定单进程。
4. 火焰图(一图胜千言)
# 装 FlameGraph 脚本
git clone https://github.com/brendangregg/FlameGraph ~/FlameGraph
# 采样
sudo perf record -F 99 -p <PID> -g -- sleep 30
sudo perf script > /tmp/out.perf
# 生成 SVG
~/FlameGraph/stackcollapse-perf.pl /tmp/out.perf > /tmp/out.folded
~/FlameGraph/flamegraph.pl /tmp/out.folded > /tmp/flame.svg
# 浏览器打开 /tmp/flame.svg
xdg-open /tmp/flame.svg
火焰图怎么读:
- X 轴:CPU 占用(宽度 = 占用比例,与时间无关)
- Y 轴:调用栈深度(栈顶在上)
- 宽块:耗 CPU 的函数(从顶往下看,找最宽的就是热点)
5. 看具体函数的汇编 / 源码
sudo perf report --stdio # 文本输出
sudo perf annotate function_name # 看汇编 + 哪一行最热
需要程序带 debug symbols(-g 编译,或装 *-dbg 包):
sudo apt install -y nginx-dbg postgresql-16-dbgsym
6. 上下文切换 / 系统调用 / 缓存命中
# 看进程的上下文切换 / 缺页 / cache miss
sudo perf stat -p <PID> -- sleep 10
# Performance counter stats for process id 12345:
# 3,532.18 msec task-clock (10 cpus utilized)
# 48,927 context-switches
# 13,200 cpu-migrations
# 1,234 page-faults
# 9,876,543,210 cycles
# 4,321,098,765 instructions (0.44 insn per cycle)
# 789,012,345 branches
# 34,567,890 branch-misses (4.38% of all branches)
# 看具体的硬件事件
sudo perf stat -e cache-misses,cache-references,L1-dcache-load-misses -p <PID> -- sleep 10
branch-misses 高(> 5%)通常意味着分支预测不友好的代码。
instructions per cycle < 1 是性能差的信号。
7. live 模式(top 风格)
sudo perf top -p <PID>
# 实时看哪些函数当前最热
排查"刚才一瞬间 CPU 高了"很有用。
8. trace syscall(替代 strace 的高性能版)
sudo perf trace -p <PID>
# 实时显示进程的所有 syscall(比 strace 开销小 10x)
sudo perf trace -s -p <PID> -- sleep 10
# 统计 10 秒内的 syscall 次数 + 耗时
9. Python / Node / Java 特殊处理
perf 默认看不到解释器 / VM 里的函数名,需要特殊 hook:
# Python: 用 py-spy 替代 perf
pip install py-spy
sudo py-spy record -o profile.svg --pid <PID>
# Node.js: 用 0x(npm install -g 0x)或 node --perf-basic-prof
node --perf-basic-prof app.js
# 然后 perf record 会读 /tmp/perf-<PID>.map 解析 JS 函数名
# Java: 用 async-profiler
java -agentpath:/path/to/libasyncProfiler.so=start,event=cpu,file=profile.html ...
10. 远程 / 容器内
# 容器内的进程:在 host 上跑 perf,看到的是 host 视角的 PID
sudo perf record -F 99 -p $(pgrep -f my-container-process) -g -- sleep 10
# 如果容器内 / 进程内没 debug symbols,host 上的 perf 看到的也是 [unknown]
# 解决:把容器内的 /usr/lib/debug 也挂到 host,或在容器里跑 perf
11. 实战案例:Python web 应用慢
# 看哪个进程在烧 CPU
top -H -p $(pgrep gunicorn | head -1)
# 多个线程?PID 列里的 TID
sudo py-spy top --pid <TID>
# 实时看每个 Python 函数 CPU 占比
# 或者出火焰图
sudo py-spy record -o /tmp/flame.svg --pid <PID> --duration 30
12. 注意采样精度
-F 99 是每秒 99 次。意味着:
- 持续 100ms 的函数:约 10 个样本,足够看见
- 持续 1ms 的函数:约 0.1 样本,看不到
短函数 + 高频调用看 -F 999 或 -F 4999(CPU 开销变大但更精细)。
踩过的坑
- 没装 debug symbols:火焰图全是
[unknown]一片黑。装*-dbg
/ 重编译加-g。 - 容器 + minimal image:image 里没 debug symbols,分析复合 image
把 dbg 包打进去。 perf record写盘飞快(GB 级 perf.data),磁盘满:缩短采样时间,
或者 perf script 后立刻删 perf.data。- root 才能 perf:调
sysctl kernel.perf_event_paranoid=-1让普通用户
可以采样(生产环境慎用,有信息泄露风险)。
登录后参与评论。