Linux 性能分析:用 perf 观察分钟级 CPU 占用率,并了解常用优化工具

2026 补充:从监控异常走到优化证据

Linux 工具实战专题新增进程、资源、ELF 与工程任务的操作 GIF。本文继续负责 perf 与优化方法,不再复制完整工具参数列表。

建议分三层记录:

层次 问题 工具与边界
现象 延迟、吞吐、错误率是否真的退化? 业务侧计时和固定负载,先定义单位与时间范围
资源 是 CPU、内存、磁盘还是等待? top / ps、free / vmstat / iostat / sar
代码 哪条调用路径消耗 CPU 或产生等待? perf 采样、栈、strace;不同观察方式不能互相替代

例如 %CPU 很低而请求延迟很高,先追问是不是排队、外部服务或锁等待,不要期待 CPU 火焰图自动解释所有墙钟时间。strace 观察系统调用,普通 CPU 采样观察运行中的代码;用户态计算、睡眠、I/O 等待的可见性不同。

一次只验证一个假设

  1. 固定输入、并发、运行时长与资源配额,记录版本。
  2. 保留预热后的基线,不把进程启动和稳定运行混在一起。
  3. 用低开销汇总先定位,再对小范围目标采样。
  4. 只改变一个因素,保留变更前后的原始输出。
  5. 重复试验,比较中位数和尾延迟;均值下降不代表最慢请求改善。

不能用调试构建的耗时直接代表发布构建,也不能把加入 strace 后的延迟当成未被观测时的精确耗时。CPU 频率、调度、缓存、并行度和采样开销都会影响结论。

Docker 中要补记哪些条件

记录 cgroup 的 CPU quota、cpuset、内存上限与 perf_event_paranoid;能读到 /proc 不代表所有指标都只属于容器。Docker Desktop 还隔着 Linux VM,VM 的块设备利用率不是 macOS 上某个文件的直接延迟。

权限不足时,先保存实际错误并确认作用域;不要为了拿到一个图就全局放开内核限制或把常驻容器改成 --privileged。本次 Linux 工具实验不声称验证裸机 PMU、生产磁盘压力、火焰图生成或长期性能稳定性。下面继续保留原 perf 用法与历史示例。

写在前面

程序优化时,最重要的不是一上来就改代码,而是先回答几个问题:

  1. CPU 是否真的高?
  2. 是哪个进程高?
  3. 是用户态高,还是内核态高?
  4. 是计算多,还是等待 IO / 锁 / 系统调用?
  5. 热点函数在哪里?
  6. 优化以后有没有真实改善?

这篇主要记录 Linux 下常用的性能分析工具,重点放在 perf,同时补充 top、htop、pidstat、sar、strace、火焰图等工具。

1. 先区分两个问题:CPU 占用率 vs CPU 热点

这两个问题很像,但不是一回事。

CPU 占用率

CPU 占用率回答的是:

这个程序在一段时间内用了多少 CPU?

比如:

某个进程 CPU = 180%

这通常表示它大约用了 1.8 个 CPU 核心。

适合用这些工具看:

  • top
  • htop
  • pidstat
  • sar
  • /proc/stat

CPU 热点

CPU 热点回答的是:

CPU 时间主要花在哪些函数/代码路径上?

比如:

40% 时间花在 matrixMultiply()
25% 时间花在 memcpy()
10% 时间花在 std::sort()

适合用这些工具看:

  • perf top
  • perf record
  • perf report
  • 火焰图 FlameGraph
  • gprof
  • callgrind

简单说:

CPU 占用率:告诉你问题大不大。
CPU 热点:告诉你应该改哪里。

2. 用 perf 按分钟观察 CPU 相关指标

perf stat 可以统计一段时间内的硬件事件和软件事件,例如:

  • cycles
  • instructions
  • branches
  • branch-misses
  • cache-misses
  • task-clock
  • context-switches
  • cpu-migrations
  • page-faults

2.1 每 60 秒输出一次总体统计

如果想每 1 分钟输出一次统计,可以用:

perf stat -I 60000 -a

含义:

  • -I 60000:interval,每 60000 ms 输出一次。
  • -a:all CPUs,统计所有 CPU。

可以指定关注的事件:

perf stat -I 60000 -a \
-e task-clock,cycles,instructions,context-switches,cpu-migrations,page-faults,cache-misses

输出大概类似:

# time             counts unit events
60.000123456 60000.00 msec task-clock
60.000123456 180000000000 cycles
60.000123456 120000000000 instructions
60.000123456 12345 context-switches

这里的 task-clock 很重要,可以用来近似判断 CPU 使用量。

2.2 观察某个进程 PID

如果只看某个进程:

perf stat -I 60000 -p <PID>

例如:

