Research
LYR Performance Note #024 計測・調整

複製したベンチマークは、静かに嘘をつく

本番をそのまま呼べ、そしてA/Bの前に同じ計測を2回回せ

2026-07-22 シリーズ第4回 9 min ハーネス計測決定性A/B

主要ロジックだけ真似て複製したベンチマークは、本番がとっくに捨てた設定を測り続けていた。本番関数をそのまま呼ぶ形に直すと、数値は +26〜31pp 回復した。

さらに、A/Bの前に「同じコードを2回流して一致するか」を確かめると、悪名高い「±20ppのノイズ」の正体が見えた。ランダムなノイズではなく、非決定性がA/B差分に紛れ込んだ交絡だった。

計測ファーストの怖さはここにある。速く測れるようになるほど、間違ったものを高速に確信できてしまう

計測ファーストで開発していると、いつしか計測器(ハーネス=測定の足場)そのものを疑わなくなる。数字が出てくる。その数字を根拠に、案を採り、案を捨てる。だが——その数字が最初から嘘だったら?

私はこの半年、ハーネスを開発のデフォルトにしてきた。測るのが速くて安くなり、測らない理由が消えた。ところが、速く測れるようになると別の落とし穴が口を開ける。間違ったものを、高速に、確信をもって測るという落とし穴だ。

この記事は、私が2度その穴に落ちた話だ。1度目は「複製したベンチが本番と別物を測っていた」。2度目は「同じコードを2回流すと、数字が一致しなかった」。どちらも、出てくる数字は一見もっともらしかった。

1度目:複製は、静かにdriftする

問題のハーネスは、Live翻訳(動画字幕をリアルタイムに訳す機能)のOCR領域追跡を測るものだった。字幕がどれだけ取りこぼしなく拾えているか——完全性(completeness)を測る。

このハーネスは、本番の追跡ロジックのうち主要な計算だけを共有し、その周り(追跡枠の破棄条件、全幅リカバリ、横方向の包絡線、枠の寿命)を手書きで複製していた。「コアは共有しているのだから、本番と同じものを測っているはずだ」——そう思っていた。

思い込みだった。

本番コードは、2026-07-04に「グリッドの横絞り」という最適化を、効果マイナスと判定して捨てていた。ところが複製ハーネスの手書き部分は、その捨てたはずの横絞りを測り続けていた。本番はもうやっていないことを、ベンチはずっと採点していたのだ。複製した瞬間から、両者は静かに枝分かれ(drift)していた。

複製をやめ、本番の追跡状態オブジェクトをそのまま渡して、本番の関数を呼ぶ形に直した。すると完全性(拾えた字幕の割合)がこう回復した(pp=パーセントポイント、以下同じ)。

ジャンル複製ハーネス本番を呼ぶ回復幅
ドラマ(字幕密)56.5%82.6%+26.1pp
ゲーム(字幕疎)50.0%81.2%+31.2pp

30ポイント近い差だ。本番が実際に出している 82.6% / 81.2% を、複製は 56.5% / 50.0% と採点していた。もしこの複製ハーネスの数字を信じて追跡ロジックの良し悪しを判断していたら、私は本番には存在しない世界を最適化していたことになる。

本番を呼ぶ形に直すと、完全性が回復 ドラマ(字幕密) 56.5% → 82.6% (+26.1pp) ゲーム(字幕疎) 50.0% → 81.2% (+31.2pp) 複製(本番が捨てた設定を測る) 本番関数を呼ぶ
図1:複製ハーネスと、本番呼び出しの差。本番が実際に出している 82.6% / 81.2% を、複製ハーネスは 56.5% / 50.0% と採点していた。測っていたのは「本番の性能」ではなく「複製の性能」だった。

教訓は単純だ。ハーネスは、本番の部品をインスタンス化してそのまま呼べ。ロジックを複製するな。 複製した瞬間、それは「本番の性能」ではなく「複製の性能」を測る別物になる。本番コードが呼びづらい構造なら、複製で回避するのではなく、本番のほうをリファクタして呼べるようにする

呼び出しづらい部分——画面依存で切り出せない処理——は本番から純関数として抽出し、型でdriftを止める。本番の状態オブジェクトを丸ごと共有し、計算関数が「サイクル冒頭の準備処理が返すトークン」を引数に要求する形にすれば、準備をすっ飛ばした複製はそもそもコンパイルが通らない。「気をつける」より「通らなくする」ほうが確実だ。抽出そのものの手順(golden特性化テストで現挙動を固定してからmove-only抽出する)は付録に置いた。

2度目:同じコードなのに、数字が一致しない

本番を呼ぶ形に直して、これでベンチは信用できる——そう思って追跡ロジックのA/B(2案の比較)を回した。差が出た。その差の機構を語りたくなった。

だが、その前に一つ確かめておくべきことがあった。同じコードを、同じデータで、2回流したら、同じ数字が出るか。

出なかった。

原因は、ハーネスの時計にあった。次の処理をいつ始められるかを決める計算に、私は実測のOCR所要時間を食わせていた。ところが端末のCPUガバナや発熱で、この所要時間は数十ms揺れる。すると1本の動画で回せるサイクル数が変わり、その下流——表示成功率、カバレッジ、字幕の出るタイミング——まで全部ずれる。同一コード・同一データで2回流した実測がこれだ。

まず、同じコードを2回流しただけで、中間量そのものが揺れた

