fixture のチュートリアル
このチュートリアルでは、ベンチマークの入力を実装に安全に渡す方法を示します。全員で共有する不変の入力、各バッチの前にコピーされる可変の入力、実装ごとに異なる方法で準備される入力、そして意図的に計測に含めるセットアップのコストです。どの例も完全なテストです。
クイックスタート
moon add Luna-Flow/mare_mark@0.3.0
import {
"Luna-Flow/mare_mark/model",
"Luna-Flow/mare_mark/fixture",
}
test "an immutable input" {
let fixture = @fixture.Fixture::immutable(
"squares", "1", context => Array::makei(context.dataset_key.scale, i => i * i), (xs : Array[Int]) => xs.length().to_string(),
)
let context = @model.GenerationContext::new(7UL, "suite", "sum", @model.DatasetKey::new(4, 0), fixture.id, fixture.version)
debug_inspect(@fixture.materialize(fixture, context), content="[0, 1, 4, 9]")
}
日常的な作業
可変の入力をコピーする
その場でソートする実装には、毎回新しいコピーを渡さなければなりません:
test "clone before prepare" {
let fixture : @fixture.Fixture[Int, Array[Int], Array[Int]] = @fixture.Fixture::new(
"reversed", "1",
context => Array::makei(context.dataset_key.scale, i => context.dataset_key.scale - i),
xs => xs.length().to_string(),
xs => xs.copy(),
(xs, _, _) => xs,
(_, _) => (),
@model.SetupPolicy::new(@model.SetupFrequency::PerBatch, @model.SetupTiming::ExcludedFromMeasurement, @model.WorkspaceScope::BatchWorkspace),
)
let context = @model.GenerationContext::new(0UL, "s", "sort", @model.DatasetKey::new(3, 0), fixture.id, fixture.version)
let input = @fixture.materialize(fixture, context)
let prepared = @fixture.prepare(fixture, (fixture.clone_input)(input), "sort", @fixture.SampleContext::new(0, "sort", 0))
prepared.sort()
debug_inspect(prepared, content="[1, 2, 3]")
debug_inspect(input, content="[3, 2, 1]")
}
clone_input はランナーが呼び出します。ここでの明示的な呼び出しは何が起こるかを示すためのものです。
実装ごとに準備する
prepare は実装 ID を受け取ります。各実装に、それが期待するレイアウトを与えます:
test "layout per implementation" {
let fixture : @fixture.Fixture[Int, Array[Int], Array[Int]] = @fixture.Fixture::new(
"matrix-2x2", "1",
_ => [1, 2, 3, 4],
xs => xs.length().to_string(),
xs => xs.copy(),
(xs, implementation, _) => if implementation == "column-major" { [xs[0], xs[2], xs[1], xs[3]] } else { xs },
(_, _) => (),
@model.SetupPolicy::new(@model.SetupFrequency::PerImplementation, @model.SetupTiming::ExcludedFromMeasurement, @model.WorkspaceScope::ImplementationWorkspace),
)
let sample = @fixture.SampleContext::new(0, "column-major", 0)
debug_inspect(@fixture.prepare(fixture, [1, 2, 3, 4], "column-major", sample), content="[1, 3, 2, 4]")
}
転置は実装ごとに 1 回、時計の外で行われます。
意図してセットアップを含める
「割り当てと計算」を 1 つのコストとして計測するには、反復ごとに準備し、それを計測に含めます:
test "setup inside the measurement" {
let policy = @model.SetupPolicy::new(
@model.SetupFrequency::PerIteration,
@model.SetupTiming::IncludedInMeasurement,
@model.WorkspaceScope::OperationWorkspace,
)
let fixture : @fixture.Fixture[Int, Int, Array[Double]] = @fixture.Fixture::new(
"fresh-buffer", "1",
context => context.dataset_key.scale,
n => n.to_string(),
n => n,
(n, _, _) => Array::make(n, 0.0),
(_, _) => (),
policy,
)
inspect(fixture.setup_policy.timing is IncludedInMeasurement, content="true")
}
すると各観測は setup_timing を IncludedInMeasurement として記録するため、読み手はその数値に割り当てが含まれていることがわかります。
さらに先へ
SampleContextのsample_idを使って、検証(-1)、ウォームアップとキャリブレーション(その他の負の ID)を計測ブロックと区別してください。一覧は runner の設計にあります。- ランダムな入力は
@generator.derive_seedを使ってcontext.seedから導出してください。generator のチュートリアルを参照してください。
よくある落とし穴
- 変更を加える実装に恒等の
clone_inputを使う。 後のバッチが変更されたデータを見ることになります。 - 高コストな
materializeの処理が計時されると思い込む。 計時されることはありません。 - バッチごとのセットアップを含める。 そうすると償却コストがキャリブレーションされたバッチサイズに依存します。
- 長寿命の値を破棄することでリセットする。 ランナーはリセット後にそれを再利用します。
次のステップ
- fixture API、fixture の設計。
- これらのフィクスチャでケースを実行するには runner のチュートリアル。