用 perf 找出 CPU 100% 进程的热点函数(含火焰图)

应用 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 让普通用户
    可以采样(生产环境慎用,有信息泄露风险)。
精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。