fullseye

Fullseye Runtime — 実測記録 (2026-08-15)

「Python で製造業の実用に耐えるか」を公開値の引用ではなく、この PC での実測で答えるための記録。 測定機 = 開発機(Windows 11, Python 3.11)。再現コマンドを各節に記載。 数字はすべて中央値 (p50) ベース。異常に良い結果は内訳を疑う方針([[feedback_benchmark_honest_disclosure]])に従い、 何を測っていないかも明記する。


★★0-a. 最初に読むこと — 同じコード・同じマシンで 1.7 倍ぶれた

本記録で最も製造業にとって重要な発見は、個々の数字ではなくこれである。

同一セッション中に、同じコードを同じマシンで 3 回測った(4 MP / 200 blob):

測定 セッション経過 手書き cv2 fslib industrial ObjectSet(numpy)
1 回目 開始 ~10 分(冷えた状態) 10.8 ms 16.9 ms 250 ms
2 回目 ~70 分後 17.9 ms 24.6 ms 318 ms
3 回目 ~90 分後(CPU 負荷 9%・アイドル) 18.3 ms 26.6 ms 266 ms

ネイティブ経路が 1.7 倍劣化した。 その間:

解釈(確度: 中): 最も整合する説明は 1.5 時間の連続負荷による熱 / 電力制限。 1 回目だけが冷えた状態で、以降は温まった状態である。 短く・メモリ帯域律速なネイティブ経路が比例的に強く効き、長い numpy 経路は相対的に鈍い、という現れ方とも整合する。 (CPU 温度は WMI から読めず 直接確認はできていない。ゆえに確度は中。)

この発見が要件に与える影響(★重要)

冷えたマシンで測ったサイクルタイムは、契約に書けない。


0. 要約 — 1 サイクルの内訳(2048x2048 = 4 MP、200 blob の検査。★絶対値は §0-a の注意つき)

構成 p50 対 現状 何を変えたか
現状(fscript.py の L1 + AST インタプリタ) 1448 ms 1.0x
L1 を直接 Python から呼ぶ(言語層を外す) 1382 ms 1.05x 言語 VM を外しただけ
ObjectSet(ラベル画像 + ID 列)に置換 250 ms 5.8x オブジェクトモデル
手書き numpy/scipy の床 244 ms 5.9x (参考: これ以上速くならない numpy の下限)
ネイティブカーネル(OpenCV)+ ObjectSet 10.8 ms 134x 画素カーネルの実装

この 134 倍のどこにも「Python をやめる」は入っていない。 言語も駆動も Python のまま 10.8 ms に到達する。

内訳:


1. サイクルタイムとジッタ

再現: py -3.11 tools/bench_realtime.py --cycles 30 --json out/bench_realtime_scale.json

構成 raw numpy ObjectSet 現 L1 fscript VM 言語層の税
512x512 / 25 blob 14.0 13.1 12.9 12.7 測定限界以下
1024x1024 / 50 blob 58.1 58.0 97.6 98.0 +0.4%
2048x2048 / 50 blob 243 257 394 409 +3.8%
2048x2048 / 200 blob 244 250 1382 1448 +4.7%

(単位 ms, p50。「言語層の税」= fscript VM ÷ 現 L1)

★この列の正しい読み方(自己訂正): 512x512 では fscript VM の方が速い(12.7 vs 12.9 ms)。 つまりこの差は測定ノイズと実装差の中に埋もれており、言語層のコストは分離できていない。 正しい主張は「+0.4〜4.7% のコスト」ではなく 「画素処理が支配的な負荷では、言語層のコストは -2%〜+5% の 範囲に埋もれて解像できない」。言語層の素のコストを分離した測定は §3(0.67 us/statement)であり、 そちらを設計判断の根拠に使うこと。 (なお 2 経路は完全に同一アルゴリズムではない — fscript 側は select_obj を index 反復し、 Rows := Rows + [...] が毎回リストを複製する。差分には実装差も含まれる。)

