はじめに

このガイドでは、空のパッケージから始めて、1 つのテストの中で検証済みのベンチマーク、統計的な判断、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 を計算する 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 はループを参照オラクルとして取り付けました。両方の実装は、計時の前に両方のスケールで検証されました(合格した検証は 4 つ)。
  2. Development は各実装をウォームアップし、実装ごとにバッチサイズをキャリブレーションし、その後スケールごとに 3 個の探索的ブロックと 10 個の確認的ブロックを、2 つの実装の順序をローテーションしながら実行しました。
  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 として再利用すること。これはプロトコルとケースを表すものです。

次に読むもの