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

写在前面

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

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

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

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

说明 solveIKmemcpy 可能是优化重点。

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 找热点
火焰图看调用栈全貌
最后再改代码并复测

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

algorithms axis-angle bode calibration chrome cmake cmakelists cnn colcon conan control cpp cpu d435i data_struct db design-pattern dots economics eigen factory-pattern fcpx figure finance forge fov gazebo gdb git gnu ibus interest isaac gym isaaclab kdl latex launch learning-notes legged locomotion legged-robot life linux mac math matlab matrix memory mlp money motor moveit mpc network ocs2 ode operator optimal algorithm optimal-control perf performance personal-finance ppo profiling python qos realsense rnn robot robotics ros ros2 rtb security shell simulation stl thread tools twist ubuntu uml unitree urdf vae valgrind vcxsrv velocity vim web wifi work wsl 中文输入 交叉编译 依赖管理 分支管理 四足机器人 强化学习 机器人视觉 构建系统 深度学习 深度相机 点云 版本控制 神经网络 输入法 配置类
知识共享许可协议