読み取れること

  1. fscript の AST インタプリタは、画素処理が支配的な検査負荷ではコストとして検出できない。 言語を Python VM で実行することはサイクルタイム上の問題にならない。 制御フローが 1 サイクルあたり数十〜数百 statement である限り成立する(§3 の境界表)。
  2. ObjectSet は raw numpy の床に到達する(250 vs 244 ms)。しかも blob 数に対して平坦 (50 blob 257 ms → 200 blob 250 ms)。一方、現 L1 は 394 → 1382 ms と blob 数に比例して悪化する。
  3. ジッタは小さい: 全条件で max/p50 = 1.02〜1.32x。

裾(p99.9): ネイティブ経路を 5000 サイクル連続

再現: py -3.11 tools/bench_realtime.py --configs 2048x2048x200 --modes native_cv2 --cycles 5000

2048x2048 / 200 blob, native_cv2 p50 p99 p99.9 max max/p50
gc 有効 10.97 12.71 13.51 16.73 1.52x
gc 無効 + freeze 10.94 12.47 14.11 19.72 1.80x

4 MP の検査を 5000 サイクル連続して、最悪 16.7 ms、p99.9 で 13.5 ms。 駆動は Python。 製造業が要求するのは平均ではなく最悪値なので、この形で提示できることが重要 (「p50 が 11 ms」ではなく「5000 回中の最大が 16.7 ms」)。 なお gc を止めた方が max が悪い(19.7 vs 16.7)。GC 抑止は無条件の改善ではない。

GC は jitter 源だったか → この負荷では No

gc.freeze() + gc.disable() の有無で比較した結果、有意差なし。測定中の GC 収集回数は全条件で 0

例(2048x2048/200 blob, ObjectSet) p50 p99 max
gc 有効 249.9 267.0 267.0
gc 無効 + freeze 257.9 264.6 264.6

★自分の結論を壊しにいった検証(確保が重い経路 x 600 サイクル)

上の結論は「ネイティブ経路 = 確保が少ない」で得たものなので、最も確保が重い経路で反証を試みたl1_builtins は 1 サイクルあたり全面 bool マスクを blob 数だけ作る(1024x1024 x 50 blob = 約 50 MB/サイクル)。 600 サイクル = 約 30 GB の確保・解放

再現: py -3.11 tools/bench_realtime.py --configs 1024x1024x50 --modes l1_builtins --cycles 600

  p50 p99 p99.9 max GC 収集回数 max/p50
gc 有効 96.32 102.13 102.59 103.18 0 1.07x
gc 無効 + freeze 96.39 101.47 103.63 103.83 0 1.08x

反証は失敗し、結論はむしろ強化された。 30 GB の確保・解放を経ても循環 GC は一度も起動していない。 理由は明快で、numpy 配列は参照カウントで即時解放され、循環参照に入らないため循環コレクタの対象にならない。 巨大配列の解放は refcount で決定的に起きる。

この結論が成り立たなくなる条件(★外部検証のデータで定量化できた):

外部の独立検証は、同じ CPython で「gen2 GC が p50 105.9 ms のスパイクを打つ」という 一見正反対の結果を出している。両方とも正しい。 分岐しているのは Python ではなく 「常駐させている Python オブジェクトの数」である:

常駐 live オブジェクト数 gen2 collection のポーズ
3 万 0.69 ms
30 万 8.47 ms
300 万 232 ms
(本測定の検査ループ) そもそも発火せず(収集 0 回)

本測定で GC が一度も走らなかったのは、この負荷が Python オブジェクトをほとんど常駐させていないため (numpy 配列は refcount で即解放され、循環コレクタの管理対象にならない)。 逆に、24 時間分のトレンドデータやレシピを dict / list で常駐させれば、簡単に 100 万個を超えて 100 ms 級のスパイクに変わる

設計指標として使える形: 10 ms サイクルで GC を 1 ms 以内に収めたいなら、 常駐 Python オブジェクトを概ね 5 万個以下に保つ。 これは「Python だから危ない」ではなく 「Python オブジェクトを何個常駐させる設計にしたか」の問題。 → 要件 R4 に反映(cycle 中は Python オブジェクトを作らない + 常駐数に上限を設けて監視する)。

