2. perf工具入门

perf 是 Linux 内核官方提供的性能分析工具集,基于 perf_event_open 系统调用和内核事件子系统实现。它能够以极低的开销采集 CPU 周期、缓存命中率、分支预测、软硬件事件以及 tracepoint 数据,是系统级性能分析的起点。本章将带你完成 perf 的安装、事件枚举和基础统计操作。

2.1 perf 的安装与验证

perf 通常随内核源码一同发布,但需要单独安装用户态工具。不同发行版的安装命令如下:

发行版 安装命令 说明
Ubuntu / Debian sudo apt install linux-tools-common linux-tools-$(uname -r) 需匹配当前内核版本
RHEL / CentOS / Fedora sudo yum install perf 或 sudo dnf install perf EPEL 仓库可能需额外启用
openSUSE sudo zypper install perf 默认包含在 kernel-source 中
Arch Linux sudo pacman -S perf 来自 linux-tools 组

安装完成后,验证 perf 是否可用:

perf --version
# 输出示例:perf version 6.8.0
提示:如果提示 WARNING: perf not found for kernel x.x.x,通常是因为缺少与当前内核版本完全匹配的 linux-tools-$(uname -r) 包。可尝试安装 linux-tools-generic 或从源码编译。

2.2 perf list:查看所有可用事件

perf list 是探索 perf 能力的第一道门。它列出当前系统支持的所有性能事件,包括硬件事件、软件事件、tracepoint 事件和动态 PMU 事件。

基本用法:

perf list
# 输出按类别分组,例如:
# List of pre-defined events (to be used in -e):
#   cpu-cycles OR cycles                               [Hardware event]
#   instructions                                       [Hardware event]
#   cache-references                                   [Hardware event]
#   cache-misses                                       [Hardware event]
#   context-switches OR cs                             [Software event]
#   page-faults OR faults                              [Software event]
#   sched:sched_switch                                 [Tracepoint event]
#   block:block_rq_complete                            [Tracepoint event]

常用过滤选项:

命令 作用
perf list hw 仅列出硬件事件
perf list sw 仅列出软件事件
perf list cache 仅列出缓存相关事件
perf list tracepoint 仅列出 tracepoint 事件
perf list sched: 列出所有调度相关的 tracepoint
perf list | grep -i "cache" 搜索包含 "cache" 的事件
关键概念:硬件事件由 CPU 性能监控单元(PMU)直接计数,精度高、开销极低;软件事件由内核计数器模拟,适用于上下文切换、缺页等场景;tracepoint 事件是内核静态插桩点,可捕获函数调用栈和参数。

下面这张 SVG 图展示了 perf 事件体系的层级结构:

perf 事件体系结构 perf 事件 硬件事件 (HW) 软件事件 (SW) Tracepoint cycles instructions context-switches page-faults sched:* 说明: 硬件事件由 CPU PMU 直接计数,开销最低;软件事件由内核模拟; Tracepoint 是内核静态插桩,可捕获上下文信息,适合细粒度分析。

2.3 perf stat:统计性能计数器

perf stat 是 perf 最基础的计数工具。它启动一个目标程序(或附加到已运行进程),在程序执行期间收集指定事件的计数器值,并在程序退出时汇总输出。

基本语法:

perf stat [options] command [args...]

典型示例——统计 ls 命令的 CPU 周期和指令数:

perf stat ls
# 输出示例:
#  Performance counter stats for 'ls':
#
#          1.234567      task-clock (msec)         #    0.123 CPUs utilized
#                12      context-switches          #    0.010 M/sec
#                 0      cpu-migrations            #    0.000 K/sec
#                45      page-faults               #    0.036 M/sec
#         3,456,789      cycles                    #    2.800 GHz
#         2,100,000      instructions              #    0.61  insn per cycle
#           450,000      branches                  #  364.567 M/sec
#            12,345      branch-misses             #    2.74% of all branches
#
#       0.010234567 seconds time elapsed

常用选项:

选项 作用 示例
-e event1,event2 指定要统计的事件列表 perf stat -e cycles,instructions ls
-p PID 附加到指定进程 ID perf stat -p 1234 sleep 5
-a 统计整个系统的计数器 perf stat -a sleep 1
-r N 重复运行 N 次并取平均值 perf stat -r 5 ./test
-I 1000 每 1000ms 打印一次计数 perf stat -I 1000 -e cycles sleep 5
--detailed 输出更详细的计数器信息 perf stat --detailed ls
注意:使用 -a 全系统模式需要 root 权限或 perf_event_paranoid 设置为较低值。可通过 sudo perf stat -a sleep 1 执行。如果遇到 Access to performance monitoring and observability operations is limited 错误,请检查 /proc/sys/kernel/perf_event_paranoid 的值。

2.4 实战:使用 perf stat 分析程序瓶颈

下面通过一个具体案例演示如何用 perf stat 定位性能瓶颈。假设我们有一个计算密集型的 C 程序 matrix_mult:

// matrix_mult.c
#include <stdio.h>
#define N 1024
double a[N][N], b[N][N], c[N][N];

int main() {
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            for (int k = 0; k < N; k++)
                c[i][j] += a[i][k] * b[k][j];
    return 0;
}

编译并运行 perf stat:

gcc -O2 -o matrix_mult matrix_mult.c
perf stat -e cycles,instructions,cache-misses,cache-references,branch-misses ./matrix_mult

输出解读:

指标 值 分析
cycles 12,345,678,900 总 CPU 周期数
instructions 8,765,432,100 总指令数
IPC 0.71 每周期指令数偏低,可能存在访存停顿
cache-misses 450,000,000 缓存未命中次数高,内存访问是瓶颈
cache-references 1,200,000,000 缓存引用总数
cache-miss rate 37.5% 缓存未命中率过高,需优化数据局部性
分析结论:该程序的 IPC 仅为 0.71,且缓存未命中率高达 37.5%,说明 CPU 大量时间在等待内存数据。优化方向应为循环分块(tiling)或使用缓存友好的矩阵乘法算法(如 OpenBLAS)。

下面这张 SVG 图展示了 perf stat 的工作流程:

perf stat 工作流程 1. 解析事件列表 2. 创建 perf_event 3. 执行目标程序 4. 内核计数累加 5. 程序退出读取 6. 格式化输出 关键点: perf stat 通过 perf_event_open 系统调用创建事件文件描述符,内核在程序执行期间原子累加计数器, 程序退出后通过 read() 获取最终值并计算衍生指标(如 IPC、缓存未命中率)。

2.5 常见问题与排查

  • perf_event_paranoid 限制:如果遇到 perf_event_open 权限错误,检查 /proc/sys/kernel/perf_event_paranoid。值为 2 时禁止非 root 用户使用硬件事件;值为 1 时允许非 root 用户使用软件事件;值为 0 或 -1 时完全开放。
  • 虚拟化环境:在虚拟机中,硬件 PMU 可能未透传,导致硬件事件计数为 0 或不可用。此时应优先使用软件事件和 tracepoint。
  • 事件别名:perf 支持事件别名,如 cycles 等价于 cpu-cycles,cs 等价于 context-switches。使用 perf list 可查看所有别名。
  • 多事件统计:perf stat 默认同时统计多个事件,但硬件事件受限于 PMU 计数器数量(通常 4~8 个)。超出时内核会进行事件复用(multiplexing),输出中会显示 not counted 或复用比例。
最佳实践:首次分析一个程序时,先使用 perf stat -d 获取默认的详细计数器集合(包括缓存和 TLB 事件),快速定位瓶颈方向,再使用 -e 精确指定事件进行深入分析。