ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

perf:Linux性能分析必备工具,从采样原理到实战排查

perf:Linux性能分析必备工具,从采样原理到实战排查 简介perf性能测试工具包是Linux系统级性能分析利器面向系统程序员、运维工程师和性能优化人员用于定位CPU热点、缓存失效、调用链瓶颈等性能问题。包内共8个文件包含perf可执行程序、动态链接库和自动化测试脚本libbfd、libelf、libunwind等库文件为perf提供符号解析与栈回溯支持脚本可辅助重复性性能采集与批量分析。压缩包大小9.08MB轻量易部署已有2519人学习下载。借助该工具包可执行perf record/report采样、perf stat事件统计、缓存命中率分析、调用图构建等操作结合源码级标注定位热点配套脚本与库文件降低了环境配置门槛便于在目标机器上快速开展测试适合需要深入分析应用执行效率的开发者。 在 Linux 上做性能分析我第一个打开的工具永远是 perf。不是因为它功能最全而是因为它是内核自带的工具几乎在所有发行版上都能直接装上不需要往生产环境里塞一堆额外的 agent。这篇文章就围绕 perf 性能测试工具把我平时排查 CPU、锁竞争、系统调用开销的完整思路和实操过程整理出来希望能帮到正在被“CPU 飙高但不知道是谁干的”这类问题折磨的朋友。perf 能干什么一句话概括它能告诉你 CPU 到底在忙什么内核在忙什么进程在忙什么以及它们各自花了多少时间。适合谁用后端开发、SRE、运维、嵌入式 Linux 工程师只要你需要摸清一台 Linux 机器的性能底细perf 就是那块最趁手的敲门砖。1. 先搞明白 perf 到底在干嘛1.1 内核事件子系统perf 的底层地基perf 不是凭空发明的一个孤立工具它脚跟底下踩着的是内核的 performance events 子系统也叫 perf_event。从内核 2.6.31 开始这个子系统就合入了主线它的作用是把硬件性能计数器、软件事件、内核 tracepoint 统一抽象成“事件”这个概念然后 perf 命令只是这套事件接口的前端交互层。硬件事件走的是 CPU 自带的 PMUPerformance Monitoring Unit性能监控单元。现代 CPU 内部有一组专用的计数器可以在不打断程序执行的情况下统计发生了多少次 cache miss、多少次分支预测失败、多少个周期在执行指令。软件事件则是内核自己维护的计数比如上下文切换次数、CPU 迁移次数、page fault 次数。tracepoint 是内核里的静态插桩点会在特定代码路径上触发回调比如系统调用进出、磁盘 IO 完成、调度器切换进程。所以 perf 的数据来源是内核 硬件不是像 gprof 那样靠编译器插桩也不是像 strace 那样靠 ptrace 阻塞目标进程。这就带来了两个重要特性一是性能开销极小采样模式下对目标程序的影响通常可以忽略二是它能看到的不只是用户态代码内核态的系统调用、中断处理、调度行为全部一览无余。很多性能问题恰恰是藏在系统调用和内核路径里的这类问题用别的手段很难定位perf 一眼就能看穿。1.2 采样不是精确测量理解这点很重要perf 最常用的工作模式是采样sampling理解它的原理才不会误读数据。采样就是每隔一个固定周期打断 CPU 一次记录下此刻正在执行的指令地址和调用栈。它像是一个按时打卡的摄像头每隔 1 毫秒拍一张照最后统计每张照片里谁出现的次数最多。这里有一个关键的概念CPU 周期cycle是硬件时钟不是墙钟时间。同一个进程在不同 CPU 频率下跑消耗的 cycles 数会和墙钟时间不一样。所以如果你要比较两个不同机器的性能看 instructions指令数比看 cycles 更靠谱如果你要定位热点看 samples 占比比看绝对值更有意义。采样天然是统计性的存在一定的误差。比如一个函数只占了 0.1% 的 CPU 时间采样 1000 次可能只拍到 1 次它在报告里的占比就不够稳定。所以在用 perf record 抓数据的时候采样时长和采样频率要匹配。一般我建议抓 30 秒以上、每秒 1000 个采样点以上也就是-F 999注意别填 1000有些内核版本会报错提示频率值不在预期范围用 999 是社区里约定俗成的稳妥写法这样报告里的占比才具有统计意义。2. 三个高频命令用熟性能分析就入门了一半2.1 perf top实时看热点像 top 一样看函数你发现系统 CPU 占用很高但 top 只告诉你哪个进程占得多没告诉你是进程里的哪个函数在烧 CPU。这个时候直接用perf top它就像 top 命令一样实时刷新只不过展示单位从“进程”换成了“函数”。perf top看到的是实时采样的结果默认按 CPU 周期排序第一列是采样占比第二列是进程名第三列是符号名。perf top默认需要 root 权限因为内核要限制非特权用户访问硬件 PMU。如果不想每次都用 root可以临时把/proc/sys/kernel/perf_event_paranoid改成 1 或者 0但这只建议在开发机上做生产环境还是老实 sudo。有个常用的参数组合sudo perf top -p 12345 -F 999-p指定进程号只采样这个进程-F指定采样频率。这样输出的就是某个进程内部的函数热点排行排查单个进程异常高时非常实用。如果你想看看某个函数底下是谁调进来的按回车展开 annotate 视图能看到每一行汇编指令的采样热度。2.2 perf stat拿整体指标先看大局再抠细节perf stat做的事情是跑一条命令或者 attach 到一个进程然后统计这段时间里各类事件的总数和耗时占比。它不告诉你“哪个函数最热”而是告诉你“系统整体表现如何”。sudo perf stat -p 12345 -- sleep 10这条命令的语义是统计进程 12345 在 10 秒内的性能事件。输出会包括 task-clock任务占用 CPU 的时间、context-switches上下文切换次数、cpu-migrationsCPU 迁移次数、page-faults缺页中断次数、cycles、instructions 等。我最常看的是两个比值IPCInstructions Per Cycle和cache-miss比例。IPC 太低说明 CPU 大量时间在等待而不是执行指令典型的可能是指令依赖导致的长流水线停顿或者是访存延迟cache miss 比例高说明数据局部性差程序在内存在来回搬运数据。定位方向完全不同——前者看算法依赖链后者看数据排布。perf stat还可以指定-e参数来采集特定事件。比如sudo perf stat -e cache-misses,cache-references,instructions,cycles -p 12345 -- sleep 10这样输出更干净只看你想看的事件。2.3 perf record perf report离线归档深挖调用栈perf top适合现场快速扫一遍但它的数据显示在终端上不方便深究。真正做深入分析靠的是perf record采集数据然后perf report离线看报告。sudo perf record -g -p 12345 -o /tmp/perf.data -- sleep 30 sudo perf report -i /tmp/perf.data-g表示采集调用栈这样 report 里才能看到“函数 A 调用了 BB 占了 40% CPU”这样的父子关系而不是只知道 B 热、不知道 B 是谁调进来的。-o指定输出文件名这个文件一般放在/tmp下因为 perf.data 默认可能有好几 MB放持久化目录里容易混进备份任务。进入 report 交互界面后默认是按 self自身采样占比排序的。self 占比高说明这个函数自己执行的代码就是热点如果 self 不高但 children子调用链累计很高那说明它是被高频调用的“老板”问题可能出在它底下的某个子函数。两个维度配合着看就能顺着调用链一路定位到最底层的真正热点。3. 怎么读懂 perf 的输出定位问题本质3.1 别只盯着 CPU 占用还要看等待和开销很多新手拿到 perf report只看 self 最高的函数觉得那一定是“罪魁祸首”。但实际排查的时候self 最高的函数往往只是表象。有一次我排查一个 Redis 兼容层进程 CPU 飙高的问题self 最高的函数是rb_next红黑树遍历看起来很像是遍历逻辑有问题。但顺着调用链往上看发现这个遍历是被一个定时器高频触发的过期键清扫任务调用的。问题根源是过期键堆积太多每次清扫都要遍历大量键而不是红黑树本身写错了。所以看 perf report 的正确姿势是先看 self 高的函数再翻 children 高的调用链把“谁在调用它”这个链条理清楚。如果热点函数可以对应到业务代码直接改这个函数所在模块的逻辑如果热点全是内核函数那就要考虑是不是系统调用太频繁、锁竞争太严重、或者是内存分配压力过大。3.2 调用栈与符号解析信息完整才能定位perf report能把调用栈显示出来但前提是程序里的符号函数名能被解析到。如果程序是 C/C 写的编译时要带-g调试信息或者至少保留.symtab段。如果是 Java、Python 这类带 JIT 的语言perf 默认看到的是 JVM 的机器码地址不是 Java 方法名需要额外的映射文件才能翻译。遇到符号显示成一堆十六进制地址0x7f8a2c001234的情况先别慌大概率是下面几个原因之一程序被 strip 过了调试信息被剥离程序运行在容器里perf 看到的是容器内路径宿主机符号文件对不上或者内核符号表没加载。核实手段是检查/proc/12345/maps里可执行文件路径和符号文件是否存在问题定位清楚再补齐。有一个小技巧抓到 perf.data 后立刻在采集数据的同一台机器、同一个程序版本上用 perf report符号解析成功率最高。换台机器或者换程序版本经常会解析不完整这是实战中很容易踩的坑。3.3 火焰图把调用栈变成直观热力图perf report 的文本界面在大调用栈下相当难读。我在定位一个深度递归导致的问题时调用链有 80 多层report 里翻起来眼睛都要瞎了。后来我把数据导出成火焰图几十层调用关系一眼就能看清哪条栈路径最宽、最深。火焰图的生成套路由三部分组成采集数据、解析堆栈、画图。采集还是用 perf record 带-g。解析堆栈用的是 Brendan Gregg 写的一套脚本先把 perf.data 转成脚本输出再折叠成按分号分隔的调用栈计数最后交给火焰图脚本生成 SVG。sudo perf record -F 999 -g -p 12345 -- sleep 30 sudo perf script -i /tmp/perf.data /tmp/perf.unfold stackcollapse-perf.pl /tmp/perf.unfold /tmp/perf.folded flamegraph.pl /tmp/perf.folded /tmp/perf.svgstackcollapse-perf.pl和flamegraph.pl都来自 FlameGraph 仓库下载后放到 PATH 里就能用。生成的 SVG 用浏览器打开X 轴是采样占比Y 轴是调用深度每个色块是一个函数色块越宽说明占 CPU 时间越多。点进去还能看这个函数完整的调用链。这个方法现在基本是我做性能分析的标准动作比直接看文本 report 快得多。4. 实战全过程一个高 CPU 问题是怎么锁定的说再多理论不如跑一遍实战。前阵子我接到一个线上告警某支付网关进程 CPU 飙到 400%多核业务方反馈接口响应变慢。我登上去后的操作顺序如下。第一步perf top -p pid扫一眼实时热点。屏幕上马上出现了一个函数叫__mcount_loc相关的符号占比超过 30%这个符号看起来异常正常业务程序不太可能集中在它身上。最可能的情况是函数被打了某种追踪插桩导致跳转开销被放大。进一步用perf annotate看这个符号的汇编指令级别热点发现每一条指令后面都紧跟一个对__fentry__的调用。__fentry__是内核提供的 ftrace 入口如果程序编译时带了-pg选项每个函数开头都会插入对它的调用。到这里原因基本清楚了要么编译参数里误加了-pg要么运行环境有工具自动对进程做了动态插桩。我看了下进程的启动参数和编译脚本果然在某个版本的 CI 构建脚本里有人为了统一加监控探针把-pg加进了 CFLAGS上线后忘了摘除。这个函数就是插桩产生的额外跳转开销占了 CPU 的 30% 以上白白浪费。第二步去掉插桩后重新抓一次数据确认。这次perf top显示热点变成了ep_poll和_raw_spin_lock占比各有 15% 左右说明程序在等事件和抢锁上花费了不少时间。我再用perf record -g抓 30 秒perf report里顺着_raw_spin_lock的调用链往上翻发现都是同一个全局配置对象的读多写少场景——业务代码每次请求都要读取这个配置对象读没有加锁保护就算了写的时候用了一把大锁导致读请求频繁被写锁阻塞。第三步修复方案是把这个全局配置改成读写锁rwlock或者更彻底一点用 RCURead-Copy Update模式读侧无锁写侧通过替换指针来更新。我选了 rwlock 先顶着线上问题回头再把配置变更改造成独立版本号 原子指针的方式。改完后 CPU 从 400% 降到了 120%接口 p99 延时就降了一半。这个案例想说明的是perf 解决的不只是“哪个函数最热”这一个层面的问题它能把一个突发的 CPU 问题拆解成是插桩开销是锁竞争还是业务逻辑本身太重。每一步排查都用 perf 的不同能力top 扫全貌、annotate 看指令级细节、recordreport 理调用关系、火焰图看全链路分布。这套组合拳打下来大部分性能问题都能定位得有鼻子有眼。5. 常见问题与排查技巧实录5.1 权限与配置导致的常见报错perf 使用中遇到最多的就是一堆权限和内核参数问题我整理了一张问题对照表基本覆盖了出现频次最高的几种情况。报错或现象常见原因快速处理perf_event_paranoid限制非 root 用户访问硬件计数器被拒root 运行或临时调小perf_event_paranoidNo permission to collect stats权限不足加 sudo或调整 paranoid 级别symbols from unknown程序被 strip、容器路径不对编译带-g从容器内采集或映射符号Function 显示为地址动态库调试信息缺失安装-dbg/-dbgsym包或设置LD_LIBRARY_PATHInvalid event name事件名拼写错误或内核版本不支持perf list查看可用事件列表采样数据量过大-F频率过高或采样时长过长降低频率或改用-F 999 -m 512M增大环形缓冲我特别想提一下perf_event_paranoid这个参数。它的默认值通常是 2含义是不允许非特权用户使用硬件 PMU只允许软件事件。有的发行版默认是 3连软件事件都不给用。调试阶段临时降到 1 就能让普通用户跑 perf但生产环境千万别图方便直接改掉安全收益和调试便利之间的取舍要清醒。5.2 采样数据的一个经典陷阱频率限制和环形缓冲区有一次我执行perf record -F 9999 -g -p 12345 -- sleep 60结果 record 结束的时候报告采样数量只有几万个远低于理论值。后来排查发现是内核的perf_event_max_sample_rate限制把频率压下来了。系统默认限制是每秒 100000 个采样事件如果超出这个值内核会直接无视你设置的频率用最大允许值替代同时打印一条警告。这时候两个选择一是接受默认限制降低-F的期望值二是调大perf_event_max_sample_rate但调大意味着采样开销变大对目标进程的影响会上升。实际项目里除非你要抓微秒级的瞬时抖动否则-F 999已经足够。另一个我踩过好几次坑的地方是环形缓冲区太小。perf record默认的 mmap 缓冲区只有 512KB频繁采样加深度调用栈会很快把缓冲区写满事件被丢弃。用参数扩大sudo perf record -F 999 -g -m 1G -p 12345 -- sleep 30-m的传参可以加单位1G 表示给每个 CPU 分配 1GB 环形缓冲区。当然这得有足够的内存才能这么干但是当你抓到的事件数量明显不对、report 里调用栈残缺不全的时候优先检查的就是缓冲区是否溢出。5.3 容器和 JIT 场景里的额外注意事项现在跑服务的环境很多是容器perf 在容器里用会遇到额外的坑。容器内的/proc是宿主机的/proc加一层挂载但符号文件、可执行文件路径经常挂在容器自己的 overlay 层里。最直接的做法是在宿主机上用 root 跑 perf利用容器进程在宿主机上可见的 PID不是容器内 PID这样符号解析路径和管理路径都能对上。在容器内跑 perf 的话需要容器具备 SYS_ADMIN 能力且内核的perf_event_paranoid设置允许容器使用 PMU大多数 Kubernetes 的默认安全配置并不满足这个条件。所以我的习惯是容器内跑 perf list 测试一下可用性不成就回宿主机采集两者效果一致。JIT 语言Java、Go、Node 等是另一个需要专门适配的场景。Go 默认编译成静态二进制带调试信息的话 perf 能直接解析函数名。Java 需要加-XX:PreserveFramePointer让 JVM 保留帧指针然后配合 perf-map-agent 生成 JIT 方法的符号映射Node 需要--perf-basic-prof参数导出 JIT 符号。这些配置要提前在程序启动参数里就设置好等出了性能问题再想加就来不及了必须重启进程才能生效。最后说两句实际体会perf 这套工具我用了大概五年从最早的perf top扫一眼到后来完整跑一次火焰图流程再到现在遇到任何线上诡异性能问题第一反应都是先摸一份 perf record 数据放到本地慢慢解析。它的学习曲线不算陡难的是建立起“看懂采样报告”的分析直觉——什么指标组合说明什么问题什么调用链形态对应什么并发模式。这种直觉只能靠一次次真实问题的排查来积累工具本身只是放大你的判断力。如果你刚开始接触 perf我的建议是先拿一台压测机器跑通perf top→perf record→perf report→ 火焰图这条完整链路再回到业务程序里找一两个已知的性能瓶颈做对照验证。这个流程走顺了后面遇到陌生问题就有了一套固定的拆解框架不会再对着高 CPU 一头雾水。本文还有配套的精品资源点击获取
返回列表