その他、成り立たなくなる条件:

正直な限界: これは短時間測定であり、 (a) 連続運転でのメモリ断片化、(b) OS スケジューリング / ウイルス対策スキャン / Windows Update、 (c) カメラ取得と同時実行したときの競合 — は測っていない。 「CPython の GC は本質的にジッタを生む」という一般論はこの負荷では再現しなかったというだけで、 24/7 運転の証拠にはならない。長時間試験は Runtime 実装後の課題(§6)。


2. 画素カーネル: numpy/scipy vs チューニング済みネイティブ

再現: 本書 §2 のスクリプト(scipy.ndimage vs cv2 4.11.0、同一入力、p50)

処理 2048x2048 での p50 倍率
gaussian — scipy.ndimage float64 54.8 ms 1.0x
gaussian — scipy.ndimage float32 46.6 ms 1.2x
gaussian — cv2.GaussianBlur float32 5.9 ms 9.3x
gaussian — cv2.GaussianBlur uint8 1.1 ms 49x
label — scipy.ndimage.label 7.5 ms 1.0x
label + 面積 + 重心 — scipy(sum_labels+center_of_mass) 195 ms 1.0x
label + 面積 + 重心 — cv2.connectedComponentsWithStats 9.2 ms 21x

★★スレッド数を揃えた再測定(敵対検証の指摘を受けた自己訂正)

上表への正当な反証があった — 「cv2 は既定で全コア(24 スレッド)を使い、scipy.ndimage は単スレッド。倍率の相当部分は SIMD ではなくスレッド数だ」。 指摘のとおりなので、cv2 を 1 スレッドに固定して測り直した:

処理(2048x2048) scipy(常に単スレッド) cv2 = 24 スレッド 倍率 cv2 = 1 スレッド 倍率
gaussian 95.5 / 103.5 ms 4.22 ms 22.6x 7.21 ms 14.4x
label + 面積 + 重心 157.4 / 157.6 ms 13.63 ms 11.5x 20.15 ms 7.8x

結論(訂正版): 反証は部分的に正しい。スレッド数は確かに差の一部を占める(22.6→14.4、11.5→7.8)。 しかし単スレッド同士でも 7.8〜14.4 倍の差が残る。これは SIMD・アルゴリズム・dtype による本物の差であり、 「ネイティブカーネルに落とす価値がある」という結論自体は単スレッド条件でも成立する。 実際、パイプライン全体の単スレッド実測(§5.5)でも 1 コアで 25.9 ms(studio 経路 240 ms に対し約 9 倍)である。

★測定環境の汚染について(正直に): この再測定時、同一マシンで調査用のエージェントが並列に走っており、 scipy gaussian の絶対値が 54.8 ms → 95.5 ms に悪化している。倍率は絶対値より頑健だが、 本記録の絶対値は負荷のある環境で取られたものを含む。クリーンな環境での再測定を要する (§6 の未測定項目に追加)。

読み取れること


3. 言語層の素の速度(画素処理を含まない)

再現: 20000 反復 x 3 statement の制御ループを fscript と CPython で比較

  時間 1 statement あたり スループット
fscript AST インタプリタ 40.3 ms 0.67 us 1.49 M stmt/s
CPython(等価な Python コード) 0.44 ms 0.007 us 136 M stmt/s

言語層は CPython の約 91 倍遅い。 ただし絶対値で評価すること:

★設計ルールとして使える境界

0.67 us/stmt から逆算すると:

サイクル予算 言語層が 10% を占める statement 数 言語層が 50% を占める statement 数
10 ms 約 1,500 約 7,500
50 ms 約 7,500 約 37,000
100 ms 約 15,000 約 75,000

1 サイクル 1,500 statement までなら、10 ms サイクルでも言語層は 10% 未満。 検査スクリプトの実態(数十〜数百 statement)から見て桁で余裕がある。 逆に「500 個の測定領域を per-object で舐める」ような制御重視スクリプトは 1,500 に近づくので、 per-object 反復は言語の for ではなく ObjectSet のベクトル化 op に落とす設計を要件に含める (これは性能だけでなく §0 の 5.8x とも同じ結論)。