perf stat -I 60000 -p 12345 \
-e task-clock,cycles,instructions,context-switches,cpu-migrations,page-faults,cache-misses

这会每分钟输出一次该进程的性能统计。

如果程序是多线程的,perf 会统计该进程及其线程的整体情况。

2.3 观察某个命令

如果想直接运行一个程序并统计:

perf stat ./my_program

如果程序运行很久,也可以:

perf stat -I 60000 ./my_program

带参数的例子:

perf stat -I 60000 ./my_program --config config.yaml

2.4 常用事件解释

指标 含义 怎么理解
task-clock 任务在 CPU 上运行的总时间 可估算 CPU 使用量
cycles CPU 周期数 越高表示消耗 CPU 周期越多
instructions 执行指令数 与 cycles 一起看 IPC
instructions per cycle 每周期执行指令数 越高通常说明 CPU 利用效率越好
context-switches 上下文切换次数 太高可能有线程竞争/调度问题
cpu-migrations 线程在 CPU 间迁移次数 太高可能影响缓存局部性
page-faults 缺页次数 太高可能有内存访问/加载问题
cache-misses 缓存未命中 太高可能说明内存访问局部性差
branch-misses 分支预测失败 分支复杂或数据不稳定时可能较高

3. perf stat 能不能直接给 CPU 占用率

perf stat 不是专门显示 %CPU 的工具,但可以用 task-clock 估算。

如果统计区间是 60 秒,某进程的 task-clock 是 120000 ms,那么:

CPU%≈task_clock_msinterval_ms×100%CPU\% \approx \frac{task\_clock\_{ms}}{interval\_{ms}} \times 100\%

代入:

CPU%≈12000060000×100%=200%CPU\% \approx \frac{120000}{60000}\times100\%=200\%

这表示程序平均使用了约 2 个 CPU 核。

所以:

perf stat 更适合看性能事件,
如果只想看 CPU 占用率,pidstat/top 更直接。

4. 用 pidstat 更直接看分钟级 CPU 占用率

如果目标是“每分钟看一次某进程 CPU 占用率”,我更推荐:

pidstat -u -p <PID> 60

例如:

pidstat -u -p 12345 60

输出类似:

09:30:01 UID PID %usr %system %guest %wait %CPU CPU Command
09:31:01 1000 12345 80.00 10.00 0.00 0.00 90.00 3 my_program

字段解释:

字段 含义
%usr 用户态 CPU 占用
%system 内核态 CPU 占用
%wait 等待 CPU 或 IO 的时间
%CPU 总 CPU 占用
CPU 当前运行在哪个 CPU 上

如果要看所有进程:

pidstat -u 60

如果要看线程级别:

pidstat -u -t -p <PID> 60

线程级别很有用,可以判断是不是某个线程特别忙。

5. 用 top / htop 快速观察实时状态

top

最基础:

top

查看某个 PID:

top -p <PID>

在 top 中常用操作:

操作 作用
P 按 CPU 排序
M 按内存排序
H 显示线程
1 显示每个 CPU 核

htop

htop 更直观:

htop

优点:

  • 彩色显示。
  • 可以按 CPU / 内存排序。
  • 可以展开线程。
  • 可以直接搜索进程。

如果只是现场快速判断“谁在吃 CPU”,top/htop 很方便。

6. 用 sar 看历史 CPU 趋势

sar 来自 sysstat,适合看历史趋势。

安装:

sudo apt install sysstat

每 60 秒看一次 CPU:

sar -u 60

看每个 CPU 核:

sar -P ALL 60

典型输出:

%user %nice %system %iowait %steal %idle

如果 %iowait 很高,问题可能不是 CPU 计算,而是 IO 等待。

7. 用 perf 找 CPU 热点函数

当你已经确认程序 CPU 高,下一步通常不是继续看占用率,而是找热点函数。

7.1 perf top:实时看热点

sudo perf top -p <PID>

或者全系统:

sudo perf top

它会实时显示 CPU 时间主要消耗在哪些函数上。

如果看到:

30.00%  my_program  my_program  [.] solveIK
20.00% my_program libc.so [.] memcpy

说明 solveIK 和 memcpy 可能是优化重点。

7.2 perf record/report:采样并离线分析

采样 30 秒:

sudo perf record -F 99 -p <PID> -- sleep 30

然后查看报告:

sudo perf report

参数说明:

  • -F 99:采样频率 99 Hz。
  • -p <PID>:指定进程。
  • sleep 30:采样 30 秒。

如果想记录调用栈:

sudo perf record -F 99 -g -p <PID> -- sleep 30
sudo perf report

-g 很关键,它能帮助看到热点函数是从哪里调用来的。

7.3 生成火焰图

火焰图适合看整体调用关系。

常见流程:

