fixture の設計

設計目標

同じ入力がすべての実装に届かなければならず、その準備のコストが計測の内側にあるか外側にあるかは、偶然ではなく宣言によって決まらなければなりません。fixture は入力に起こる 5 つのこと(生成、識別、コピー、準備、リセット)を分離し、ランナーがそれぞれを時計の正しい側に置けるようにします。

数学的背景

フィクスチャは、データセット DD と実装 II ごとに適用される小さな状態機械です:

ctx→ materialize x→ clone x′→ prepare(⋅, I, sample) p→ runn p→ reset ⊥.\text{ctx} \xrightarrow{\ \text{materialize}\ } x \xrightarrow{\ \text{clone}\ } x' \xrightarrow{\ \text{prepare}(\cdot,\, I,\, \text{sample})\ } p \xrightarrow{\ \text{run}^n\ } p \xrightarrow{\ \text{reset}\ } \bot .

2 つの等式が、ランナーが依存する契約を表します。

  1. 分離。 実装が pp を変更しても、次の準備はなお元の xx を見なければなりません。prepare(clone(x))\text{prepare}(\text{clone}(x)) は xx と可変な状態を共有してはなりません。そうすれば、すべてのバッチが同じ状態から始まり、計測されるワークロードはどのブロックでも xx の同じ関数になります。
  2. 決定性。 materialize\text{materialize} はコンテキストだけの関数なので、等しいコンテキストからは等しい入力と等しいフィンガープリントが得られます: ctx=ctx′⇒fingerprint(materialize(ctx))=fingerprint(materialize(ctx′))\text{ctx} = \text{ctx}' \Rightarrow \text{fingerprint}(\text{materialize}(\text{ctx})) = \text{fingerprint}(\text{materialize}(\text{ctx}'))。

ランナーはどちらも検査しません。これらに違反すると、ブロックごとに異なるワークロードを計測することになります。

計測に含まれるセットアップのコスト

1 回の操作のコストを cc、1 回の準備のコストを aa とします。nn 回の操作からなるバッチについて、ランナーは T^/n\hat T / n を報告します。ここで

SetupFrequencyIncludedInMeasurementExcludedFromMeasurement
PerIterationc+ac + acc
PerSample, PerBatchc+a/nc + a/ncc
長寿命cc(1 回の準備は早期の、破棄されるバッチに入ります)cc

バッチごとにセットアップを含めると、キャリブレーションが選んだバッチサイズに依存する償却コストを計測することになります。セットアップは反復ごとに含めるか、バッチサイズが固定されている場合にのみ含めてください。

設計上の決定

トレイトではなくレコード内のクロージャ

問題。 フィクスチャは入力の型、準備後の型、ポリシーが異なり、テスト内にその場で書かれることが多いです。選択。 Fixture は 3 つの型パラメータを持つ関数のレコードです。理由。 トレイトではフィクスチャごとに 1 つの型が必要になり、ID、バージョン、ポリシーを値として持てません。

準備の前にクローン

ランナーは常に prepare の前に clone_input を呼び出します。Fixture::immutable は両方を恒等関数にします。これは決して変更されない値ではコストがかかりません。可変な入力は本物のコピーを提供します。クローンと準備を分けておくことで、フィクスチャは入力を一度コピーしてその周りにワークスペースを構築でき、それぞれのコストが見えるようになります。

実装を意識した準備

prepare は実装 ID を受け取るため、実装が期待するレイアウト(パック済み、転置済み、パディング済み)を生成できます。こうすると準備はフィクスチャの一部になり、その計時は 1 つの実装のペイロードの中に隠れるのではなく、宣言されたポリシーに従います。

データとしてのポリシー

SetupPolicy はすべての観測(setup_timing)とイベントストリームに記録されます。読み手はコードを読まなくても、コールドスタートの計測とウォームな計測を見分けられます。

正しさと不変条件

  • materialize と fingerprint はデータセットごとに 1 回実行されます。
  • prepare は常に新しい clone_input の結果を受け取ります。
  • 短寿命の準備済みの値は、それぞれちょうど 1 回リセットされます。長寿命の値はランナーのチェックポイントでリセットされ、再利用されます(runner の設計を参照)。
  • Fixture::immutable は恒等関数と何もしないリセットを使います。

採用しなかった代替案

  • 実装に自分の入力をコピーさせる。 コピーが一部の実装では計時され、他の実装では計時されないことになります。
  • 1 つのセットアップフック。 生成(データセットごとに 1 回)と準備(バッチごとまたは反復ごと)を区別できません。

境界

  • フィクスチャは分離も決定性も強制せず、契約を述べるだけです。
  • WorkspaceScope は記述的なものです。ランナーはキャッシュを実装とデータセットごとにキー付けします。
  • PerRun と PerDataset は、PerImplementation と同様に実装ごとに準備されます。
  • フィクスチャはメモリを計測しません。