perf 使用方式详细说明

perf 是 Linux 系统下一款非常强大的性能分析工具。它基于内核的性能计数器子系统(Performance Counters for Linux, PCL),能够对 CPU 硬件事件、内核事件、软件事件等进行采样和分析,从而帮助开发者和系统管理员深入了解程序的性能瓶颈和系统行为。由于其功能强大且直接与内核交互,perf 通常需要 root 权限或特定的 CAP_PERFMON (或旧版内核的 CAP_SYS_ADMIN) 能力来执行。

1. perf 的核心概念

理解 perf 的一些核心概念有助于更好地使用它:

  • 性能事件 (Performance Events)perf 监控的基本单位。这些事件可以是:
    • 硬件事件 (Hardware Events):由 CPU 的性能监控单元 (PMU, Performance Monitoring Unit) 直接提供。例如:CPU 周期 (cpu-cycles)、指令数 (instructions)、缓存命中/未命中 (cache-references, cache-misses)、分支预测失败 (branch-misses) 等。
    • 软件事件 (Software Events):由内核维护的计数器。例如:上下文切换 (context-switches)、CPU 迁移 (cpu-migrations)、缺页中断 (page-faults) 等。
    • Tracepoints (跟踪点):内核代码中预定义的静态探测点,用于在特定代码路径执行时触发。例如:系统调用、调度事件、磁盘 I/O 事件等。
    • Kprobes (内核探针):动态插入到内核几乎任意指令地址的探测点,非常灵活。
    • Uprobes (用户空间探针):动态插入到用户空间程序任意指令地址的探测点。
  • 采样 (Sampling)perf 以一定的频率(例如每秒 N 次)或每当某个事件发生 N 次时,记录下当前程序的执行状态(如程序计数器 PC 值、调用栈等)。通过统计这些样本,可以分析出哪些代码区域是热点。
  • 计数 (Counting)perf 统计在一段时间内某个或某些事件发生的总次数。

2. perf 的安装

perf 通常作为 linux-tools-common 和特定内核版本的 linux-tools-<kernel-version> 包的一部分。

在基于 Debian/Ubuntu 的系统上:

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

在基于 RHEL/CentOS/Fedora 的系统上:

sudo yum install perf  # 或 dnf install perf

如果找不到匹配内核版本的 linux-tools,可能需要从源码编译或寻找其他仓库。

3. perf 的常用子命令

perf 命令的结构通常是 perf <subcommand> [options]

3.1. perf list :列出可用事件

此命令用于显示当前系统支持的所有可测量的性能事件。

perf list

输出会非常多,可以配合 grep 进行筛选:

perf list | grep cache  # 查看与缓存相关的事件
perf list | grep sched  # 查看与调度相关的事件
perf list tracepoint   # 查看所有跟踪点

3.2. perf stat :收集程序或系统的整体性能统计

perf stat 用于运行一个命令并收集其执行期间的性能统计数据,或者附加到一个已运行的进程,或者进行系统范围的统计。

基本用法:

perf stat <command>
# 例如:
perf stat ls -l /

执行完毕后,perf stat 会输出一系列默认事件的统计信息,如任务时钟、上下文切换、CPU 迁移、缺页中断、CPU 周期、指令数、分支数和分支预测失败次数等。

常用选项:

  • -e <event1>,<event2>,...:指定要统计的事件。perf stat -e cycles,instructions,cache-misses,branch-misses gcc my_program.c
  • -p <pid>:附加到已运行的进程。perf stat -p 1234 # 统计 PID 为 1234 的进程
  • -a:系统范围统计 (system-wide)。perf stat -a sleep 5 # 统计整个系统在 5 秒内的性能数据
  • -I <ms>:按指定的时间间隔(毫秒)输出统计信息。perf stat -I 1000 -p 1234 # 每秒输出 PID 1234 的统计信息
  • -r <N>:重复运行命令 N 次并输出平均值和标准差。perf stat -r 5 ls -l /
  • -d:详细模式,会显示更多事件,如 L1/L2/LLC 缓存相关的硬件事件。
  • --per-socket, --per-core, --per-thread:按 CPU 插槽、核心或线程聚合统计信息。

示例:

统计 my_app 运行时的 CPU 周期、指令数和 L1 数据缓存未命中次数:

perf stat -e cycles,instructions,L1-dcache-load-misses ./my_app