bytecode VM 化はこの数字を数倍改善しうるが、サイクルタイム上の動機は無い。 VM 化の動機は速度ではなく step/breakpoint/ソース span/ウォッチの実装しやすさである(正直に区別する)。


4. コールドスタート

再現: py -3.11 -X importtime -c "import api" / subprocess 3 回の最小値

  時間
Python 起動のみ 15 ms
+ numpy 80 ms
+ numpy, scipy.ndimage 152 ms
+ fullseye api(654 op レジストリ) 1817 ms
import fscript 単体 82 ms

import api の内訳: torch が 800 ms(ops.pybackends_korniakorniatorch が eager import)。

含意

★Runtime プロファイルで実測(fslib.py PoC)

  起動
Runtime プロファイル import fslib(numpy + scipy) 147 ms
Runtime プロファイル import fslib, cv2 160 ms
Studio プロファイル import api(650 op レジストリ) 1663 ms

10.4 倍の改善。160 ms なら、ウォッチドッグによるプロセス再起動が 0.2 秒で完了する (= 障害復旧が実運用に耐える)。これは「Runtime を Studio と分ける」判断の直接の裏付けであり、 fslib.py が 650 op レジストリを import しない設計にした理由でもある。


5. 配布フットプリント

パッケージ インストール実測
numpy 31.7 MB
scipy 157.0 MB
cv2 (opencv-python) 137.6 MB
小計(産業ランタイム最小) 326 MB
Python stdlib + exe 49 MB
Runtime 合計(目安) ~375 MB
PySide6(Studio のみ) 634 MB
torch(Studio/研究のみ) 4487 MB

含意: 顧客 PC に Python をインストールさせない「閉じた Runtime イメージ」は ~400 MB で構成できる。 商用ビジョンランタイムと同程度で、配布サイズは差別化要因にならない。 torch と PySide6 を Runtime に混ぜないことがサイズ要件の全て。


5.4 fslib.py PoC — プロファイル切替の実測

再現: docs/FSCRIPT_DECISION.md §1.7 のスクリプト(gauss → threshold → connection → select_shape → region_features)

構成 studio / reference(numpy) industrial(cv2) 倍率
1024x1024 / 50 blob 58.6 ms 5.42 ms 10.8x
2048x2048 / 200 blob 240.2 ms 16.94 ms 14.2x

同じスクリプト・同じ API・同じ結果(差分テストで一致を保証)で、profile を切り替えただけ。

★PoC 構築中に見つかった設計要件: 最初の版は 4 MP で 41.7 ms だった。 原因は API の形状が連結成分パスを 3 回走らせていたこと (connection で 1 回、select_shape の測定で 2 回目、最終取得で 3 回目)。 → ObjectSet が測定済み特徴量を id 索引で保持して運ぶことを要件に加え、41.7 → 16.9 msオブジェクトモデルは「形」だけでなく「測定結果の持ち回り」まで含めて設計する必要がある。

正直な残差: 手書き cv2 の 10.8 ms に対し PoC は 16.9 ms。 差の原因は特定済み(threshold が u8 上で numpy 比較 2 回 + AND、connection で uint8 変換のコピー)。 アーキテクチャの限界ではなく実装の詰めしろ。


5.5 コア数依存(産業 PC の最悪側を押さえる)

「産業 PC(低クロック・少コア)では数倍悪化しうる」という限界を、測定可能な形に変換した。 fslib の industrial プロファイルで 4 MP / 200 blob を、OpenCV のスレッド数を絞って実測 (測定機 = 24 コア):

スレッド数 p50 p99 max 対 24 スレッド
24(既定 = 全コア) 16.66 ms 17.45 19.08 1.00x
4 18.53 ms 19.58 19.69 1.11x
2 21.62 ms 22.29 22.49 1.30x
1 25.92 ms 27.83 27.88 1.56x

