news 2026/9/16 19:33:48

Linux性能分析利器perf:从perf stat到火焰图与动态追踪

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux性能分析利器perf:从perf stat到火焰图与动态追踪

聊Linux性能分析,绕不开perf。它是Linux内核自带的性能剖析工具,从CPU热点定位、缓存失效分析,到内核函数动态插桩、火焰图生成,几乎覆盖了日常性能排查的所有主流场景。简单说,perf就是一套“内核级探针+采样器+数据分析器”的组合,不需要额外安装agent,只要内核支持perf_events子系统,拿来即用。

这篇文章我把perf从安装、权限配置,到stat、record、report、annotate这些高频命令,再到火焰图、动态追踪这类进阶玩法,按我平时排查问题的实际路径完整过一遍。适合刚接触性能调优的运维和开发,也适合已经会用perf top、但还没把record/report链路跑通的进阶用户。实战中踩过的坑我会重点标出来,这些在官方文档和网上教程里基本看不到。

1. perf到底能解决什么问题

1.1 perf的定位:不只是“CPU占用率”工具

很多人把perf理解成“看CPU占用”的工具,这个理解太窄了。top、htop也能看CPU占用,perf真正的价值在于回答三个问题:程序的时间到底花在哪条指令上?程序为什么频繁发生上下文切换?用户态到内核态的调用路径上,哪个环节成了瓶颈?

perf基于内核的perf_events子系统实现,它既能做基于硬件的性能计数器采样,也能做基于软件事件和tracepoint的追踪。硬件计数器这块尤其强大,比如CPU缓存命中率、分支预测失败率、流水线停顿周期,这些指标用普通系统监控工具根本看不到,但perf可以直接从PMU里读出来。

我用一个生活化的比喻:top相当于看体检报告上的体重和体温,perf则像是给你做一个全身CT加心电图,能把问题定位到具体是哪一块肌肉在抽搐。比如你发现CPU使用率90%,top只能告诉你进程CPU高,perf能告诉你高在libc里的memcpy,还是高在内核的page fault处理路径上,差很远。

1.2 典型场景:什么时候应该掏perf

我自己的使用习惯,遇到下面几类问题会第一时间想到perf:

第一类是CPU飙高但找不到原因。业务进程多线程,线程名又起得乱七八糟,top看不出具体是哪个线程在空转,用perf top -p按进程/线程抓,热点函数直接浮出来。

第二类是性能数据“正常”但用户体验差。比如请求RT抖动,平均延迟不高,但P99很高。这种情况多半和调度延迟、锁竞争、CPU迁移有关,用perf sched timehist配合perf record -g追踪callchain,能比较清楚地还原当时的时序。

第三类是内核态开销异常。用户态CPU才占了30%,但整机CPU已经100%,剩下的哪去了?大概率在内核。普通工具看不透内核,perf可以采样内核调用栈,直接定位到某个驱动、某个协议栈函数,甚至某个锁。

第四类是缓存和访存密集型应用的调优。数据库、搜索引擎这类对内存带宽敏感的场景,用perf stat看cache-misses和stalled-cycles,基本能判断是访存模式出了问题还是数据布局出了问题。

2. 环境准备与第一次出手

2.1 安装perf:不同发行版的差异点

perf工具在大多数发行版里不是默认安装的,但装起来很省事。Debian/Ubuntu系列跑:

sudo apt install linux-tools-common linux-tools-generic linux-tools-$(uname -r)

CentOS/RHEL/Rocky系列跑:

sudo yum install perf

有个细节容易被忽略:Ubuntu如果只装了linux-tools-common,没装linux-tools-$(uname -r),执行perf会提示“WARNING: perf not found for kernel X”,然后建议你装某个具体版本包。这个提示其实完全可以照做,它指向的包名通常就是当前内核对应的tools版本。

