3. 性能度量与基准测试设计
性能剖析的第一步不是打开工具,而是明确“用什么量、怎么量、量得准不准”。如果指标定义含糊,后续所有火焰图、计数器采样和优化判断都会失去参照。本节围绕五类核心指标——FLOPS、内存带宽、缓存命中率、IPC 与停顿周期——说明它们的物理含义、采集方式,以及如何设计可复现的微基准与代表性子程序。
一、五类核心指标的定义与采集口径
FLOPS(每秒浮点运算次数)衡量计算吞吐。理论峰值由“核数 × 每周期浮点操作数 × 频率”决定,例如一个 8 核、每周期可执行 2 次双精度 FMA(每次 FMA 计 2 次浮点操作)、频率 3.0 GHz 的处理器,双精度峰值约为 8 × 2 × 2 × 3.0×10⁹ ≈ 96 GFLOPS。实测时要用足够大的循环体避免被编译器优化掉,并区分单精度/双精度、是否使用 FMA。常用做法是统计一个已知运算量的内核(如稠密矩阵乘)在固定时间内的完成量,再除以时间。
内存带宽衡量单位时间可搬运的字节数。采集时区分“理论带宽”和“可达带宽”:理论值由内存通道数 × 位宽 × 频率推算,可达值需用流式内核(如大规模数组拷贝、读写 triad)实测。注意区分读带宽、写带宽和读写混合带宽,它们往往不同。
缓存命中率反映访问在 L1/L2/L3 中的命中比例。它不能只看单一数字,必须结合工作集大小与访问步长解读:顺序访问大数组时 L1 命中率低但预取有效,性能未必差;随机访问小数组时命中率高才真正说明局部性好。采集通常依赖硬件性能计数器(如 cache-references、cache-misses)。
IPC(每周期指令数)等于退休指令数除以周期数,反映流水线填充程度。它与频率共同决定指令吞吐。IPC 低不一定是坏事——可能是内存受限而非前端受限,需结合停顿原因判断。
停顿周期指流水线因等待而空转的周期,常见来源包括缓存未命中、分支预测失败、执行单元争用、依赖链过长。采集时按原因分类统计(如 stalled-cycles-frontend、stalled-cycles-backend),才能定位瓶颈类型。
二、指标之间的耦合关系
这五个指标不是孤立的。一个内核若 IPC 高、FLOPS 接近峰值,说明计算受限;若 IPC 低、停顿周期中后端停顿占比高、缓存未命中多,则更可能是访存受限。判断“计算受限还是访存受限”的经典方法是算术强度:每字节内存访问对应的浮点操作数。当算术强度低于机器平衡点(峰值 FLOPS 除以峰值带宽)时,性能上限由带宽决定。例如峰值 96 GFLOPS、带宽 50 GB/s 的机器,平衡点约为 1.92 FLOP/Byte;一个算术强度仅 0.5 的内核,即使 FLOPS 数字好看,也注定被带宽卡住。因此采集指标时要成组观察,而不是追求单一漂亮数字。
三、可复现微基准的设计要点
微基准的价值在于隔离单一因素。设计时需注意:第一,防止编译器把“无用”计算优化掉,可用 volatile、将结果写回并校验、或依赖运行时输入;第二,控制工作集大小以命中目标缓存层级,例如用 32 KB、256 KB、8 MB 三档数组分别压 L1、L2、L3;第三,固定线程数、绑核、关闭频率漂移(如设置 performance 调频策略),并预热若干轮再计时;第四,多次重复取中位数并报告离散度,而非单次结果。一个典型的内存带宽微基准可写成 triad 形式:
for (i = 0; i < n; i++)
a[i] = b[i] + scalar * c[i]; // 每元素 2 读 1 写,约 24 字节访存
// 带宽 = 3 * n * sizeof(double) / 耗时
运行后若实测带宽只有理论值的 60%~70%,属于常见合理区间;若远低于此,应检查是否跨 NUMA 节点、是否未对齐、线程是否争用同一内存通道。
四、代表性子程序的选择
微基准证明“机器能跑多快”,代表性子程序回答“真实负载跑得多好”。选择时应覆盖程序的主要计算模式:稠密线性代数(计算受限)、稀疏矩阵向量乘(访存不规则)、stencil 或卷积(带宽与缓存敏感)、排序或哈希(分支与缓存混合)。每个子程序应固定输入规模与数据分布,记录版本、编译选项、线程绑定和随机种子,使他人能复现同一组数字。建议为每个子程序维护一份基线记录,包含指标值、采集命令和环境描述,后续优化以此为对照。
perf stat 一类工具一次性读取多个计数器,减少多次运行带来的环境差异;对跨轮次波动超过 5% 的指标,先排查频率、后台负载和 NUMA 放置,再讨论优化。把指标定义、采集口径、微基准与代表性子程序固定下来,性能剖析才有一个稳定、可比较的坐标系,后续的瓶颈定位与优化验证才有意义。