読み取れること(重要)

  1. 24 コアを使っても 1 コアの 1.56 倍にしかならない = この処理は メモリ帯域律速であり、計算律速ではない (4 MP の画像を何度も舐める処理としては典型的)。
  2. 「産業 PC はコアが少ないから数倍遅い」は成立しない。2〜4 コアで 18〜22 ms、最悪の 1 コアでも 25.9 ms。 残る変動要因は CPU の単スレッド性能とメモリ帯域であり、そこが半分の機械なら概ね倍、という見積りになる。
  3. 設計への含意: コアを増やしても速くならない。速度の主レバーは並列化ではなく (a) 処理範囲を狭める(= domain / reduce_domain による ROI 限定)、(b) パス数を減らす、(c) dtype を小さくする。 → HALCON の「Image は domain を内包する」モデルを型に入れる判断は、意味論だけでなく性能上も正しい。

5.6 ★soak テスト — 25 分 / 52,800 サイクル連続(「長時間安定性は未測定」を一部埋めた)

再現: py -3.11 tools/bench_soak.py --minutes 25 --bucket 400 --json out/bench_soak_25min.json 条件: fslib industrial プロファイル、2048x2048 / 200 blob、温まった状態から開始

指標 結果
総サイクル 52,800(25.0 分)
p50 のドリフト 前半 25.98 ms → 後半 26.26 ms = 実質ゼロ(全体でも 26.47 → 23.89 = −9.8%)
RSS のドリフト 98.9 → 100.0 MB = +1.6 MB / 52,800 サイクル ≒ 30 バイト/サイクル
GC 収集回数 52,800 サイクルで 7 回
p50 中央 26.07 ms(範囲 22.02〜33.82)
p99 中央 55.65 ms(範囲 40.09〜87.89)= p50 の 2.1 倍
max バケット max の中央 77.28 ms、全体最大 118.72 ms = p50 の 4.6 倍

良い側 — 2 つの懸念が実測で消えた

  1. 熱ドリフトは定常に落ち着く。 §0-a で見た劣化は「冷 → 温」の一度きりの遷移であり、 温まった後は 25 分間ドリフトしない(前半 25.98 / 後半 26.26 ms)。 ∴ 「時間とともに際限なく遅くなる」ではない。熱定常で測れば再現する数字が出せる。
  2. メモリリークが無い。 52,800 サイクルで +1.6 MB。 1 サイクルあたり 30 バイトは事実上アロケータのノイズであり、リークの兆候ではない

悪い側 — ★裾が本当の仕事である

p50 26 ms に対し、52,800 回中の最悪は 118.7 ms(4.6 倍)。

製造業に提示できるのは p50 ではなく最悪値なので、現状の実力は「4 MP 検査 ≒ 最悪 120 ms」であり、 「26 ms」ではない。裾を詰めることが、平均を速くすることより優先度が高い。

裾の原因は未診断(候補: OS スケジューリング / 他プロセス / アロケータ / cv2 スレッドプール / ページフォールト / timeBeginPeriod 未設定)。次に測るべきはここであり、本記録では原因を断定しない。

なお §1 の 5000 サイクル(native_cv2、max 16.7 ms)と本測定(52,800 サイクル、max 118.7 ms)の差は、 (a) 冷 vs 温、(b) 手書き cv2 vs fslib(numpy 比較 + uint8 変換が入る)、(c) 試行回数 10 倍 の 3 つが混ざっている。裾は試行回数を増やすほど悪化して見えるという当然の性質も含む。


6. 測っていないこと(正直な限界)

この記録は設計判断に必要な最小限しか測っていない。以下は未測定であり、主張に使ってはいけない:


7. 実測から出た欠陥(コードの事実)

測定の過程で、fscript.py黙って誤った値を返す欠陥が 5 件見つかった。 いずれも「言語が自前の型モデルを持たず numpy/Python の意味論を継承している」ことが根因で、 実装言語を変えても残る。回帰テスト = tests/test_fscript.py★I-2(2026-08-15)で 5 件すべて封鎖済 = fscriptfslib の型モデルへ載せ替え(全スイート 4372→4377 passed)。

