mandelbrot/docs/PERFORMANCE_IMPLEMENTATION.md
2026-09-06 23:28:03 +09:00

194 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 軽量化の実装設計と精査結果
更新日2026-09-06。[改善案](PERFORMANCE_PLAN.md)の続き。以下は承認前の設計と精査記録。採用した実装、検証結果、残る数値上の制約は [実装結果](IMPLEMENTATION_RESULTS.md) を参照。
## 1. 精査で確定した修正
| 論点 | 現在のコードで確認した内容 | 案への反映 |
| --- | --- | --- |
| 参照の強制更新 | `ReferenceService.request(..., fresh=false)` があり、trueで該当cacheを削除する。実コードを使った小さなCPU確認でも再投入された | 「引数がなく無効」を撤回。同じ入力から同じ短い参照を再選択し得る問題として扱う |
| 局所参照の位置 | `FAILURE_TILE_MAP_WGSL` が失敗indexの最小値を求め、`readNumericalFailureTiles()` が代表点へ変換する | 既に失敗画素を使う。重心選択と誤記せず、代替候補・回収実績を改善対象とする |
| reason 4・7 | 4は定義・集計、7は名前がある。現行シェーダーにそれらを失敗として書く経路は確認できない | 現在の発生原因や改善効果に算入しない |
| 継続の終端 | `DEEP_ACTIVE_CONTINUE_WGSL` の終端分岐はtarget>=150001が必要だが、通常の目標は最大150000 | 目標到達の判定とstateの寿命を分ける |
| 最後の1反復 | 継続loopは更新後のnがstopに達すると、次回の脱出判定より先に終了する | 結果採用時に最終stateの脱出・誤差を確認する |
| DS queueの上限 | `computeAccurateDirectHybrid()` が初期batchを時間に応じて拡大し、元の仕事量上限へ戻していない | 適応後のclampと横分割を前提にしてからまとめ投入する |
| 現行の計測値 | `session.pixelIterations` は入口active数×chunkの加算 | 実反復数と呼ばず、仕事量上界として比較する |
| 現行の「正確」 | Direct DSDeep DS補修はDSで計算するが、全丸め誤差を区間伝播する証明ではない | 数値採用・有限回未脱出・内点証明を分ける |
照合対象は現行 [index.html](../index.html)。精査時のSHA-256は `045a5de85b816808ea8f54c5a390196b7436b3c7802267aadcd7bc1baa7c6792`。この値は照合した版を識別する記録で、今後の編集を禁止するゲートではない。
## 2. 計算状態の契約
既存のmetaを表示用の最小情報として残し、継続可能なstate・参照の識別・仕事の所属を分ける。reasonやclassを増やすこと自体を目的にしない。
| 論理状態 | 必須情報 | 次の処理 |
| --- | --- | --- |
| 未着手 | 画素index、描画世代 | 初期計算 |
| 継続可能 | 実到達n、参照内のm、差分、指数、誤差、参照ID | 同じ数値方式で次の有界chunk |
| 数値補修待ち | 失敗理由、画素index、最後に有効だったstateの有無 | 理由に合う参照/精度で補修 |
| 目標まで未脱出 | 採用済みのn、誤差確認の状態、継続stateの有無 | 次の反復目標を待つ。集合内の証明とはしない |
| 脱出を採用 | 脱出反復数、smooth、採用条件 | 数値計算から除外し、配色変更だけに応答 |
| 内点を確認 | 確認した方式と対象画素 | 継続から除外 |
各画素が同時に複数の仕事へ所属しないことを基本にする。画素単位の所有者は `継続/補修/保留/数値処理終了` のどれか1つ。集計はこの排他的な所属を数え、active件数とmetaのUNKNOWN総数をそのまま足し合わせない。
`n` は処理した反復数、`m` は参照軌道内の位置であり、片方から他方を推定しない。画素ごとに到達nが違う可能性があるため、sessionの単一progressを全画素の到達証明として使わない。
### 目標到達時の処理
1. 最後の更新後のstateから `z_n` を再構成する。参照範囲外なら補修へ回し、末尾indexを丸めて代用しない。
2. 脱出条件を満たすか、境界を誤差込みで判定できるかを確認する。確定できない境界は補修へ回す。
3. 未脱出なら現在の目標に対する誤差条件を確認し、metaへ実到達nを記録する。
4. 採用できた未脱出画素はstateを保存し、次の目標に使える保留対象として保持する。脱出・内点は除外する。
5. 誤差条件を満たさないstateを「正常な継続state」として再利用しない。前chunk等の有効stateがある場合だけそこから補修できるものとする。
P0では既存の誤差閾値を無条件に緩めず、未実行だった採用処理を到達可能にする。DSやCPU補修にも採用条件の限界を残す。数学的保証を強める場合は、別の数値方式変更として評価する。
## 3. GPUスケジューラーの具体案
同じ描画世代・参照ID・数値方式・誤差方針の画素を1グループとして処理する。最初は既存の2本のactive queueを使い、複数の常駐エンジンを増やさない。
### まとめ投入の上限
```text
upper = このまとまりの入口のactive件数
workCap = 継続カーネル1回の上限初期案4,000,000
chunk = min(256, floor(workCap / upper), この目標までの残り反復)
batchPasses = 14初期は1、短い完了時間を確認して増加
upper == 0 または残り反復 == 0 → 反復を投入せず目標の完了処理へ
floor(workCap / upper) == 0 → 対象を分割する。chunkを1へ丸めて上限を超えない
batchPasses × upper × chunk → まとまり全体の仕事量上限でも制限
```
この上界が有効なのは、まとまりの途中で対象を追加せず、各画素を高々1回だけ次queueへ戻す場合である。補修済み画素の再参加・別グループの統合はまとまりの境界で行う。nの違う画素の残り反復はカーネル内でも切り詰める。
```text
世代を確認
同じ参照のbufferを確保
24回以下の「件数→indirect引数→有界継続」をencode
最後の件数・失敗要約だけをstagingへcopy
submit
staging.mapAsyncの成立を待つ
世代を再確認して要約を採用、unmap
表示更新・入力処理へ制御を返す
残件があれば次のまとまりへ
```
CPU時間による初期調整目標は、1まとまりの完了を数ms十数ms程度に収めること。ただしmapを含む経過時間は純GPU時間ではない。過去の実測が長いときは回数か仕事量を減らし、数値失敗を捨てて短くしない。初回に4回を固定投入したり、毎回時間計測用の追加同期を挟んだりしない。
専用参照・uniform・queue・stagingは、そのまとまりが使い終わるまで破棄上書きしない。`mapAsync` が成立したstagingは `unmap` までGPUへ再投入しない。単一stagingを直列再利用する実装から始め、重ね合わせが必要になった場合だけ少数ringへ広げる。
GPUへ既に投入した処理を世代tokenだけで取り消すことはできない。入力時は追加投入を止め、旧世代の結果を新しい画素バッファへ書き込まない。参照の破棄は、それを使う処理の完了後に行う。
### 他の重い経路にも適用する上限
`computeAccurateDirectHybrid()` のDS queueは、初期値と適応後の値の両方を `maxPixels=floor(workCap/iter)` で制限する。workgroupが64でも仕事件数を最低64にする必要はなく、端数laneをguardすれば163画素も処理できる。
全幅1行でも予算を超えるDeep stripは、既存の `correctUnknownFrameScan()` と同様にx方向も分割する。処理量は論理画素数×反復上限で数え、丸められたworkgroup数だけを根拠にしない。デバイスのbuffer・binding・dispatchサイズ上限は、この仕事量上限とは別に満たす。
## 4. 補修と参照を再利用する手順
### 正常画素を巻き戻さない
失敗したindexを別queueへ移す際、残る画素のstateとqueueを保持する。補修から戻る情報は次の2種類を区別する。
- **meta・smoothのみ**脱出なら計算終了。未脱出ならstate未提供として保留し、この画素だけを必要に応じて再計算する。現行のDSCPU補修は主にこちら。
- **有効な継続state付き**:同じ参照・数値方式のグループへ戻せる。異なる局所参照なら、その参照に属する別グループへ入れる。
局所参照で回復したstateを、元の全体参照のstateとして扱わない。全体の `setDeepContext()` を変更して別グループの処理と競合させず、encode時に使う参照bufferを明示する。原点が変わる場合の差分変換は、この最初の実装には含めない。
### 参照軌道の末尾延長
参照IDは原点・実際の計算精度・数値方式で識別し、反復目標だけの増加では同じIDを維持する。精度の自動再試行でbit数が増えた場合は、要求精度ではなく採用した精度に基づいて別IDにする。
Workerは参照本体の `zr, zi, zr², zi², n` と、検査用の高精度軌道の独立した終端stateを保存する。精度Pの丸め済み終端を単にP+32へ拡張して検査の初期値にしない。両者の検査済みprefixを維持し、それぞれのstateから末尾を進める。
参照が既に脱出した場合は単純延長を続けず、別候補か局所参照へ進む。同じ候補を同じ条件で何度も選び直さない。prefixの検査点配置は長さに依存しない規則へ揃えるか、新目標で必要になる過去の検査点も確認する。GPU側も容量が足りる範囲では末尾だけを転送し、容量拡張時は旧参照の利用完了を待つ。
### CPU補修の小分け化
最初の候補設定は同時12 Worker、1ジョブ最大1664画素に加え `件数×反復数` による上限を設ける。これは固定の最適値ではない。精度bit数・実行時間も見てジョブを縮小し、少数でも長い画素がUIの次の仕事を妨げないようにする。
同じ精度の結果再利用は最初に導入できる。Workerへ渡した入力の世代・座標・解像度・反復予算・精度が同じ場合だけ再利用し、前回の高精度結果を次の比較へ渡す。
結果は完了したジョブから受け取るが、GPUへの反映は1か所で直列化する。writeBufferscatterの**前**に世代と対象metaを確認し、同時進行するGPU補修が確定させた画素を古いCPU結果で上書きしない。現在の `applyPrecisionFallback()` 内ではtoken確認がwriteBuffer後にあるため、呼び出し側の確認だけに依存する非同期化を避ける。
回収できなかった画素は、小さな保留リストと次の手段を保持する。時間予算を使い切ったことを `numeric-complete` として報告しない。
## 5. 未脱出の反復予算と表示
最初のP0P2では、変更前に採用されていた反復方針を可能な範囲で固定して比較する。ただしP0で新しい失敗や目標境界の脱出を正しく認識した場合、その差を性能回帰と誤認しない。
P3の候補は、同じ大域目標の中で「境界周辺・孤立成分・長時間残件」を別優先度にする方式から始める。全候補が目標に到達したかはGPUで `未到達件数` を集約する。低優先度の残件にも割当を設け、速く終わる画素ばかりを処理し続けない。これだけでは総反復数は減らないが、表示の改善順と処理継続性を制御できる。
総反復数を減らす変更は、検証できた内点の除外か、明示した有限予算への停止として別に扱う。内点判定を増やす場合は適用領域を限定し、軌道の近接や少数回の無変化だけで内点へ昇格しない。
表示・履歴・出力の意味は次のように統一する。
| 表示段階 | 条件 | 履歴・PNG |
| --- | --- | --- |
| 現在視点の暫定表示 | 計算済みタイルを順次表示。未処理領域があることを状態として保持 | 完成履歴へ保存しない |
| 数値補修が完了した予算画像 | 被覆漏れ0、数値補修待ち0、残る未脱出は到達nを記録 | 既定の反復方針を満たすまでは暫定。明示的な暫定出力を設けるなら予算を付記 |
| 現在の品質方針で完了 | 必要な全候補が目標と数値条件を満たし、品質方針の停止条件を満たす | 完成履歴へ保存できる。集合内の完全証明とは別 |
同一画素格子のタイル表示を先に検討する。低解像度の全体previewを追加する場合、半画素中心のずれを確認し、単に同じ画像範囲という理由で最終画素の計算へ流用しない。暫定表示で初回待ちが改善しても、数値補修完了の時間を別途示す。
## 6. 実装単位と確認ケース
| 単位 | 対象 | 採用するための確認 |
| --- | --- | --- |
| A判定と上限 | 継続の目標終端、meta更新、DSDeepの分割 | 最後の反復での脱出、誤差超過、未脱出の再投入、全分割の被覆を確認 |
| B同期とBigInt重複 | 小さなまとめ投入、精度ペア再利用 | Aと同一入力・予算で分類、脱出反復、smooth、到達nが一致。同期や軌道数が減る |
| Cstateと参照の寿命 | 補修queue分離、参照末尾延長 | 少数の補修で正常stateが0に戻らず、参照変更・精度変更を混同しない |
| D補修転送 | タイル別GPU queue、scatter反映 | 件数だけでなくindexの被覆を確認。重複・古い世代・確定画素への上書きがない |
| E予算と表示 | 内点判定、優先度、暫定表示 | 未到達を完了扱いしない。操作と出力で品質方針が一致する |
必要な確認は既存の2つのテストファイルへ足す。常時監査や別の昇格システムは作らない。
| ケース | 確認する差分 | 現行テストの不足 |
| --- | --- | --- |
| 目標最後の1回で脱出する単一画素 | 更新後の脱出反復が目標内に記録される。例c=1なら半径2を越えるのは3反復目 | Workerの参照は半径4で生成されるため、既存のWorker確認だけでGPUの目標境界は証明できない |
| 目標到達時の誤差超過・未脱出・参照末尾 | 誤差検査が走る。正常未脱出だけが継続可能。参照外を読まない | スモークはmetaの反復到達・この分岐を直接検査しない |
| 01636465件、奇数回偶数回のまとめ投入 | queue交換、端数lane、空queue、同じ画素の重複処理がない | 現行テストは複数pass化のqueue内容を確認しない |
| 広いcanvas・高反復・適応batch拡大 | `logicalCount×iterationBound` がカーネル別上限内、横分割で全画素を1回ずつ処理 | 現行のstrip検査は最小1行の上限超過を許容する |
| 大半が正常で1画素だけ補修、参照の原点精度変更 | 正常な進捗保持、補修画素だけの再参加、参照IDの分離 | ハッシュだけでは無駄な再計算を検出できない |
| CPU補修中のパン・再ズーム・戻る操作 | 古い世代がwriteBuffer前に破棄される。最新画面の結果だけが残る | 現行のパン往復は各回の完了を待つため、処理中の競合を検査しない |
| 同じ深部座標のFASTDeepと独立した少数画素 | 共通バグを含む一致と、独立な高精度確認を区別 | 比較関数は主に不一致画素だけをCPU確認する |
| 暫定表示中・完了後のPNG | 配列長だけでなく、同じ座標・予算の数値結果を出力する | 現行スモークは小さなRGBAタイルの長さ・checksumで、PNG全体の検証ではない |
正確性用の小ケースを先に実行し、性能ケースは初期表示とSeahorseのFASTDeepを基本にする。主カージオイド周期2円の境界変更時は `re=-0.75, im=0, span=0.02` の実軸付近を追加する。既知の黒領域問題は次の入力を固定小解像度で使用する。
```json
{
"re": "-29466147000382485924219538765424656674342940730553342389512209954887705938978413587",
"im": "5179018789925933872641942859702187326563010594882200888329237785122225251258352",
"span": "250637324516887125119843167990565159991251418774166207381437546036409",
"bits": 273,
"baseIter": 350,
"adaptive": true,
"processMode": "standard",
"renderMode": "accurate",
"fixed": true
}
```
出典はGit履歴の `tests/optimization_webgpu_corpus.mjs` にある `reported-black-regression``fixed:true` の整数座標なので10進実数として読み替えない。今回はこのケースをGPU実行していない。
## 7. 性能評価と中止条件
同じadapter、canvas画素数、座標、初期反復、最終品質方針で比較する。固定予算での実行効率と、予算を変える方式は別の結果にする。ブラウザー起動直後のコンパイル込み時間と、同じpipelineを使う描画時間も分ける。
ウォームアップ1回計測3回を最初の上限とし、中央値と全3件の値を保存する。ばらつきが大きく結論が出ない場合は「未判定」とし、同一条件が整ってから必要なケースだけ再測定する。多数の測定を自動で走らせ続けない。
追加する計測は、理由別失敗数、未到達数、GPU往復回数、readback byte数、仕事量上界、再初期化した画素数、BigInt軌道数の小さな集計に絞る。性能測定中に全画面のoracle比較を走らせない。独立した画素確認は正確性用の別実行にする。
数値補修完了時間が悪化する変更は、初回表示だけの改善と区別する。仕事量が減ってもメモリ増加や同期で時間が増える場合は、その段階の変更を採用しない。数値判定を緩めて性能値を合わせない。
GPUエラー・device loss・画素欠落・古い世代の書込みがあれば、そのケースを中止して原因を確認する。重いケースのtimeout後に、新しいEdgeを追加起動して続けない。検証用Edgeは1インスタンス・1タブ・直列実行とし、起動したプロファイルを識別して終了まで管理する。
## 8. 計画作成時点の成果と未実施事項
今回実施したのは、現行コードとの照合、旧案の誤記修正、参照キャッシュの小さな実コード確認、仕事量の数値例の再計算、実装設計の追記である。終端分岐・queue・精度の確認方法を具体化したが、新しい数値方式やスケジューラーを実装・GPU検証したわけではない。
参照サービスのfresh処理、代表点選択、予約reason、処理量上限、現行テストの限界は本文へ反映済み。期待効果は仮説として残し、改善率や任意座標での未確定ゼロを検証済みとは記載していない。新たなEdgeは起動していない。