3.3. perf top :实时显示系统/进程的性能热点

perf top 类似于 Linux 的 top 命令,但它显示的是函数级别的 CPU 使用率(或其他事件的开销)。它会实时采样并更新显示。

基本用法:

sudo perf top

默认情况下,它会显示 CPU 周期消耗最多的函数。

常用选项:

  • -e <event>:指定关注的事件。sudo perf top -e cache-misses # 查看缓存未命中次数最多的函数
  • -p <pid>:只分析指定 PID 的进程。
  • -t <tid>:只分析指定 TID 的线程。
  • -K:隐藏内核符号。
  • -U:隐藏用户空间符号。
  • -F <freq>:设置采样频率(每秒多少次)。sudo perf top -F 99 # 每秒采样 99 次
  • -c <count>:每发生 count 次事件采样一次。
  • -g--call-graph:显示调用图(需要编译时支持,如 DWARF 或 frame pointers)。sudo perf top -g sudo perf top --call-graph fp # 使用 frame pointer sudo perf top --call-graph dwarf # 使用 DWARF (更精确,但开销大)
  • -s <sort_keys>:指定排序键,如 overhead, overhead_sys, overhead_us, symbol 等。
  • --stdio:将输出重定向到标准输出,而不是使用 TUI 界面。

perf top 的交互界面中,可以按 a (annotate) 查看选定函数的汇编指令级注释,d (filter out) 过滤掉选定符号等。

3.4. perf record :记录性能数据供后续分析

perf recordperf 中最核心的命令之一。它对指定的事件进行采样,并将采样数据保存到一个名为 perf.data 的文件中(可以使用 -o 指定其他文件名),之后可以用 perf reportperf script 等命令进行分析。

基本用法:

sudo perf record <command>
# 例如:
sudo perf record ./my_program

这会以默认频率(通常是根据 CPU 速度动态调整的)对 my_program 执行期间的 cycles 事件进行采样。

常用选项:

  • -e <event>:指定要记录的事件。sudo perf record -e cycles ./my_program sudo perf record -e cpu-clock -e task-clock ./my_program
  • -F <freq>:设置采样频率(每秒 freq 次)。这对于大多数 CPU 热点分析是推荐的方式。sudo perf record -F 99 ./my_program # 每秒采样 99 次 (99 是一个常用的值,避免与某些系统中断频率重合)
  • -c <count>:设置采样周期,即每发生 count 次事件采样一次。sudo perf record -e instructions -c 1000000 ./my_program # 每 1,000,000 条指令采样一次
  • -g:记录调用图 (call graph)。这对于理解函数调用关系和热点路径至关重要。sudo perf record -F 99 -g ./my_program
    可以进一步指定调用图的收集方式:--call-graph fp (frame pointer,较快,但需要程序编译时保留帧指针,通常 -fno-omit-frame-pointer 选项),--call-graph dwarf (使用 DWARF 调试信息,较慢但通常更准确,需要程序编译时带调试信息 -g),或 lbr (Last Branch Record,某些 CPU 支持)。
  • -p <pid>:记录指定 PID 进程的事件。sudo perf record -F 99 -g -p 1234
  • -t <tid>:记录指定 TID 线程的事件。
  • -a:系统范围记录。sudo perf record -F 99 -ag -- sleep 10 # 系统范围采样 10 秒
  • -o <filename>:指定输出文件名,默认为 perf.data
  • --:用于分隔 perf record 选项和要执行的命令,特别是当命令本身也带有选项时。sudo perf record -F 99 -g -- ./my_program --arg1 --arg2
  • -k <clockid>:指定使用的时钟源,如 monotonic, realtime
  • --user-regs, --intr-regs:记录用户或中断寄存器的值。

示例:以 99Hz 频率对 my_app 进行采样,并记录调用图:

sudo perf record -F 99 -g -- ./my_app

执行完毕后,会在当前目录生成 perf.data 文件。

3.5. perf report :分析 perf.data 文件

perf report 读取由 perf record 生成的 perf.data 文件,并以交互式的方式展示分析结果,显示哪些函数(符号)消耗了最多的采样事件。

基本用法:

sudo perf report

如果当前目录有 perf.data 文件,它会自动加载。