国产Linux发行版近两年我接触得也多了,比如基于CentOS的、基于Debian的都有,安装方式和上游基本一致,要么yum install perf,要么apt install linux-tools。但国产发行版的内核版本可能定制过,偶尔会遇到perf工具版本和内核版本不匹配的情况,报错一般是“Kernel address maps”或“module ... not found”之类。遇到这类问题,别硬折腾工具本身,先确认内核的debugfs和perf_event是否被默认关闭。

编译安装也算一条路。内核源码里tools/perf目录拿下来,直接make就能编出perf,依赖libelf、libdw、slang这些。不过这属于少数派需求,绝大多数服务器用发行版自带的包就够了。

2.2 权限配置:perf_event_paranoid是第一个坑

权限配置几乎是perf新手的第一道坎。内核有一个参数叫kernel.perf_event_paranoid,默认值很多发行版设的是2,这个值下普通用户不能访问硬件性能计数器,也不能做内核态采样。

查当前值:

sysctl kernel.perf_event_paranoid

临时改:

sudo sysctl -w kernel.perf_event_paranoid=1

永久生效写到/etc/sysctl.conf里。值设多少合适,我建议是这样:

  • 2:默认安全值,用户态的基本统计分析还能做,但硬件计数器用不了
  • 1:允许使用CPU硬件计数器,但内核态采样受限
  • 0:允许内核态采样,perf probe也可以用了
  • -1:完全放开,没有任何限制

生产环境建议先设成1,排查内核态问题时再临时降到0或-1,排查完再改回来。你如果直接装完perf就report,很容易遇到“perf_event_open failed: Permission denied”之类的报错,第一反应就应该是查这个参数。

2.3 第一眼定位热点:perf top实战

perf top是我最常用的“快速扫描”命令,类似top的交互界面,但它显示的是热点函数而不是进程。

sudo perf top

默认按CPU周期采样,刷新实时显示热点函数。界面里第一列是开销占比,第二列是符号名,第三列是调用信息。按t可以切换排序字段,按1展开或收起CPU核心视图。

几个参数值得记一下:

# 只看某个进程 sudo perf top -p <pid> # 显示调用链,能看到热点函数是被谁调上来的 sudo perf top -g # 指定采样事件,比如看cache miss热点 sudo perf top -e cache-misses # 指定采样频率,默认4000Hz,低一点对系统影响更小 sudo perf top -F 999

这里有个经验:perf top -g开调用链后,输出信息量暴涨,但如果你对代码不熟,反而容易懵。我个人的习惯是先不开-g,直接看哪个函数占比最高,然后去查这个函数属于什么模块,再针对性地用record抓调用链。一步一步来,比一次性把所有信息糊脸上效果好得多。

2.4 perf list:先看看你的机器支持什么

perf支持的事件非常多,取决于CPU型号、内核版本、芯片厂商。查看全部事件:

perf list

这个命令输出很长,可以按类别过滤,比如只看硬件事件、软件事件、tracepoint事件:

perf list hw perf list sw perf list tracepoint

tracepoint是内核预设的静态插桩点,数量巨大,按子系统分类,比如sched:*block:*kmem:*syscalls:*。我在排查进程调度问题的时候,最常用的是这组:

perf list 'sched:*'

看输出你会发现,sched_switch、sched_wakeup这些事件都能直接作为采样源。这比单纯采CPU周期更适合回答“线程为什么卡住”这类问题。每次拿到一台新机器,我会扫一眼perf list,心里大概有个数,知道哪些事件可用,到真正排障时就不用手忙脚乱。

3. 三条主命令:stat、record、report彻底吃透

3.1 perf stat:一条命令看穿硬件计数器

perf stat适合对“一次运行”做全量统计,用法简单:

perf stat <command> # 也可以针对运行中的进程做一段时间统计 sudo perf stat -p <pid> -- sleep 10

输出长这样:

