- 写在前面
- 1. 先区分两个问题:CPU 占用率 vs CPU 热点
- 2. 用 perf 按分钟观察 CPU 相关指标
- 3. perf stat 能不能直接给 CPU 占用率
- 4. 用 pidstat 更直接看分钟级 CPU 占用率
- 5. 用 top / htop 快速观察实时状态
- 6. 用 sar 看历史 CPU 趋势
- 7. 用 perf 找 CPU 热点函数
- 8. 其他常用优化辅助工具
- 9. 一个推荐的排查流程
- 10. 常见注意事项
- 总结
写在前面
程序优化时,最重要的不是一上来就改代码,而是先回答几个问题:
- CPU 是否真的高?
- 是哪个进程高?
- 是用户态高,还是内核态高?
- 是计算多,还是等待 IO / 锁 / 系统调用?
- 热点函数在哪里?
- 优化以后有没有真实改善?
这篇主要记录 Linux 下常用的性能分析工具,重点放在 perf,同时补充 top、htop、pidstat、sar、strace、火焰图等工具。
1. 先区分两个问题:CPU 占用率 vs CPU 热点
这两个问题很像,但不是一回事。
CPU 占用率
CPU 占用率回答的是:
这个程序在一段时间内用了多少 CPU? |
比如:
某个进程 CPU = 180% |
这通常表示它大约用了 1.8 个 CPU 核心。
适合用这些工具看:
tophtoppidstatsar/proc/stat
CPU 热点
CPU 热点回答的是:
CPU 时间主要花在哪些函数/代码路径上? |
比如:
40% 时间花在 matrixMultiply() |
适合用这些工具看:
perf topperf recordperf report- 火焰图 FlameGraph
gprofcallgrind
简单说:
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 \ |
输出大概类似:
# time counts unit events |
这里的 task-clock 很重要,可以用来近似判断 CPU 使用量。
2.2 观察某个进程 PID
如果只看某个进程:
perf stat -I 60000 -p <PID> |
例如:
perf stat -I 60000 -p 12345 \ |
这会每分钟输出一次该进程的性能统计。
如果程序是多线程的,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,那么:
代入:
这表示程序平均使用了约 2 个 CPU 核。
所以:
perf stat 更适合看性能事件, |
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 |
字段解释:
| 字段 | 含义 |
|---|---|
%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 |
说明 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 |
-g 很关键,它能帮助看到热点函数是从哪里调用来的。
7.3 生成火焰图
火焰图适合看整体调用关系。
常见流程:
sudo perf record -F 99 -g -p <PID> -- sleep 60 |
火焰图怎么看:
- 横向越宽,表示占用 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 |
确认:
- 是不是 CPU 高。
- 哪个进程高。
- 是单核满,还是多核都高。
第二步:看分钟级趋势
看某进程:
pidstat -u -p <PID> 60 |
看线程:
pidstat -u -t -p <PID> 60 |
看全系统:
sar -u 60 |
第三步:看 perf 事件
perf stat -I 60000 -p <PID> \ |
关注:
- CPU 使用是否稳定。
- context-switches 是否异常高。
- cache-misses 是否异常高。
- IPC 是否过低。
第四步:找热点函数
sudo perf top -p <PID> |
或者:
sudo perf record -F 99 -g -p <PID> -- sleep 60 |
第五步:针对性优化
常见方向:
- 算法复杂度优化。
- 减少重复计算。
- 减少内存拷贝。
- 改善缓存局部性。
- 减少锁竞争。
- 减少系统调用。
- 合理使用多线程。
- 避免频繁分配释放内存。
第六步:优化后再测一次
优化不能只靠感觉,要对比数据:
优化前 CPU 占用多少? |
10. 常见注意事项
需要权限
有些系统上运行 perf 需要权限:
sudo perf top |
如果遇到权限问题,可能与内核参数有关:
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 高有时候说明程序充分利用了计算资源。
真正要看的是:
- 任务是否按时完成。
- 延迟是否可接受。
- 是否影响其他进程。
- 是否有不必要的空转。
- 是否还有明显热点可以优化。
总结
如果只是看分钟级 CPU 占用率,优先用:
pidstat -u -p <PID> 60 |
如果想用 perf 每分钟看性能事件,用:
perf stat -I 60000 -p <PID> \ |
如果要找真正的优化点,用:
sudo perf top -p <PID> |
或者:
sudo perf record -F 99 -g -p <PID> -- sleep 60 |
我建议形成一个固定习惯:
top/htop 看现象 |
性能优化最怕凭感觉改。先测量,再定位,再优化,最后复测。这样才不会把时间花在错误的地方。