sudo perf record -F 99 -g -p <PID> -- sleep 60
sudo perf script > out.perf
stackcollapse-perf.pl out.perf > out.folded
flamegraph.pl out.folded > flamegraph.svg

火焰图怎么看:

  • 横向越宽,表示占用 CPU 时间越多。
  • 越靠上,表示调用栈越深。
  • 找最宽的块,通常就是优化重点。

8. 其他常用优化辅助工具

工具 主要用途 适合场景
top 实时看进程 CPU/内存 快速定位哪个进程异常
htop 更友好的 top 交互式查看进程和线程
pidstat 按进程/线程统计 CPU 分钟级 CPU 占用率监控
sar 系统历史性能趋势 看 CPU/IO/内存随时间变化
perf stat 性能事件统计 看 cycles、instructions、cache miss
perf top 实时热点函数 现场观察 CPU 热点
perf record/report 采样分析 细看热点调用栈
strace 系统调用跟踪 判断是否频繁 syscall/IO
ltrace 库函数调用跟踪 看动态库调用
valgrind/callgrind 精细函数级分析 较慢,但信息细
gprof 编译插桩分析 老工具,需要编译支持
bpftrace eBPF 动态跟踪 高级线上诊断
iostat 磁盘 IO 判断是否 IO 瓶颈
vmstat 系统整体状态 CPU、内存、上下文切换
free 内存概况 快速看内存是否紧张

9. 一个推荐的排查流程

我自己比较推荐这个顺序:

第一步:先看整体

top
htop

确认:

  • 是不是 CPU 高。
  • 哪个进程高。
  • 是单核满,还是多核都高。

第二步:看分钟级趋势

看某进程:

pidstat -u -p <PID> 60

看线程:

pidstat -u -t -p <PID> 60

看全系统:

sar -u 60

第三步:看 perf 事件

perf stat -I 60000 -p <PID> \
-e task-clock,cycles,instructions,context-switches,cpu-migrations,page-faults,cache-misses

关注:

  • CPU 使用是否稳定。
  • context-switches 是否异常高。
  • cache-misses 是否异常高。
  • IPC 是否过低。

第四步:找热点函数

sudo perf top -p <PID>

或者:

sudo perf record -F 99 -g -p <PID> -- sleep 60
sudo perf report

第五步:针对性优化

常见方向:

  1. 算法复杂度优化。
  2. 减少重复计算。
  3. 减少内存拷贝。
  4. 改善缓存局部性。
  5. 减少锁竞争。
  6. 减少系统调用。
  7. 合理使用多线程。
  8. 避免频繁分配释放内存。

第六步:优化后再测一次

优化不能只靠感觉,要对比数据:

优化前 CPU 占用多少?
优化后 CPU 占用多少?
延迟是否下降?
吞吐是否提升?
是否引入新的抖动?

10. 常见注意事项

需要权限

有些系统上运行 perf 需要权限:

sudo perf top
sudo perf record ...

如果遇到权限问题,可能与内核参数有关:

cat /proc/sys/kernel/perf_event_paranoid

临时调整示例:

sudo sysctl kernel.perf_event_paranoid=1

线上机器不要随便改系统参数,最好先确认权限和安全要求。

编译符号很重要

如果希望 perf report 里看到清楚的函数名,编译时最好保留符号信息:

g++ -O2 -g main.cpp -o main

-g 用于调试符号,-O2 保持接近真实优化环境。

如果二进制被 strip,看到的函数名可能不完整。

采样时间要覆盖真实场景

不要只采样程序刚启动的几秒。

最好覆盖真实负载,例如:

sudo perf record -F 99 -g -p <PID> -- sleep 60

如果问题偶发,可以采样更久,或者配合日志定位问题发生时间。

CPU 高不一定是坏事

CPU 高有时候说明程序充分利用了计算资源。

真正要看的是:

  1. 任务是否按时完成。
  2. 延迟是否可接受。
  3. 是否影响其他进程。
  4. 是否有不必要的空转。
  5. 是否还有明显热点可以优化。

总结

如果只是看分钟级 CPU 占用率,优先用:

pidstat -u -p <PID> 60

如果想用 perf 每分钟看性能事件,用:

perf stat -I 60000 -p <PID> \
-e task-clock,cycles,instructions,context-switches,cpu-migrations,page-faults,cache-misses

如果要找真正的优化点,用:

sudo perf top -p <PID>

或者:

sudo perf record -F 99 -g -p <PID> -- sleep 60
sudo perf report

我建议形成一个固定习惯:

top/htop 看现象
pidstat/sar 看趋势
perf stat 看性能事件
perf top/record/report 找热点
火焰图看调用栈全貌
最后再改代码并复测

性能优化最怕凭感觉改。先测量,再定位,再优化,最后复测。这样才不会把时间花在错误的地方。

知识共享许可协议