Performance counter stats for './myapp': 1,023.45 msec task-clock # 0.999 CPUs utilized 5 context-switches # 0.005 /sec 0 cpu-migrations # 0.000 /sec 124 page-faults # 0.121 K/sec 2,542,310,101 cycles # 2.484 GHz 1,982,113,452 instructions # 0.78 insn per cycle 302,123,411 branches # 295.203 M/sec 4,510,223 branch-misses # 1.49% of all branches

每个字段都有讲究:

  • task-clock:进程占用CPU的总时间
  • context-switches:上下文切换次数,频繁切换往往意味着锁竞争或调度问题
  • cpu-migrations:进程在不同CPU核心之间迁移的次数,大量迁移会搞坏CPU缓存
  • page-faults:缺页次数,频繁缺页说明进程内存访问模式有问题
  • cycles:CPU周期总数
  • instructions:执行的指令数
  • insn per cycle(IPC):每周期指令数,这是评估CPU利用率的核心指标,IPC接近1说明流水线利用得不错,低于0.5大概率有访存停顿或分支预测问题
  • branch-misses:分支预测失效率,对分支密集的代码影响巨大

一条命令就能发现应用是“计算密集”还是“访存密集”还是“调度频繁”,这是perf stat最有价值的地方。实战中我建议多跑几组对照,比如并发量低的时候跑一次,并发量高的时候再跑一次,对比cache-misses和context-switches的变化趋势,问题基本就浮出来了。

如果要叠加更多定制事件:

perf stat -e cycles,instructions,cache-references,cache-misses,context-switches <command>

cache-references和cache-misses的比例是判断缓存命中率的关键。命中率低于95%、甚至低于90%的时候,程序基本就是在等内存,加再多CPU核心也没用。

3.2 perf record + perf report:采样与离线分析的黄金组合

perf stat给的是宏观统计数据,如果要定位到具体函数,就必须用record做采样。

# 全系统采样,带调用链,99Hz采样频率,持续60秒 sudo perf record -a -g -F 99 -- sleep 60 # 针对某个进程采样 sudo perf record -p <pid> -g -- sleep 30 # 针对某次命令执行的采样 sudo perf record -g ./myapp --input data.bin # 也可以指定事件,比如采上下文切换事件 sudo perf record -e context-switches -a -g

几个参数的含义:

  • -a:全系统模式
  • -g:记录调用链,backtrace
  • -F 99:采样频率99Hz,99这个数字是性能分析界的“标准值”,既能保证统计可靠性,又不至于开销太大
  • -p:指定进程ID

采样结束后当前目录会生成perf.data文件,然后replay输出:

sudo perf report

report界面是交互式的,常用操作键:

  • 回车/展开:展开调用链
  • 上下箭头:移动光标
  • t:切换绝对占比和相对占比
  • a:进入annotate模式,查看热点代码的汇编和源码
  • +/-:展开/折叠调用链
  • h:帮助

第一次用report的人容易被海量信息淹没。我的建议是,先看顶部的“Children”和“Self”两列,Self占比高表示时间真正花在这个函数内部,Children占比高表示时间花在它的子函数里。优先处理Self占比高的函数,这些才是真正的CPU消耗点。Children占比高但Self很低的,通常是聚合节点,比如main函数,没必要在main上花时间优化。

3.3 perf annotate:热点代码的“显微镜”

report只能看到函数级别,annotate可以深入到指令级别。在report界面光标选中热点函数后按a,或者直接用命令行:

# 生成单个函数的汇编级注释报告 sudo perf annotate -s <function_name> --stdio

输出会逐条显示指令,以及每条指令的采样占比。这里能直观看到耗时指令是哪条,比如一条movslq占了这个函数50%的采样,说明它极大概率在反复从内存加载数据。配合源码,基本能断定是循环、数组访问还是锁等待。

annotate能工作有个前提:二进制必须带符号表。很多线上程序strip过,只有地址没有符号名。解决办法有两个:一是优化编译时保留-g选项;二是针对线上程序,用perf内置的符号解析能力加载外部符号表文件。但最省事的手段,是让开发环境用同样的代码重新编译一个带符号的版本,采样数据拷过去做离线分析。整个过程不影响线上运行,只需要约定期限的采样文件和二进制副本。

