编写基准测试#

基准是一种衡量代码性能的方法。它们可以用于比较不同的实现质量,或跟踪随时间变化的性能。

使用测试块进行基准测试#

对一个函数进行基准测试的最简单的方法是使用带有 @bench.T 参数的测试块。它有一个方法 @bench.T::bench,该方法接受一个类型为 () -> Unit 的函数,并自动确定合适的迭代次数然后运行该函数多次。测量和统计分析将在 moon 中进行,并在控制台输出中显示。

fn fib(n : Int) -> Int {
  if n < 2 {
    return n
  }
  return fib(n - 1) + fib(n - 2)
}

test (b : @bench.T) {
  b.bench(fn() { b.keep(fib(20)) })
}

输出如下所示(仅供参考):

time (mean ± σ)         range (min … max) 
  21.67 µs ±   0.54 µs    21.28 µs …  23.14 µs  in 10 ×   4619 runs

该函数执行了 10 × 4619 次。第二个数字是由基准工具自动确定的。它会增加迭代次数,直到测量时间足够长,以获得准确的计时。第一个数字可以通过向 @bench.T::bench 传递命名参数 count 来调整。

test (b : @bench.T) {
  b.bench(fn() { b.keep(fib(20)) }, count=20)
}

@bench.T::keep 是一个重要的辅助函数,它可以防止你的计算被优化掉并完全跳过。如果你正在基准测试一个纯函数,请确保使用这个函数以避免潜在的优化。然而,编译器仍然有可能提前计算并将计算替换为常量。

批量基准测试#

基准测试的一个常见场景是比较同一函数的两个或多个实现。在这种情况下,你可能想要在一个块中批量测试它们,以便于比较。 @bench.T::bench 方法的 name 参数可以用来标识测试块内的基准测试。

fn fast_fib(n : Int) -> Int {
  if n < 2 {
    return n
  } else {
    let mut a = 0
    let mut b = 1
    for i = 2; i <= n; i = i + 1 {
      let t = a + b
      a = b
      b = t
    }
    b
  }
}

test (b : @bench.T) {
  b.bench(name="naive_fib", fn() { b.keep(fib(20)) })
  b.bench(name="fast_fib", fn() { b.keep(fast_fib(20)) })
}

现在你可以通过查看输出,来评估哪个实现更快:

name      time (mean ± σ)         range (min … max) 
naive_fib   21.01 µs ±   0.21 µs    20.76 µs …  21.32 µs  in 10 ×   4632 runs
fast_fib     0.02 µs ±   0.00 µs     0.02 µs …   0.02 µs  in 10 × 100000 runs

基准统计的原始数据#

有时用户可能希望获取原始基准统计数据以进行进一步的分析。有一个函数 @bench.single_bench 返回一个抽象的 Summary 类型,用户可以将其序列化为 JSON 格式。我们不保证 Summary 类型内部字段的稳定性。

在使用 @bench.single_bench 时,用户必须确保计算没有被优化掉。此时没有独立的 keep 函数可用;之前介绍过的 keep@bench.T 的一个方法。

fn collect_bench() -> Unit {
  let mut saved = 0
  let summary : @bench.Summary = @bench.single_bench(name="fib", fn() {
    saved = fib(20)
  })
  println(saved)
  println(summary.to_json().stringify())
}

输出可能如下所示:

6765
{
    "name": "fib",
    "sum": 217.22039973878972,
    "min": 21.62009230518067,
    "max": 21.87286402916848,
    "mean": 21.72203997387897,
    "median": 21.70412048323901,
    "var": 0.007197724461032505,
    "std_dev": 0.08483940394081341,
    "std_dev_pct": 0.39056830777787843,
    "median_abs_dev": 0.08189815918589166,
    "median_abs_dev_pct": 0.3773392211360855,
    "quartiles": [
        21.669052078798433,
        21.70412048323901,
        21.76141434479756
    ],
    "iqr": 0.09236226599912811,
    "batch_size": 4594,
    "runs": 10
}

时间单位均为微秒。