指標1回目2回目
OCR所要(中央値)247 ms303 ms
総サイクル数6758

このrun間の揺れ自体は、まだ「同じA/B案を2回測った内輪の誤差」にすぎない。問題は、この揺れがA/Bの差分に混入したときに起きた。同じ2案(旧案 vs 新案)を別々の録画で比べると、表示成功率のA/B差分の符号すら逆転した。

録画表示成功率のA/B差分(新案 − 旧案)
録画A-6.7pp
録画B+12.5pp

同じ2案を比べているのに、片方の録画では「新案が6.7pt悪化」、もう片方では「新案が12.5pt改善」。この状態でA/Bを回して「差が出た、機構はこうだ」と語っていた。

そして気づいた。私が以前から「このハーネスにはノイズが±20ppある」と諦め半分に扱っていたもの——その幅の出どころは、いま見たA/B差分が録画をまたいで −6.7pp〜+12.5pp(約19pp幅)に散らばること、まさにそれだった。しかもこれはランダムなノイズではない。時計に実測値を食わせたことで生まれた非決定性——run間の揺れ——が、A/Bの差分に紛れ込んだものだ。非決定性そのものは各runの揺れにすぎない。だがそれがA/B差分に混入した瞬間、差の符号を決めてしまう。ここで初めて交絡(比べたい効果と、比べたくない揺れが分離できない状態)と呼べる。

ノイズを測るより、源を消す

直し方は逆説的だった。ノイズを何度も測って差分と比べるより、ノイズの源そのものを消すほうが速くて確実だった。

壁時計時間をシミュレーションのタイムラインに入れるのをやめ、固定値を使うようにした(既定は実測の中央値相当)。実測のレイテンシは、計測用に記録だけして、タイムラインには持ち込まない。レイテンシ自体を測りたいrunは、別に分ける。

こうして決定的モードができた。同じ入力なら、同じサイクル列が、ビット単位で再現する。この決定的モードで例のA/Bをやり直すと、282の指標が一致し、私が「機構」だと思っていた差は消えた。読解で納得していた仮説は、決定的A/Bにあっさり反証された。

ここに、直感に反する結論がある。「3回走らせて統計を取る」より、「決定的に1回」のほうが強い主張になる。 2回流して一致するなら、3回目も一致する。統計でノイズを平均化して薄めるのではなく、そもそもノイズが存在しない足場を作る——そのほうが、差を差だと言い切れる。

そして決定性を確かめる勘所は、集計値の一致ではなくサイクル列そのものの一致だ。集計が偶然そろっても、途中が違えば別物である。逆に——ここが美しいところだが——実測レイテンシは変わってよい。変わっているのに結果が動かないこと、それこそが「交絡を切り離せた」証拠なのだ。

教訓

  1. ベンチは本番をそのまま呼べ。複製は必ずdriftする。 主要ロジックだけ共有した複製は、本番がとうに捨てた設定を測り続ける。呼びづらければ本番をリファクタし、型でdriftを止める(準備を飛ばしたらコンパイルを通すな)。本番を呼ぶ形に直しただけで完全性は+26〜31pp回復した。

  2. A/Bの前に、同一コードを2回流して一致を確かめる。 一致しないなら、そのハーネスで過去に測った差分はすべて疑わしい。悪名高い「±20ppのノイズ」の正体は、たいてい非決定性(run間の揺れ)がA/B差分に混入した交絡で、それをランダムノイズと名前を付け違えたものだ。非決定性と交絡は別物——揺れがA/Bの差に紛れ込んで初めて交絡になる。

  3. ノイズを測るより、源を消すほうが速い。そして計器を直したら、過去の採否は棚卸しに戻る。 統計で薄めるのでなく非決定性そのものを無くす(「決定的な1回」は「3回の統計」より強い)。ただし直した瞬間、そのハーネスで捨てた案も採った案も全部が再評価の対象に戻る——この手戻りの量まで見積もって、直す順番を決める。

計測ファーストの怖さは、ここにある。速く測れるようになるほど、間違ったものを高速に確信できてしまう。だから、対象を測る前に一度、計測器そのものを疑う。同じ入力に同じ数字が返るか——その当たり前の検算が、去年の正解が今年の間違いになる世界で、数字を信じてよい唯一の根拠になる。


付録:生データ

項目実測条件・留保
複製ハーネスのdrift本番の状態オブジェクトごと共有に直すと、完全性が ドラマ密 +26.1pp / ゲーム疎 +31.2pp 回復複製側は、本番が効果マイナスで捨てた設定を測り続けていた
設定のdrift既定引数が本番と不一致(「引数なしで回す」が本番を再現していなかった)既定を本番に合わせ、A/Bのときだけ明示的に落とす。配線されていない引数は削除
非決定性の実測OCR所要中央値 247→303ms、総サイクル 67→58、成功率の差が録画により −6.7pp / +12.5pp と符号逆転同一コード・同一データ。時計に実測時間を食わせた結果。既定を固定時計に切替
決定的A/Bによる反証サイクル列を突き合わせて 282指標が一致「破棄が1サイクル早い」という読解上の仮説は等価と反証され、コメントを訂正
リファクタ手順①現挙動をビット単位で捕捉 → ②純関数をmove-only抽出 → ③pinテスト緑 → ④ハーネスを繋ぎ替え偶然の一致でも、コピーである限りdrift源。解像度・前処理も本番規約を共有する