generator のチュートリアル
このチュートリアルでは、ベンチマークの入力を再現可能にします。1 つの実行シードからデータセットごとに独立したシードを作り、フィクスチャがそれを使って入力を生成し、フィンガープリントが何を計測したかを正確に記録します。どの例も完全なテストです。
クイックスタート
moon add Luna-Flow/mare_mark@0.3.0
import {
"Luna-Flow/mare_mark/model",
"Luna-Flow/mare_mark/generator",
"Luna-Flow/mare_mark/fixture",
}
test "one run seed, many dataset seeds" {
let run_seed = 2026UL
let seeds = [0, 1, 2].map(id => @generator.derive_seed(run_seed, "dataset", id))
inspect(seeds[0] != seeds[1] && seeds[1] != seeds[2], content="true")
inspect(seeds[2] == @generator.derive_seed(2026UL, "dataset", 2), content="true")
}
日常的な作業
導出したシードからランダムな入力を生成する
導出したシードを使って任意の擬似乱数生成器を駆動します。この例では、他のパッケージを必要としないように小さな線形合同法生成器を使います:
fn random_array(seed : UInt64, length : Int) -> Array[Int] {
let mut state = seed
Array::makei(length, _ => {
state = state * 6364136223846793005UL + 1442695040888963407UL
(state >> 33).to_int() % 1000
})
}
test "reproducible random input" {
let seed = @generator.derive_seed(42UL, "sort-input", 0)
let first = random_array(seed, 5)
let again = random_array(seed, 5)
inspect(first == again, content="true")
inspect(first.length(), content="5")
}
フィクスチャ内で生成する
ランナーはフィクスチャに、実行シード、ケース ID、データセットキーを持つ GenerationContext を渡します。そこでデータセットのシードを導出します:
test "a seeded fixture" {
let fixture : @fixture.Fixture[Int, Array[Int], Array[Int]] = @fixture.Fixture::immutable(
"random-ints",
"1",
context => {
let seed = @generator.derive_seed(context.seed, context.case_id, context.dataset_key.dataset_id)
random_array(seed, context.dataset_key.scale)
},
xs => @generator.stable_fingerprint(xs.map(x => x.to_string()).join(",")),
)
let context = @generator.context(42UL, "suite", "sort", @model.DatasetKey::new(8, 0), fixture.id, fixture.version)
let input = @fixture.materialize(fixture, context)
inspect(input.length(), content="8")
inspect(input == @fixture.materialize(fixture, context), content="true")
}
何を計測したかを記録する
入力の正規のシリアライズのフィンガープリントを取り、結果とともに保存します。ランナーは、フィクスチャのフィンガープリントをすべての検証イベントとすべての失敗アーティファクトに入れます:
test "fingerprints identify serializations" {
let a = @generator.stable_fingerprint("[1,2,3]")
let b = @generator.stable_fingerprint("[1, 2, 3]")
inspect(a.length(), content="71")
inspect(a == b, content="false")
}
2 つの文字列は同じ配列を表していますが、シリアライズは異なります。正規形を 1 つ選んでください。
計測ごとのシード
実験がブロックごとにノイズを引く場合は、3 つの ID すべてから導出します:
test "per-measurement seeds" {
let seeds = [
@generator.measurement_seed(1UL, 0, 0, 0),
@generator.measurement_seed(1UL, 0, 0, 1),
@generator.measurement_seed(1UL, 0, 1, 0),
@generator.measurement_seed(1UL, 1, 0, 0),
]
let distinct = seeds.all(s => seeds.filter(t => t == s).length() == 1)
inspect(distinct, content="true")
}
さらに先へ
- 生成関数とそのフィンガープリントを
@generator.Generatorに包み、値が変わったときは必ずversionを上げてください。 - generator の設計で、異なるインデックスや異なる実行シードが同じ導出シードを生まないことを証明しています。
よくある落とし穴
- すべてのデータセットで
context.seedを直接使う。 すべてのデータセットが同じストリームを引くことになります。データセットごとに導出してください。 - 正規形でないテキストをハッシュする。 マップや浮動小数点数は、フィンガープリントを取る前に順序と形式を固定する必要があります。
- フィンガープリントを署名として扱う。 フィンガープリントは鍵なしです。
次のステップ
- generator API、generator の設計。
- 生成された入力の周りのライフサイクルについては fixture のチュートリアル。