登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  linux

perf stat 组合硬件计数器分析 CPU 周期

来源:17golang原创

时间:2026-10-10 21:04:25 154浏览 收藏

分析 CPU 周期时,先用 perf stat 同时采集 cycles 和 instructions,再用周期数除以指令数得到 CPI。这样能把“程序慢”拆成可观察的计数器关系,而不是只看一次墙上时钟时间。Linux 内核文档说明,perf stat 会借助现代 CPU 中的硬件计数器统计软硬件事件。

官方文档:https://docs.kernel.org/admin-guide/workload-tracing.html

下面的示例只展示命令写法和解释方法,配图是静态说明图,不是本机运行截图,也不代表某台机器已经产生了固定数值。

先把 perf stat 的三层对象分开

这条命令里有三个容易混淆的对象:

  • 事件名:例如 cycles、instructions 和 branches,描述想数什么。
  • PMU 计数器:CPU 提供的性能监控单元,负责在硬件层累计事件。
  • perf stat:把事件配置、启动/停止计数、缩放和结果展示串起来的用户态工具。

因此,看到某个事件显示为不可用时,首先要判断是 CPU、内核、虚拟机还是权限没有提供它,而不是立刻把它解释为程序没有执行相关操作。

工作负载通过 perf stat 选择 cycles、instructions 和 branches,再由 CPU PMU 计数器统计并输出结果的结构说明图
图1:结构说明图,展示 perf stat 选择硬件事件并交给 CPU PMU 计数器统计的关系。

先用 perf list 确认当前机器能看到什么

硬件事件不是所有机器都完全相同。先列出当前 perf 能识别的事件,再决定组合方式:

# 列出当前环境可见的事件,先确认名称再写统计命令
perf list

# 只筛选周期、指令和分支相关事件,便于快速阅读清单
perf list | grep -E 'cycles|instructions|branches'

如果清单中没有预期的通用事件,可能是 CPU PMU 暴露方式不同、内核配置不同,或者运行在限制硬件计数器的虚拟机里。不要把网上另一台 CPU 的事件别名原样搬过来;以当前机器的 perf list 结果为准。

用一条命令组合 CPU 周期和指令数

对一个稳定的工作负载,可以先使用最小组合:

# 同时统计 CPU 周期与退休指令,sleep 只是一个占位工作负载
perf stat -e cycles,instructions -- ./your-program --input sample.dat

# 再加入分支事件,观察控制流相关的额外线索
perf stat -e cycles,instructions,branches,branch-misses -- ./your-program --input sample.dat

-e 后用逗号分隔事件。-- 用来明确后面的内容是被测程序及其参数,避免程序自己的参数被 perf 误解。示例中的程序名、参数和文件只是写法占位,实际采集时应替换成可以重复运行的真实工作负载。

硬件计数器数量有限时,perf 可能分时复用事件,结果会带有缩放信息;如果事件组合过多,还可能出现无法调度或某个计数器不可用。第一轮建议只保留能回答当前问题的两个到四个事件。

让每次采集可以被比较

单次运行只能说明一次运行的计数结果。要比较优化前后,至少固定输入、参数、CPU 亲和性和重复次数,并把结果保存成机器可读格式:

# 使用 CSV 输出,便于保存多次运行的计数结果
perf stat -x, -o perf-baseline.csv -e cycles,instructions -- ./your-program --input sample.dat

# 绑定到同一 CPU 集合,减少调度位置变化带来的噪声
taskset -c 2-3 perf stat -r 5 -x, -e cycles,instructions -- ./your-program --input sample.dat

-r 5 让 perf 重复运行工作负载并汇总结果,-x, 用逗号作为字段分隔符,-o 将统计输出写入文件。这里的重复运行是测量设计,不是质量保证;仍要确保工作负载本身没有随机输入、缓存预热差异或后台任务干扰。

如果程序会改变系统状态,不能简单重复执行同一个命令。可以准备只读输入、隔离输出目录,或让程序提供可重复的基准模式。

用 CPI 把两个计数器变成判断线索

得到计数后,用下面的关系做第一层判断:

# CPI 表示平均每条退休指令消耗的 CPU 周期
CPI = cycles / instructions

CPI 较高通常意味着流水线等待、缓存未命中、分支预测失误或其他微架构停顿更明显;CPI 较低并不自动等于程序更快,因为总指令数、频率、并发度和输入规模也会影响总耗时。更稳妥的比较方式是同时观察:

观察项作用不要单独推出的结论
cycles估计工作负载消耗的处理器周期不能单独代表墙上时间
instructions观察退休指令规模不能单独代表每条指令成本
CPI把周期与指令规模联系起来不能直接定位具体代码行
branch-misses观察分支预测失败线索不能证明唯一瓶颈就是分支
cycles 与 instructions 推导 CPI 并结合基线比较,同时受 perf_events、CAP_PERFMON 和内核配置权限影响的关系说明图
图2:关系说明图,展示 CPI 的静态推导和 perf_events 权限边界;它不是运行输出。

把权限和虚拟化边界排除在性能结论之外

perf_events 访问受到内核安全策略约束。Linux 内核文档指出,CAP_PERFMON 可以为非 root 进程提供性能监控相关能力,使用过大的管理员权限并不是首选方案。生产环境还应考虑计数结果中可能包含 CPU 型号、进程名、路径和硬件配置等信息。

# 查看当前 perf 工具和内核环境,记录测量上下文
perf --version
uname -a

# 查看当前进程的 perf_event 相关限制;不同发行版可能有不同权限策略
cat /proc/sys/kernel/perf_event_paranoid

遇到 Permission denied、事件显示 或计数器无法调度时,按“权限—内核配置—CPU/虚拟机能力—事件数量”顺序排查。不要为了让命令有输出就直接使用 root,也不要把权限放宽后的结果与普通权限下的结果无条件混合比较。

一份可复用的最小检查清单

  1. 先用 perf list 确认事件名称和当前环境能力。
  2. 从 cycles,instructions 开始,按问题需要加入分支或缓存事件。
  3. 固定输入、CPU 集合、重复次数和输出格式,再比较优化前后。
  4. 同时保存 cycles、instructions、CPI 与墙上时间,避免单指标判断。
  5. 把权限、虚拟机、内核策略和计数器复用记录在测量说明里。

常见问题

为什么 cycles 和 wall-clock time 不一样?

cycles 是处理器性能计数器的事件数量,wall-clock time 是经过的实际时间,两者受到频率变化、调度、并行线程和系统空闲时间等因素影响。分析 CPU 计算量时看 cycles,评估用户感知延迟时仍要保留实际耗时。

事件太多时为什么会出现缩放或不可用?

硬件 PMU 的可编程计数器数量有限,perf 可能在事件之间分时复用;当某些事件无法同时调度时,结果可能被缩放或直接不可用。减少事件组合并分组测量,通常比盲目添加事件更容易得到可解释结果。

CPI 高是否就说明缓存有问题?

不一定。CPI 是综合指标,缓存等待、分支预测、流水线资源竞争和内存访问都可能影响它。应再选择与假设对应的缓存、分支或停顿事件,并与相同输入下的基线比较。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>