常用选项:

  • -i <filename>:指定输入的 perf.data 文件。
  • -g <type>,<min_percent>:配置调用图的显示。例如 -g graph,0.5 表示以图形方式显示调用图,并且只显示占比超过 0.5% 的路径。type 可以是 graph (百分比从调用者到被调用者), flat (按符号自身开销排序), fractal (类似 graph 但更紧凑), none
  • --stdio:将报告输出到标准输出,而不是 TUI。这对于脚本处理或重定向非常有用。
  • -s <sort_key>:指定排序键,如 comm (命令名), pid, symbol, dso (动态共享对象,即库或可执行文件)。
  • --symbol-filter <filter>:只显示匹配特定名称的符号。
  • --dsos <dso_list>:只显示来自特定 DSO 的符号。
  • -n, --show-nr-samples:显示每个符号的原始样本数量。
  • --percent-limit <float>:忽略开销低于此百分比的条目。

交互界面操作:

  • 上下箭头:移动光标。
  • Enter:展开/折叠调用图(如果可用)。
  • a:Annotate symbol,显示选定函数的汇编代码及各指令的开销。
  • d:Filter out symbol/DSO,从当前视图中排除。
  • s:Zoom into DSO,只显示选定 DSO 中的符号。
  • /:搜索符号。
  • H:显示帮助。
  • q:退出。

3.6. perf script :将 perf.data 转换为可读文本

perf scriptperf.data 文件中的原始跟踪数据转储为人类可读的文本格式或供其他脚本处理的格式。

基本用法:

sudo perf script

输出内容非常详细,包含了每个样本的时间戳、PID、TID、CPU、事件类型、指令指针、符号和调用栈(如果记录了)。

常用选项:

  • -i <filename>:指定输入的 perf.data 文件。
  • -F <fields>:指定要输出的字段,如 comm,pid,tid,cpu,time,event,ip,sym,dso,callchainsudo perf script -F comm,pid,sym,ip
  • -D:转储原始跟踪数据(十六进制)。
  • --header:显示文件头信息。
  • --fields:显示所有可用的字段名。
  • --no-inline:不展开内联函数。

perf script 的输出是生成火焰图 (Flame Graphs) 的重要输入。火焰图是一种可视化 CPU 时间消耗的强大工具。

生成火焰图的典型流程(需要 Brendan Gregg 的 FlameGraph 工具集):

# 1. 记录数据 (通常需要调用图和高频采样)
sudo perf record -F 99 -ag -- sleep 60 # 采样整个系统 60 秒

# 2. 生成折叠后的调用栈
sudo perf script | /path/to/FlameGraph/stackcollapse-perf.pl > out.folded

# 3. 生成 SVG 火焰图
/path/to/FlameGraph/flamegraph.pl out.folded > kernel.svg

3.7. perf annotate :注解源码或汇编

perf annotate 读取 perf.data 文件,并用性能数据(如每个指令的样本数)来注解函数的源代码或汇编代码。这需要调试符号。

基本用法:

sudo perf annotate -s <symbol_name>
# 例如:
sudo perf annotate -s main # 注解 main 函数

如果程序编译时带有 -g 选项,并且调试信息可用,perf annotate 可以将开销直接关联到源代码行。

常用选项:

  • -i <filename>:指定输入的 perf.data 文件。
  • -d <dso_name>:指定 DSO (可执行文件或库)。
  • -l:显示行号。
  • -k <vmlinux_path>:指定内核符号文件路径(用于注解内核函数)。

3.8. perf trace :类似 strace 的系统调用跟踪

perf trace 可以跟踪系统调用,类似于 strace,但它利用 perf 的跟踪点机制,开销通常更低。

基本用法:

sudo perf trace <command>
# 例如:
sudo perf trace ls -l

常用选项:

  • -p <pid>:跟踪指定进程。
  • -e <syscall_name>:只跟踪指定的系统调用。sudo perf trace -e open,read,write ./my_app
  • -s:显示系统调用摘要统计。
  • -o <file>:输出到文件。
  • --duration <seconds>:跟踪指定时长。
  • --no-syscalls:不跟踪系统调用,可以用于跟踪其他 tracepoint 事件。

3.9. perf sched :分析调度器行为和延迟

perf sched 用于分析内核调度器的行为,包括上下文切换、调度延迟等。