# 欠陥 症状 状態
1 * を行途中でもコメント扱い A := I * 2A := I になりエラーも出ない 修正済(行頭のみコメント)
2 _norm01 が画像の最大値で正規化 隅の明るい画素 1 個で同じ部品の area が 256 → 1 に変わる 修正済(I-2) = FImage が値域を運ぶ
3 数値 Tuple の + が連結 [1,2,3]+[10,20,30] = 連結。HALCON は要素和 [11,22,33] 修正済(I-2) = + 要素ごと・連結は [t1,t2]
4 iconic が条件式で暗黙に真偽化 if (Region).any() として通る 修正済(I-2) = iconic は真偽値なし→エラー
5 比較が配列を返し条件で潰れる if (Image = 0) が「1 画素でも 0 なら真」 修正済(I-2) = iconic の比較はエラー

さらに value_kind が画素の中身から型を推測していた(相異なる値が 2 個以下なら Region)欠陥も I-2 で型ベースに修正(2 値のグレースケール画像を Region と誤判定しない)。→ 型は運ぶもので推測するものではない。

欠陥 2 が最も重かった: 実ラインの正反射・ホットピクセルで判定が黙って反転していた。 これは性能問題ではなく信頼性問題であり、ネイティブ化より優先度が高いと判断し I-2 で最初に閉じた。


8. 設計判断への含意(まとめ)

  1. 「Python か否か」は問いとして誤りだった。実測が示す優先順位は (1) 型・意味論の正しさ → (2) オブジェクトモデル → (3) 画素カーネルの実装 → (4) 言語の実行方式。 言語の実行方式(Python VM か native か)は最も影響が小さい
  2. L1 の契約を「native に差し替え可能」な形で切ることが、性能上も移植上も唯一の要石。
  3. Studio(設計時)と Runtime(配布)を別プロファイルにすることが、コールドスタート・フットプリント・ 決定論のすべてを同時に解く。

正本の設計判断は docs/FSCRIPT_LANGUAGE.md へ反映する。


9. 裾(N1b)の初期診断 — タイマ分解能レバーはこの実検査には効かなかった(honest)

背景: 決定案 R4 は timeBeginPeriod(1)(Windows タイマ分解能)を裾の最大レバーと位置づけた (参照測定で max 8.905 → 0.340 ms = 26x)。そこで tools/bench_soak.pytimer_resolution(1) コンテキスト(--timer-resolution/--no-timer-resolution、既定 ON)を追加し、 実検査パイプラインで A/B した(2026-08-15、この PC、cv2 studio→industrial 相当、GC 発火 0)。

構成 タイマ p50 p99 p99.9 max サイクル
1024²×50 OFF 5.50 6.21 6.50 6.65 4,000
1024²×50 ON 5.46 6.09 6.30 6.46 4,000
2048²×200 OFF 17.07 18.31 18.77 18.81 4,800
2048²×200 ON 16.98 18.35 18.62 18.67 4,800

正直な結論(過大主張しない):

  1. タイマ分解能を上げても、この実検査の裾は測定可能なほど動かない(4MP で max 18.81→18.67 ms、誤差域)。 R4 の 26x は合成の空 10 ms ループの数字で、OpenCV カーネルが主要時間を占める実検査には転移しない。 → 裾の主因はタイマ分解能ではない(少なくともこの負荷・この長さでは)。候補から降格。
  2. この短い soak では 118.7 ms の裾を再現できていない。max はわずか ~18.8 ms。理由は 2 つ: (a) 82 秒(~5,000 サイクル)は熱定常前で、p50 が 17 ms(正本 soak の 26 ms より低い=cold が速い、 [[feedback_benchmark_thermal_steady_state]] と整合)、(b) 118.7 ms は 52,800 サイクル中の稀事象 (p99.9=55.7 ms / max=118.7 ms)で、5,000 サイクルでは 1/50,000 の事象を踏まない。
  3. ∴ 裾の真の診断には、正本と同じ長さ(~25 分 / 52,800 サイクル)の熱定常 soak が必要タイマ分解能の capability は追加済(R4 の Runtime 要件・既定 ON)だが、裾の是正効果は実証できていない。