3.4 record/report高频参数速查

我用过一段时间的perf之后,整理了一套高频组合,分享给大家:

# 用户态+内核态调用链,99Hz采样60秒,最适合CPU热点排查 sudo perf record -a -g -F 99 -- sleep 60 # 指定进程,带调用链,适合单服务分析 sudo perf record -p <pid> -g -- sleep 30 # 采调度事件,查延迟抖动 sudo perf record -e sched:sched_switch -a -g -- sleep 10 # 采缓存失效事件,查访存瓶颈 sudo perf record -e cache-misses -a -g -- sleep 30

-F采样频率不是越大越好。频率太高,perf自身开销会明显影响采样结果,出现严重的观测者效应。99Hz已经能覆盖绝大多数场景,追求更细的时间分辨率可以用-F 999,但要评估对业务的干扰。

4. 进阶玩法:火焰图、动态追踪与延迟分析

4.1 火焰图:从采样数据到可视化

perf report的交互界面虽然强大,但在对外汇报、团队协作场景下,火焰图明显更直观。Brendan Gregg发明的火焰图已经成为性能分析的事实标准,perf的数据可以直接转换。

生成火焰图需要两个脚本:stackcollapse-perf.plflamegraph.pl,来自Brendan Gregg的FlameGraph项目:

git clone https://github.com/brendangregg/FlameGraph.git cd FlameGraph # 先把perf.data转成脚本数据 sudo perf script > out.perf # 折叠成调用栈计数格式 ./stackcollapse-perf.pl out.perf > out.folded # 生成SVG火焰图 ./flamegraph.pl out.folded > out.svg

生成的SVG用浏览器打开,每个色块代表一个函数,宽度和采样占比成正比,鼠标悬停可以看到具体函数名和占比。火焰图底部是最底层的函数,顶部是最外层的调用入口。找性能瓶颈的核心方法很简单:找“平顶山”,也就是顶部很宽且颜色厚实的色块,这些函数占的CPU时间最多,值得深挖。

我在实践中发现一个坑:perf script输出的调用栈信息,如果程序是用内部符号表解析的,可能只显示地址不显示函数名。这种情况通常是符号表不完整,要么用带符号的二进制重新解析,要么在record前用--symfs指定符号目录。大多数时候,缺符号的问题根源就是strip,多说一句:线上程序最好保留带符号的副本,哪怕只用来看perf数据,也值这个磁盘空间。

4.2 perf probe:动态插桩的实用场景

perf还有一个很能打的功能是动态插桩,可以在运行中的内核函数或用户态程序的函数入口注入探针,采集参数和返回值。这在排查某些诡异问题的时候比采样好用得多。

先看内核函数插桩:

# 在系统调用入口插桩 sudo perf probe --add tcp_sendmsg # 查看插桩点 sudo perf probe -l # 按插桩事件采样 sudo perf record -e probe:tcp_sendmsg -a -- sleep 10

用户态程序的动态插桩,用-x指定二进制路径:

sudo perf probe -x /usr/lib/x86_64-linux-gnu/libc.so.6 malloc sudo perf record -e probe_libc:malloc -a -- sleep 5

更高级的用法是抓函数参数:

sudo perf probe 'tcp_sendmsg size' sudo perf record -e probe:tcp_sendmsg -a -- sleep 10 sudo perf script

这样能看到每次tcp_sendmsg调用时size的具体值,对分析网络协议栈的报文大小分布很有用。

但要注意,perf probe在内核态插桩,强烈不建议在生产环境随意添加。插桩本身有风险,搞不好会引入内核不稳定因素。我在非生产环境试过在文件系统写路径上插桩,就遇到过因为探针事件过多导致的内核日志刷屏。生产环境要动它,务必先在预发环境完整验证,并且用perf probe --del及时清理不需要的探针。

