bench 教程

本教程介绍如何运行 floating 的基准测试套件、结果存放在哪里、如何阅读分析行,以及如何使用 bench 工具包编写新的基准测试。这些套件使用 Maremark 框架测量每个数值包的内核路径、核心路径和 checked 路径,并以带自助法(bootstrap)置信区间的配对比较形式报告结果。

快速入门

在仓库根目录下运行一个套件:

just bench bin-float

该命令以 release 模式在 native 目标上运行 src/bench/bin_float 中被跳过的性能测试,然后写出

Maremark artifact: .tmp/bench/bin-float.jsonl
Maremark analysis: .tmp/bench/bin-float.analysis.txt

.jsonl 文件每行保存一个带版本(mmka_1)的事件:每次观测、环境信息和运行汇总。.analysis.txt 文件保存精简后的结果,每个比较一行,例如

MAREMARK_HOTSPOT=bin-float/mul/2 core_pct=… full_pct=…

含义是:对于 bin-float/mul 的数据集 2,core/bin-float 路径的每次调用中位时间比系数内核高 core_pct 个百分点(若为负则为低),而 checked 路径比核心路径高 full_pct 个百分点。

日常任务

选择套件

套件运行内容
just bench bin-floatsrc/bench/bin_float 的全部测试(算术、初等函数、平方自动调优)
just bench elementary仅二进制初等函数基准测试
just bench auto-tune仅 mul(x, x) 与 square(x) 的交叉点
just bench decimalsrc/bench/decimal
just bench decimal-gdasrc/bench/decimal_gda
just bench ball-floatsrc/bench/ball_float
just bench all四个包的套件

添加 --output PATH 可选择产物路径,添加 --dry-run 则只打印 moon test 命令而不运行。

阅读自动调优结果

自动调优套件为每个数据集打印一个决策,并打印一个交叉点:

MAREMARK_TUNE=bin-float/autotune/square/64 candidate=square median_us=… samples=20
MAREMARK_CROSSOVER=bin-float/autotune/square below=… at_or_above=…
MAREMARK_POLICY=piecewise case=bin-float/autotune/square lookup=4:…,8:…

candidate 是在该数据集上每次调用中位时间最小的实现;交叉点是另一个候选实现开始胜出的第一个规模;策略行是一张可嵌入内核的查找表。

在代码中检查性能退化

confirmatory_regression 比较配对样本(相同顺序、相同分块),is_significant_regression 则给出判定:

///|
test "is it slower?" {
  let before = [10.0, 10.2, 9.9, 10.1, 10.0, 10.3, 9.8, 10.0]
  let after = [10.1, 10.2, 10.0, 10.0, 10.1, 10.2, 9.9, 10.1]
  let comparison = @bench.confirmatory_regression(before, after, 42UL).unwrap()
  inspect(@bench.is_significant_regression(comparison), content="false")
}

约 0.5 % 的变化低于 3 % 的实际阈值,因此即使它在统计上是明确的,也不算性能退化。

编写新的基准测试

一个基准测试由一个 immutable_bench 规格加上一个被跳过的异步测试组成,该测试运行规格并打印 Maremark 行。所有套件都采用如下模式:

///|
fn square_spec() -> @runner.BenchSpec[Int, BigInt, BigInt, BigInt, BigInt, Unit, BigInt?, BigInt?] {
  @benchkit.immutable_bench(
    "example/square",
    "square",
    [64, 256, 1024],                        // datasets: bit sizes
    bits => bits.to_string() + "bit",
    context => (1N << context.dataset_key.scale) - 1N,
    value => value.bit_length().to_string(),
    [
      @runner.Implementation::stateless("mul-self", "0.8.0", x => {
        @model.OperationResult::completed(x * x, ())
      }),
      @runner.Implementation::stateless("pow", "0.8.0", x => {
        @model.OperationResult::completed(x.pow(2N), ())
      }),
    ],
    x => x * x,                              // reference
    (expected, actual) => expected == actual,
    x => x.bit_length().to_string(),
    y => y.bit_length().to_string(),
  )
}

///|
test "plan compiles" {
  ignore(square_spec().compile().unwrap())
}

///|
#skip("performance benchmark")
async test "square paths" {
  let memory = @event.InMemorySink::new()
  let stream = @event.streaming_jsonl(
    line => println("MAREMARK_JSONL=" + line),
    "stdout://bench/example/square",
  )
  let summary = @benchkit.run(
    square_spec(),
    @benchkit.environment(@model.ExecutionTarget::Native, "bigint", "example-square"),
    @event.tee(memory.as_sink(), stream),
    20260715UL,
    @runner.ProtocolPreset::Development.validated(),
  )
  assert_eq(summary.failed_count, 0)
  for dataset_id in 0..<3 {
    let comparison = @benchkit.paired_hotspot(
      memory.observations, "example/square", dataset_id, "mul-self", "pow", 3.0, 20260715UL,
    ).unwrap()
    println("MAREMARK_HOTSPOT=example/square/" + dataset_id.to_string() +
      " pow_pct=" + comparison.relative_delta_pct.to_string())
  }
}

保持 plan-compiles 测试不被跳过:它会在每次常规测试运行中检查规格。在 tools/benchmark.py 中注册该包,以便 just bench 收集其输出。

深入了解

  • Maremark 的协议预设固定了预热、批次校准、样本数和顺序:QuickCheck(3 个确认性分块)、Development(10 个分块,各套件使用)和 RegressionGate(20 个分块,自动调优套件使用)。参见 Maremark 文档。
  • bench 设计 解释了估计量和置信区间。
  • 性能审计 和 performance/ 页面记录了实测结果。

常见陷阱

  • 基准测试默认被跳过。 moon test 只运行 plan-compiles 测试;请使用 just bench(它会传入 --include-skipped、--release 和 --no-parallelize)。
  • 仅限 native。 tools/benchmark.py 只接受 native 目标。
  • 配对需要相等的样本数。 一次比较中的两个实现必须具有相同数量的有效确认性观测;否则 paired_hotspot 返回 MismatchedPairs。
  • 热点区间并不是 95 % 区间。 paired_hotspot 传入的置信度是 0.95 个百分点;请依据其相对差值和判定结果。

后续步骤