性能分析工具入门:Linux perf、火焰图、strace、top/htop 的基本使用

性能优化这件事,说白了就是先找到瓶颈在哪,再动手改代码。但很多新手一上来就盯着代码看,觉得「我这段逻辑写得挺好啊」。嗯,我当年也这么干过,结果调了一整天,发现是系统调用太频繁了。

所以这一章,咱们先不急着优化。先把工具箱里的几把「扳手」认全了。你想想看,连问题在哪都不知道,怎么修?

1. top / htop:看一眼就知道谁在吃资源

这是最基础的命令,也是我每次排查问题的第一站。登录服务器,先敲个 top,看看 CPU 和内存的「大盘」。

top 的核心字段

字段 含义 我关注的点
%CPU 进程 CPU 使用率 超过 100% 说明是多线程在跑
%MEM 进程内存占比 单个进程吃太多,可能有泄漏
RES 常驻内存(单位 KB) 这个才是实际占用的物理内存
S 进程状态 R 运行,S 睡眠,D 不可中断
TIME+ 累计 CPU 时间 跑了几小时的进程,TIME+ 会很大
小技巧:按 P 按 CPU 排序,按 M 按内存排序。我习惯先按 P 看一眼,再按 M 确认一下。

htop 是 top 的增强版。颜色更直观,支持鼠标操作,还能直接看到进程树。我个人更推荐 htop,尤其是当你需要频繁切换排序时。

# 安装 htop
sudo apt install htop   # Ubuntu/Debian
sudo yum install htop   # CentOS/RHEL

# 直接运行
htop

在 htop 里,你可以用 F6 选择排序字段,F9 直接杀进程。嗯,比 top 方便不少。

注意:top 和 htop 看到的是「瞬时值」。如果 CPU 忽高忽低,建议用 top -b -n 2 -d 1 采集两秒的数据,或者用 htop 的延时模式。

2. strace:追踪系统调用的「显微镜」

有时候进程 CPU 不高,但就是慢。这时候我通常会怀疑:是不是系统调用太多了?

strace 就是干这个的。它能记录进程发起的每一次系统调用,包括参数和返回值。

# 追踪一个正在运行的进程(PID 为 1234)
strace -p 1234

# 追踪新启动的命令
strace -o output.log ls -l

# 只看特定的系统调用(比如 read/write)
strace -e trace=read,write -p 1234

我曾经遇到过一个线上问题:一个 Java 服务每隔几秒就卡顿一下。用 strace 一追,发现它在频繁调用 gettimeofday,每秒几千次。后来查代码,是某个日志框架在每次写日志时都取了一次时间戳。去掉之后,性能直接翻倍。

核心思路:strace 的输出会很多。别傻看,用 -c 参数做统计,看看哪个系统调用耗时最长。
# 统计系统调用耗时
strace -c -p 1234

# 输出示例
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 45.23    0.123456       12.34     10000           read
 30.12    0.087654        8.76     10000           write
 15.45    0.045678        4.56     10000           gettimeofday
...

看到没?gettimeofday 占了 15% 的时间。这就是线索。

避坑指南:strace 会大幅降低进程性能(可能慢 10-100 倍)。千万不要在生产环境直接 strace 一个高并发服务。我一般先在测试环境复现,或者用 -p 只 attach 几秒钟。

3. Linux perf:CPU 性能分析的「核武器」

top 告诉你哪个进程吃 CPU,strace 告诉你系统调用慢。但如果你想知道「CPU 到底在忙什么」,那就得上 perf 了。

perf 是 Linux 内核自带的性能分析工具。它能采样 CPU 的指令指针,告诉你代码的哪些行、哪些函数占用了最多的 CPU 时间。

perf 的基本用法

# 采样一个正在运行的进程(PID 1234),持续 10 秒
perf record -p 1234 -g -- sleep 10

# 生成报告
perf report

# 只看调用链
perf report --stdio -g

-g 参数是关键。它开启调用链采样,这样你就能看到「谁调了谁」。我见过很多团队只采样不记录调用链,结果看到一个热点函数叫 do_work,但不知道是谁调进来的,等于白干。

我的习惯:先用 perf top 看一眼热点,再决定要不要 perf record。perf top 是实时显示的,不用等。
# 实时查看 CPU 热点
perf top -p 1234