4.3 perf sched:揪出延迟抖动和调度问题

对于“性能数据正常但体验卡顿”的问题,perf sched是一把好手。它专门分析内核调度器行为。

# 采样调度事件 sudo perf sched record -- sleep 10 # 查看调度延迟统计 sudo perf sched latency

latency输出会按任务展示平均调度延迟、最大延迟和延迟分布。我排查过一个网络服务P99延迟飙高的问题:业务线程的平均调度延迟只有0.2ms,但P99达到了50ms,perf sched timehist一看,原来是某个线程频繁让出CPU,然后被调度器放到另一个NUMA节点上,导致内存访问跨节点,延迟飙升。这就是典型的调度和NUMA共同作用的问题,单靠业务日志根本看不出来。

在虚拟化场景下,perf同样能帮上忙。比如你在一台KVM宿主机上跑了很多虚拟机,某个虚机网络时延异常。先在宿主机上执行:

sudo perf top -g

大概率能看到vhost进程或者kvm模块的函数热点。如果是vhost作为网络数据平面的瓶颈,perf会告诉你热点是在vhost_work系列函数里,还是内核协议栈里。这个信息对判断“该调virtio队列数”还是“该换网卡多队列策略”非常关键。

4.4 perf kvm:虚拟化场景的性能视角

KVM环境下的性能分析,perf有一个专门的扩展模式:

# 采集host和guest的采样数据 sudo perf kvm --host --guest record -a -g -- sleep 30 # 生成report sudo perf kvm --host --guest report

它会同时区分host和guest的调用栈,能看出是虚拟化层开销大还是guest内部自身开销大。排查虚机CPU steal高的场景,这类工具几乎是唯一能直接给你证据的手段。

不过perf kvm对内核配置有要求,某些发行版的内核没开KVM的perf支持,执行时会报错。遇到报错先去确认/boot/config-$(uname -r)里有没有CONFIG_KVM_INTELCONFIG_KVM_AMD,以及有没有CONFIG_KVM_TRACE。没有对应配置的话,perf kvm就用不了,只能退回到host侧做常规采样分析。

5. 常见问题与排查技巧实录

5.1 所有计数器都是0或Permission denied

这是perf最经典的问题。报错信息通常是perf_event_open failed: Permission denied,或者You may not have permission to collect stats。原因基本都指向kernel.perf_event_paranoid。解决方式前面说了:

sudo sysctl -w kernel.perf_event_paranoid=1

如果设成1还不行,可能是Secure Boot或内核配置禁用了perf_event。这种情况需要检查内核启动参数,确认没有加perf_event_paranoid=2之类的hardcode,也没有在/sys/module/目录下禁用相关模块。

5.2 符号全是十六进制地址

perf report里看到大量地址而不是函数名,基本可以判断是符号表缺失。常见原因有三个:

  • 程序编译时没加-g参数,或者strip过
  • 动态库版本更新过,采样时的库和当前解析时的库对不上
  • 内核自身的kallsyms符号表被压缩或关闭了

对于用户态程序,解决方法是找一份带符号的二进制,用perf report --symfs /path/to/symbols指定符号路径,或者干脆在编译机上重新执行record和report。对内核,确认CONFIG_KALLSYMS已开启,并且/proc/kallsyms有读取权限。

5.3 采样频率太高导致系统卡顿

perf的观测者效应是真实存在的。采样频率过高,比如-F 9999,系统的整体性能会被明显拖慢,采出来的数据也会失真。我的经验是:

  • 常规CPU热点分析:99Hz足够
  • 短时间高精度追踪:999Hz,最多持续十几秒
  • 事件频率本身很高的场景(cache-misses、tracepoint),优先用-c指定采样周期而不是-F指定频率

另外,perf record时加上--all-user--all-kernel可以只采样用户态或只采样内核态,能减少一半的采样开销。

5.4 常见问题速查表