9.1 裾は外部 CPU 競合に強く依存する(示唆的・要追加測定 / 2026-08-16)

この節は当初「原因を特定」「唯一の確実策」「DEFINITIVE」と書いたが、自分の敵対検証(4 レンズ WF)で **複数の overclaim を摘発し、以下に格下げ・訂正した([[feedback_benchmark_honest_disclosure]] / [[feedback_no_false_reporting]])。 データ(下表)は実測だが、そこから引ける結論は当初主張より弱い**。

tools/bench_soak.py --diagnose(op 別内訳 + ページフォールト)で 4 条件を測定(4MP×200・GC 実質ゼロ、各 N=1):

条件 p50 max(最遅 cycle) jitter 備考
クリーン(アイドル機・54,000 cycle/15分) 17.0 21.0 1.24x p50 ドリフト +0.4%・リークなし・GC 3 回。計装なしでも max ~18.9ms(独立確認)
18 burner(6 コア空き) 18.0 21.7 1.20x コアに余裕があれば裾は不変
24 burner(全コア飽和) ~73 cv2 系が膨張: gauss 0.9→~25ms / connection 12→40-63ms
24 burner + cv2-threads=1 ~32 ~57 1.8x 単スレッド化で裾は縮むが p50 上昇

★所見(honest・確度を明記):

  1. クリーンなアイドル機では、この pipeline の裾はタイト(54,000 cycle で max 21.0ms=1.24x、計装なしでも max ~18.9ms)。これは確度が高い
  2. CPU コア飽和で同一 pipeline の裾は大きく膨れる(全コア飽和で max ~73ms、cv2 の gauss/connection が膨張・numpy threshold は不変)。方向性は確かだが — ★正本の 118.7ms は本セッションで一度も再現していない(最大でも ~73ms=62%)。「正本の裾域を再現」は言い過ぎだった。
  3. ∴ 118.7ms は pipeline 固有でなく環境要因の可能性が高い、が確定ではない。特に:
    • A/B が熱条件で非整合: クリーン基準は p50 17ms(この機の今の定常)だが、正本 soak は p50 26ms(§5.6)= 別の(重い)条件。同条件の対照ではない。
    • 最も素直な説明: 正本 soak 当時、この開発機で私自身の調査エージェント等が並列にコアを食っていた(§2/§6)ための汚染。もしそうなら、クリーンな産線 PC は元から ~21ms で、コア確保は「必要」ではなく「効きうる」に留まる。
    • cv2 プリエンプトとページフォールト嵐を分離できていない: 相はウォールクロックで、飽和時の major fault を cv2 相の時間として計上しうる(fault は per-bucket 集計で per-cycle 未取得)。毎 cycle ~57MB 新規確保は pipeline 側の増幅器であり、バッファ pool 化が裾にも効く可能性を排除できない(「p50 専用」は言い過ぎ)。
  4. ミティゲーション(すべて N=1・未確立、要反復):
    • コア確保: 6 コア空きで 18 busy 同居でも max 21.7ms。ただし 24 コア機での 1 例。★4-8 コアの実産線 PC では未測定で、OS/カメラ取得と競合し逆効果の恐れもある。「唯一の確実策」ではなく有力候補
    • cv2.setNumThreads 抑制: 飽和下で弱く noisy(N=1)。優先度(SetPriorityClass HIGH)は本環境で未適用=未検証。timer は §9 の通り実検査で効かず(ただし飽和条件では未試験)。
  5. バルク内訳(裾と別軸だが関連): connection(cv2)~12ms + threshold(numpy)~4.9ms。次にやるべき測定 = (a)4-8 コア機での裾、(b)各条件 ≥3 反復 + p99/p99.9、(c)per-cycle hard-fault、(d)バッファ pool 版の飽和下 A/B、(e)正本 118.7ms の再現(または汚染源の特定)。