子命令:

  • perf sched record <command>:记录调度事件。sudo perf sched record -- sleep 10
  • perf sched latency:报告调度延迟统计。sudo perf sched latency --sort max # 按最大延迟排序
  • perf sched map:显示调度事件的文本映射。
  • perf sched script:将调度事件转储为文本。
  • perf sched replay:重放调度事件(用于调试)。

3.10. perf mem :分析内存访问

perf mem 用于记录和分析内存访问(加载和存储)事件,需要 CPU 支持精确事件(如 PEBS on Intel)。

子命令:

  • perf mem record <command>:记录内存访问事件。sudo perf mem record -e mem-loads,mem-stores -- ./my_app # 或者更精确的: # sudo perf mem record -t load,store -- ./my_app (取决于CPU支持)
  • perf mem report:分析记录的内存访问数据。sudo perf mem report -i perf.data --stdio

可以帮助识别数据局部性问题、伪共享等。

3.11. perf kmem :分析内核内存分配

perf kmem 用于跟踪内核内存分配和释放事件 (如 kmalloc, kfree)。

子命令:

  • perf kmem record <command>:记录内核内存事件。
  • perf kmem stat:显示内核内存分配统计。sudo perf kmem stat --alloc --caller # 显示分配者和调用者

3.12. perf lock :分析锁竞争

perf lock 用于分析内核锁的争用情况。

子命令:

  • perf lock record <command>:记录锁事件。
  • perf lock report:报告锁争用统计。
  • perf lock script:转储锁事件。

3.13. perf probe :动态定义跟踪点

perf probe 允许用户在内核 (kprobe) 或用户空间程序 (uprobe) 的几乎任何位置动态地定义新的跟踪点,而无需重新编译内核或程序。

定义 kprobe:

# 在 vfs_read 函数入口处设置一个探针
sudo perf probe -a vfs_read
# 在 vfs_read 函数返回时设置一个探针,并记录返回值
sudo perf probe 'p:myprobe vfs_read%return +0($retval)'

定义 uprobe (需要程序的调试符号):

# 在 my_app 程序的 my_function 函数入口处设置探针
sudo perf probe -x /path/to/my_app my_function

定义后,这些探针就可以像其他 tracepoint 一样被 perf recordperf trace 使用。

4. perf 的主要使用场景

4.1. CPU 热点分析

这是最常见的场景,找出消耗 CPU 时间最多的函数或代码路径。

  • 快速查看sudo perf top -g
  • 详细分析
    1. sudo perf record -F 99 -g -- ./my_application (或 -ag 进行系统范围分析)
    2. sudo perf report -g graph,0.5 (交互式查看) 或 sudo perf script | stackcollapse-perf.pl | flamegraph.pl > app.svg (生成火焰图)
    3. sudo perf annotate -s hot_function (深入到汇编/源码级别)

4.2. 缓存未命中分析

分析程序因缓存未命中导致的性能下降。

  1. perf list | grep -i cache (查找可用的缓存事件,如 cache-misses, L1-dcache-load-misses, LLC-load-misses)
  2. sudo perf stat -e cycles,instructions,cache-misses,L1-dcache-load-misses ./my_app (获取总体统计)
  3. sudo perf record -e cache-misses -c 10000 -g -- ./my_app (记录导致缓存未命中的代码位置,-c 的值需要根据情况调整)
  4. sudo perf report

4.3. 分支预测错误分析

分支预测错误会严重影响流水线效率。

  1. perf list | grep -i branch (查找分支事件,如 branch-misses, branch-instructions)
  2. sudo perf stat -e branch-instructions,branch-misses ./my_app (计算分支预测错误率)
  3. sudo perf record -e branch-misses -g -- ./my_app (定位导致分支预测错误的热点代码)
  4. sudo perf report

4.4. 锁竞争分析

找出多线程程序中竞争激烈的锁。

  1. sudo perf lock record -- ./my_multithreaded_app
  2. sudo perf lock report

4.5. 系统调用分析

了解程序与内核的交互模式,或者排查与系统调用相关的性能问题。

  • sudo perf trace ./my_app (类似 strace)
  • sudo perf trace -p <PID> -s (统计运行中进程的系统调用)
  • sudo perf record -e 'syscalls:sys_enter_*' -ag -- sleep 10 (记录所有系统调用入口事件)
  • sudo perf report

4.6. 内存带宽分析

需要 CPU 支持特定事件 (如 Intel 的 offcore_response)。

  1. sudo perf record -e offcore_response.all_reads.l3_miss.local_dram -c 10000 -g -- ./my_app (示例,具体事件名依赖 CPU 型号)
  2. sudo perf report