现象可能原因排查/解决办法
perf stat全0或Permission deniedperf_event_paranoid限制sysctl设置paranoid为1或0
report显示地址无函数名符号表缺失/strip加-g编译,或--symfs指定符号
调用栈只有一层编译器优化内联编译加-fno-omit-frame-pointer,或采样用--call-graph dwarf
record后perf.data太大采样频率过高/无事件过滤降低-F,或按事件过滤
热点函数不是业务代码锁竞争/CPU迁移/内核开销看sched事件,用perf sched分析
KVM场景guest数据空白内核KVM perf支持未开启检查CONFIG_KVM_TRACE等选项

5.5 一个完整的排查路径参考

假设你处理的问题是“Java服务CPU飙升到300%”,我的排查路径大致是:

# 找到线程PID top -Hp <pid> # 看CPU热点,确认是JIT编译后的代码还是JVM内部 sudo perf top -p <pid> -F 999 # 采样带调用链,抓到JIT符号 sudo perf record -p <pid> -g --call-graph dwarf -- sleep 30 # 生成report,看热点是GC线程还是业务线程 sudo perf report

Java场景有个额外的问题:JIT即时编译出来的代码会产生动态符号,perf如果不配合-XX:+PreserveFramePointer和perf map文件,抓到的大概率是[unknown]。这属于perf的一个特殊应用方向,不过原理和普通程序完全一致,只是要额外处理JVM的符号映射。说白了,perf的核心能力不挑语言,挑的是你能不能把符号表准备好。

6. 最后的实操心得

perf这套工具,要说难用,它确实有学习曲线,命令多、参数多、输出信息密;要说好用,一旦熟悉了,几乎所有性能问题都能快速定位到“该看哪里”。

我个人最常组合使用的一条命令是:

sudo perf record -a -g -F 99 --call-graph dwarf -- sleep 60

然后根据report结果,决定下一步用stat看宏观指标,还是用annotate深入指令级,或者用sched查延迟。这套流程我用了很久,从简单Web服务到性能敏感的中间件都验证过,稳定可靠。

最后分享两个小技巧。第一,perf的采样数据perf.data可以离线分析,完全可以在测试环境跑压测,把perf.data和带符号的二进制一起拷到本地分析,不影响线上。第二,养成“先看宏观再深入”的习惯,别一上来就开-g,很多新手被调用链淹没后反而下不了手。先从perf stat全局看一遍,再perf top找热点函数,最后record抓调用链,从粗到细,问题很快就能水落石出。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 19:32:51

Sonoma下CocoaPods安装失败?用rbenv管理Ruby环境一劳永逸

1. Sonoma下安装CocoaPods为什么总是翻车1.1 系统自带Ruby的那个"坑"2024年把Mac升级到Sonoma之后&#xff0c;很多iOS开发者做的第一件事就是打开终端&#xff0c;敲下那句看了无数遍的命令&#xff1a;gem install cocoapods然后下一秒就被红色报错糊了一脸&#x…

作者头像 李华
网站建设 2026/9/16 19:31:24

VSCode 背景图设置全攻略:插件、自定义 CSS 与直接改文件的三种方案

说实话&#xff0c;VSCode 已经是我每天打开时间最长的软件&#xff0c;没有之一。但你再喜欢一个编辑器&#xff0c;盯着同一块默认的灰蓝色界面看久了&#xff0c;也会觉得少了点什么。那段时间我把主题、字体、文件图标都折腾了一遍&#xff0c;接下来自然就盯上了背景图。很…

作者头像 李华
网站建设 2026/9/16 19:30:11

温控系统稳定性实战:传感器选型与PID整定全解析

做温控做久了&#xff0c;你会发现一个特别扎心的规律&#xff1a;把温度升上去从来不是难事&#xff0c;难的是让温度在设定值附近老老实实待着。我手里这套PTMP4718配合R7KA8D2KFLCAC的方案&#xff0c;当初就是为了解决“待着”这两个字折腾了快两周。PTMP4718作为温度采集探…

作者头像 李华