快速入门

本指南在一个测试中带你从一个空包走到经过验证的基准测试、统计决策和 HTML 报告。随后它会在仓库附带的测试数据上展示命令行工具。

1. 添加模块

你需要 moonc 0.10 或更高版本的 MoonBit。

moon add Luna-Flow/mare_mark@0.3.0

在存放基准测试的包的 moon.pkg 中:

import {
  "Luna-Flow/mare_mark/model",
  "Luna-Flow/mare_mark/event",
  "Luna-Flow/mare_mark/runner",
  "Luna-Flow/mare_mark/stats",
  "Luna-Flow/mare_mark/report",
  "moonbitlang/async",
}

2. 基准测试、决策、报告

下面的测试比较两种计算平方和 02+12+⋯+(n−1)20^2 + 1^2 + \dots + (n-1)^2 的方法:循环和闭式公式 (n−1)n(2n−1)/6(n-1)n(2n-1)/6。

fn squares_loop(n : Int) -> Int64 {
  let mut total = 0L
  for i in 0..<n {
    total += i.to_int64() * i.to_int64()
  }
  total
}

fn squares_formula(n : Int) -> Int64 {
  let m = n.to_int64()
  (m - 1L) * m * (2L * m - 1L) / 6L
}

async test "loop versus closed form" {
  // 1. Describe the case: inputs, implementations, oracle.
  let looped = @runner.Implementation::stateless("loop", "1", (n : Int) => {
    @model.OperationResult::completed(squares_loop(n), ())
  })
  let formula = @runner.Implementation::stateless("formula", "1", (n : Int) => {
    @model.OperationResult::completed(squares_formula(n), ())
  })
  let plan = @runner.single_step("sum-of-squares", [100, 10000])
    .with_immutable_input(context => context.dataset_key.scale, n => n.to_string())
    .compare([looped, formula])
    .against_equal(squares_loop, (expected, actual) => expected == actual)
    .compile()
    .unwrap()
  // 2. Run it with a seed, an environment and two sinks.
  let memory = @event.InMemorySink::new()
  let record = @event.JsonlSink::new()
  let environment = @model.EnvironmentSnapshot::new(
    @model.SemanticEnvironment::new(@model.ExecutionTarget::Native, "moonc 0.10", "release", "i64"),
    @model.PerformanceEnvironment::new("native", "my-cpu", "default", 1, "monotonic"),
    @model.ProvenanceEnvironment::new("my-os", "my-host", "2026-10-08T12:00:00Z", "HEAD", "getting-started"),
  )
  let summary = @runner.run(
    plan,
    @runner.RunContext::new(
      environment,
      @event.tee(memory.as_sink(), record.as_sink()),
      42UL,
      @runner.ProtocolPreset::Development.validated(),
    ),
  )
  inspect(summary.passed_count, content="4")
  inspect(summary.failed_count, content="0")
  // 3. Decide on the larger dataset with paired confirmatory blocks.
  let baseline = memory.observations
    .filter(o => o.dataset_id == 1 && o.implementation_id == "loop" && o.phase is Confirmatory)
    .map(o => o.raw_elapsed_us)
  let candidate = memory.observations
    .filter(o => o.dataset_id == 1 && o.implementation_id == "formula" && o.phase is Confirmatory)
    .map(o => o.raw_elapsed_us)
  let comparison = @stats.compare_paired_with_bootstrap(
    "loop", "formula", baseline, candidate, 5.0, @model.confirmatory_interval(), 42UL, 2000, 95.0,
  ).unwrap()
  inspect(comparison.valid_samples, content="10")
  // 4. Render the record.
  let document = @report.document_from_jsonl(record.to_jsonl(), target="native").unwrap()
  inspect(@report.html(document).has_prefix("<!doctype html>"), content="true")
}

发生了什么:

  1. against_equal 把循环作为参考判定器附加上去。在任何计时之前,两个实现都在两个规模上经过了验证(四次验证通过)。
  2. Development 对每个实现进行预热,为每个实现校准批次大小,然后在每个规模上运行 3 个探索性区组和 10 个验证性区组,并轮换两个实现的顺序。
  3. 验证性区组按位置配对(循环的第 ii 个区组与公式的第 ii 个区组)。comparison.decision 和 comparison.speedup 取决于你的机器;预期公式为 Faster。
  4. JSONL 记录(record.to_jsonl())保存了每个事件;请把它保存在 HTML 旁边。

用 moon test --target native 运行它。该测试也可以在 js 和 wasm 上运行。

3. 使用命令行

在仓库的检出目录中:

moon run src/cli --target native -- report testdata/report/sample.jsonl report.html
moon run src/cli --target native -- report - - < testdata/report/sample.jsonl > report.html
moon run src/cli --target native -- replay testdata/replay/sample.jsonl --dry-run

report 写出一个自包含的 HTML 文件。replay --dry-run 打印验证失败中记录的命令;把 --dry-run 换成 --yes 即可执行它。参见 cli 教程。

常见的入门错误

  • 在实现函数内部做准备工作(分配、解析、复制),使其被计时。请把它放到夹具中。
  • 比较来自不同区组、阶段、数据集或目标的数组。
  • 把 Unsupported、超时或验证失败当作数字。
  • 未先用 --dry-run 阅读就执行重放产物。
  • 把 summary.run_id 当作唯一 id 复用;它指明的是协议和用例。

下一步