4. 基准测试工具链与环境搭建

性能基准测试的可信度,首先取决于工具链是否选对、环境是否可控。同一段代码在不同编译器、不同驱动、不同频率策略下可能相差数倍,若工具链与环境没有固定,测出的数字就无法横向比较,更无法用于优化决策。本节围绕四类主流框架——Google Benchmark、CUDA Samples、rocBLAS、STREAM——说明各自定位与最小可运行示例,并给出 CPU/GPU 异构环境的可重复性配置要点。

四类框架的定位与分工

Google Benchmark 是 C++ 微基准框架,擅长对单个函数或小段代码做纳秒级计时,自动处理预热、迭代次数自适应、统计方差,并支持参数化扫描与 JSON 输出,适合 CPU 侧算法与内核的细粒度对比。CUDA Samples 是 NVIDIA 官方示例集合,其中 bandwidthTest、deviceQuery、matrixMul 等既是教学样例也是快速体检工具,适合验证 GPU 是否可用、显存带宽与计算能力是否达到预期。rocBLAS 是 AMD ROCm 平台的 BLAS 实现,自带 benchmark 客户端,可对 SGEMM、DGEMM 等做规模扫描,用于评估 GPU 浮点吞吐。STREAM 则是内存带宽基准的经典实现,通过 Copy、Scale、Add、Triad 四个内核测量可持续访存带宽,是判断“是否受内存带宽限制”的第一道标尺。

CPU/GPU 异构基准测试工具链与环境分层 环境层:固定变量,保证可重复 编译器与优化级别 驱动 / CUDA / ROCm CPU 频率与绑核 GPU 时钟与功耗墙 NUMA / 内存通道 工具层:四类框架各司其职 Google Benchmark CPU 微基准 / 参数扫描 预热 + 自适应迭代 输出 JSON 便于比对 CUDA Samples deviceQuery 体检 bandwidthTest 带宽 matrixMul 算力 rocBLAS SGEMM / DGEMM 扫描 规模 M=N=K 递增 输出 GFLOPS STREAM Copy/Scale/Add/Triad 可持续访存带宽 判断带宽瓶颈 指标层:统一口径才能横向比较 延迟(ns / μs) 吞吐(GFLOPS / GB/s) 相对加速比 波动率(多次运行标准差) 基线版本号 可重复性红线:同一测试须记录 CPU 型号/核数、GPU 型号、驱动与工具链版本、编译选项、频率策略、运行次数与统计方式; 任一变量变化,结果只能与同条件数据比较,不得跨环境直接引用。

Google Benchmark:CPU 微基准的最小闭环

以测量一个向量点积为例,先写基准函数,再用 BENCHMARK 注册并指定迭代范围。框架会自动决定运行次数,使单次测量时长落在合理区间,并输出中位数与标准差。

#include <benchmark/benchmark.h>
#include <vector>

static void BM_DotProduct(benchmark::State& state) {
  std::vector<float> a(state.range(0), 1.0f), b(state.range(0), 2.0f);
  for (auto _ : state) {
    float sum = 0.0f;
    for (size_t i = 0; i < a.size(); ++i) sum += a[i] * b[i];
    benchmark::DoNotOptimize(sum);
  }
  state.SetBytesProcessed(state.iterations() * state.range(0) * sizeof(float) * 2);
}
BENCHMARK(BM_DotProduct)->RangeMultiplier(4)->Range(1<<10, 1<<24);
BENCHMARK_MAIN();

编译时使用 -O2 -DNDEBUG 并链接 benchmark 库,运行后得到每个规模下的耗时与 GB/s。关键判断:当规模增大而 GB/s 趋于平台,说明已进入内存带宽受限区;若 GB/s 随规模持续上升,则仍受循环开销或缓存未命中影响。示例中 DoNotOptimize 防止编译器把求和优化掉,这是微基准最常见的陷阱。

CUDA Samples 与 rocBLAS:GPU 侧体检与算力扫描

GPU 环境先做体检:运行 deviceQuery 确认设备可见、计算能力与显存容量符合预期;再运行 bandwidthTest 得到主机到设备、设备到主机的实测带宽,与理论值(如 HBM 带宽)对比,若实测不足理论值的六成,应排查 PCIe 代际、驱动或 ECC 配置。算力侧可用 matrixMul 或 rocBLAS 客户端做规模扫描:

# rocBLAS 自带客户端示例(AMD ROCm)
./rocblas-bench -f gemm -r f32_r --m 4096 --n 4096 --k 4096 \
  --alpha 1 --beta 0 --transA N --transB N --iters 100

输出中的 GFLOPS 应与设备峰值浮点吞吐对照。示例假设某卡 FP32 峰值为 20 TFLOPS,若 4096³ 规模实测约 14 TFLOPS,则达到峰值七成,属正常范围;若仅 3 TFLOPS,需检查是否误用 FP64 路径、是否受显存容量限制导致分块过小。规模扫描的意义在于找到“算力饱和点”,小于该规模的矩阵乘法更多反映启动开销而非硬件能力。

STREAM:先判断是否受带宽限制

STREAM 的 Triad 内核 a[i]=b[i]+scalar*c[i] 最接近真实科学计算访存模式。编译时需根据 CPU 支持选择向量化选项,并把数组大小设为远大于末级缓存(例如每数组 512 MB),线程数设为物理核数并绑核。运行后取多次结果中稳定值,与内存理论带宽比较:若 Triad 带宽接近理论值七成以上,说明访存效率良好;若明显偏低,应检查 NUMA 跨节点访问、内存通道是否插满、是否被其他进程抢占。

可重复性配置清单

把上述工具串成可复现流程,需要固定以下变量并随结果一同归档:编译器版本与优化选项、CUDA/ROCm 与驱动版本、CPU 绑核与频率策略(如 cpupower frequency-set -g performance)、GPU 时钟锁定与功耗上限、运行次数与统计口径(取中位数还是最小值)、以及基线版本号。建议每次测试至少运行 5 次,剔除首次冷启动,报告均值与波动率;波动率超过 5% 时先排查环境抖动,再解读性能差异。只有工具、环境、口径三者同时固定,基准数据才具备跨版本、跨平台比较的资格。