4.7. 调度延迟分析

分析线程等待 CPU 的时间。

  1. sudo perf sched record -- sleep 10 (记录系统调度事件)
  2. sudo perf sched latency --sort max (查看最大调度延迟)

4.8. 内核函数性能分析

当怀疑性能瓶颈在内核态时。

  1. sudo perf top -K (实时查看内核热点函数,隐藏用户态)
  2. sudo perf record -F 99 -g -a -- sleep 10 (系统范围采样,包含内核调用栈)
  3. sudo perf report (此时需要确保内核符号表 /proc/kallsyms 可读,并且最好有未压缩的内核镜像 vmlinux 以获得更准确的符号解析)

4.9. 分析特定进程/线程

  • perf stat -p <PID> -d
  • perf top -p <PID>
  • perf record -p <PID> -g

4.10. 监控特定硬件事件

如 TLB miss, stalled cycles front/back end 等,用于非常底层的性能调优。

sudo perf stat -e dTLB-load-misses,iTLB-load-misses ./my_app
sudo perf record -e cycles_t -e stalled-cycles-frontend -e stalled-cycles-backend -g -- ./my_app
sudo perf report

(事件名 cycles_t 表示线程私有的 cycles,stalled-cycles-* 等事件名可能因 CPU 架构而异)

5. 重要提示

  • 权限:大多数 perf 命令需要 root 权限或 CAP_PERFMON 能力。可以通过设置 /proc/sys/kernel/perf_event_paranoid 来调整权限需求(例如,设为 -1 允许非特权用户使用,但不推荐用于生产环境)。
  • 符号表:为了得到有意义的函数名而不是地址,你需要:
    • 用户程序:编译时带上调试信息 (-g),并且不要 strip掉符号表。
    • 内核:确保 /proc/kallsyms 可读。对于更详细的内核符号解析和注解,可能需要与内核版本匹配的 vmlinux 文件(通常在内核调试包中,如 kernel-debuginfo)。
    • 库文件:确保安装了对应库的调试信息包 (e.g., libc6-dbg, libstdc++6-dbg)。
  • 采样频率与开销
    • -F <freq> (频率采样) 通常比 -c <count> (周期采样) 更容易控制开销,因为它会尝试均匀分布采样。
    • 过高的采样频率会带来显著的系统开销,甚至影响被测程序的行为(观察者效应)。99Hz999Hz 是常用的起始值。
    • 对于长时间运行的系统级监控,频率不宜过高。
  • 调用图 (-g)
    • --call-graph fp (frame pointer):开销较低,但如果程序优化时省略了帧指针 (GCC 的 -fomit-frame-pointer 是很多发行版的默认选项),则调用栈可能不完整或不准确。编译时可使用 -fno-omit-frame-pointer
    • --call-graph dwarf (DWARF unwinding):基于调试信息,通常更准确,但开销较大,可能导致采样丢失或性能数据失真。需要程序编译时带 -g
    • --call-graph lbr (Last Branch Record):某些 CPU 支持,可以精确记录分支历史,对调用栈回溯有帮助,开销介于两者之间。
  • perf.data 文件大小:高频采样、长时间记录或记录大量事件(尤其是带调用栈)会产生非常大的 perf.data 文件。确保有足够的磁盘空间。
  • 内核版本兼容性perf 工具与内核版本紧密相关。最好使用与当前运行内核匹配的 perf 版本。
  • 解释数据
    • Overhead 列显示的是某个符号(函数)占总样本数的百分比。
    • Children 表示该函数调用的其他函数所占的开销。
    • Self 表示该函数自身(不包括其调用的其他函数)所占的开销。
    • IPC (Instructions Per Cycle):instructions / cycles。高 IPC 通常表示 CPU 执行效率高。低 IPC 可能表示内存瓶颈、分支预测失败等。
  • 从简单到复杂
    1. 先用 perf stat 对程序或系统进行整体评估。
    2. perf top 快速定位热点。
    3. perf record 收集详细数据。
    4. perf reportperf script (及火焰图) 进行深入分析。
    5. perf annotate 查看具体代码。

本文版权归原作者zhaofujian所有,采用 CC BY-NC-ND 4.0 协议进行许可,转载请注明出处。

发表评论