perf 的输出里,你会看到类似这样的内容:

Samples: 1K of event 'cycles', Event count (approx.): 123456789
Overhead  Shared Object       Symbol
  25.12%  libc-2.31.so        [.] __memcpy_avx_unaligned
  15.45%  myapp               [.] process_request
  10.23%  [kernel]            [k] do_syscall_64
...

看到 __memcpy_avx_unaligned 占 25%?说明你的程序在大量拷贝内存。这时候就该想想:能不能减少拷贝?能不能用零拷贝技术?

4. 火焰图:让性能瓶颈「一目了然」

perf report 的输出是文本的,看久了眼睛疼。火焰图(Flame Graph)是 Brendan Gregg 发明的可视化工具,把 perf 的采样数据变成一张 SVG 图。

火焰图长什么样?

  • X 轴:采样数量(不是时间),越宽的函数占 CPU 越多。
  • Y 轴:调用栈深度,越往上越深。
  • 颜色:通常橙色是用户态,蓝色是内核态。

怎么看?找「平顶山」——就是那些顶部很宽的函数。它们就是 CPU 的热点。

生成火焰图的步骤

# 1. 用 perf 采集数据
perf record -p 1234 -g -- sleep 30

# 2. 生成火焰图(需要 FlameGraph 工具)
git clone https://github.com/brendangregg/FlameGraph
cd FlameGraph

# 3. 转换 perf 数据
perf script -i ../perf.data | ./stackcollapse-perf.pl > out.perf-folded

# 4. 生成 SVG
./flamegraph.pl out.perf-folded > flame.svg

# 5. 用浏览器打开 flame.svg
关键点:火焰图不是实时工具,它是事后分析的。我一般先跑 perf record 采集 30-60 秒,再生成火焰图慢慢看。

我曾经帮一个团队排查 Node.js 服务的性能问题。他们觉得是数据库慢,结果火焰图一看,40% 的 CPU 花在 JSON 序列化上。后来换了更快的序列化库,QPS 直接翻倍。

小技巧:火焰图支持鼠标悬停查看函数名和占比。你还可以点击某个函数「放大」,只看它的子调用链。

5. 知识体系:这些工具怎么配合?

工具多了,容易乱。我画了一张图,帮你理清思路。

性能分析工具知识体系 性能分析工具知识体系 你遇到了什么性能问题? CPU 使用率高 进程卡顿/慢 内存/IO 异常 perf / perf top 火焰图(FlameGraph) strace -c 统计 strace -e trace=xxx top / htop 查看 结合 perf 看内核 先看大盘(top/htop),再选工具深挖。不要一上来就 strace 或 perf record。

这张图的核心逻辑很简单:

  1. 先看大盘:用 top/htop 确认是 CPU、内存还是 IO 的问题。
  2. CPU 高:上 perf 采样,生成火焰图,找热点函数。
  3. 进程卡顿:用 strace 追踪系统调用,看是不是频繁 IO 或锁等待。
  4. 内存/IO 异常:top 看内存,perf 看内核态,结合分析。
我的经验:80% 的性能问题,用 top + strace -c 就能定位。只有遇到 CPU 密集型瓶颈时,才需要上 perf 和火焰图。别把工具用复杂了。

6. 避坑指南:新手最容易犯的错

  • 在线上直接 strace 高并发进程:strace 会让进程慢 10-100 倍。我一般用 -p attach 几秒,或者先抓 perf 数据。
  • perf record 不设时长:默认会一直跑,直到你 Ctrl+C。建议加 -- sleep 10 限制时间。
  • 火焰图不看 Y 轴:只看 X 轴宽度,忽略调用栈深度。其实 Y 轴能告诉你「这个函数是被谁调进来的」。
  • 只看用户态,不看内核态:很多性能瓶颈在内核,比如文件系统、网络栈。perf 默认采样用户态,记得加 -k 或 --all-user 参数。
曾经我踩过的坑:有一次我 perf record 了一个 Java 服务,结果火焰图里全是 JVM 的 C2 编译器。后来才发现,我没装 debug symbols,perf 没法把地址翻译成函数名。所以,记得装 perf-map-agent 或者用 -g 参数。