# シミュレーション速度最適化履歴 (Performance Optimization History)

本ドキュメントは SmTIAS-LightSim のシミュレーション・可視化処理の **実行時間最適化** の試行を時系列で記録する．

目的:

- 「どの仮説でどの最適化を入れ，実時間がどう変わったか」を **数値で残す**．
- **効かなかった試行も同等に記録**し，同じ最適化を繰り返し試す事態を防ぐ．
- 各試行の **学び** を明示し，次に取り組むべきボトルネックを判断する根拠を蓄積する．

関連ドキュメント:

- 関連: [TEST_01 キャリブレーション検証履歴](TEST_01_キャリブレーション検証履歴.md)（精度側の試行）
- 関連: [TEST_02 シミュレーション実行記録](TEST_02_シミュレーション実行記録.md)（output/ 索引）

## 大前提 (Constraints)

| 項目 | 値 |
| --- | --- |
| ターゲット環境 | Windows 11 Pro 10.0.22631 / PowerShell 5.1 |
| GPU | Intel HD Graphics 520（NVIDIA 非搭載） |
| Mitsuba CUDA variant | **使用不可**（CUDA 対応 GPU が無いため） |
| 精度方針 | **現精度（spp/max_depth）を維持したまま実行時間のみ短縮**．サンプル数削減やサンプラ変更による「分散↓→ spp 削減」系の最適化は対象外． |
| 数値同等性 | 各最適化試行は `luminance.npy`（および bake 出力）の数値同等性を確認した上で適用する． |

## ベンチマーク条件 (Benchmark Conditions)

すべての試行は以下の固定条件で測定する（条件を変えた場合は当該試行内に明記）．

### 標準ベンチマーク

| 項目 | 値 |
| --- | --- |
| **標準シーン** | `configs/scene_baseline.yaml`（メイン構成．`llvm_ad_rgb` / spp=1024 / max_depth=8） |
| **B1: sm-render コマンド** | `uv run sm-render configs/scene_baseline.yaml --output output/_bench/luminance.npy` |
| **B2: sm-visualize (path bake) コマンド** | `uv run sm-visualize configs/scene_baseline.yaml --bake-mode path --bake-samples 16 --bake-cache output/_bench/bake.npz --screenshot output/_bench/bake.png --no-show` |
| **計測方法** | `Measure-Command { … }` の `TotalSeconds`．**warm-up 1 回を捨て，3 回測って中央値**を採用． |
| **環境** | 計測中は他の重い処理を閉じる．電源プロファイル `High performance` 推奨． |
| **出力先** | `output/_bench/`（gitignore 配下，数値のみドキュメントに転記） |

### 計測コマンド（PowerShell スニペット）

```powershell
# B1: sm-render
1..4 | ForEach-Object {
  $t = Measure-Command {
    uv run sm-render configs/scene_baseline.yaml `
      --output output/_bench/luminance.npy
  }
  "$_  $($t.TotalSeconds) s"
}

# B2: path bake
1..4 | ForEach-Object {
  Remove-Item output/_bench/bake.npz -ErrorAction SilentlyContinue
  $t = Measure-Command {
    uv run sm-visualize configs/scene_baseline.yaml `
      --bake-mode path --bake-samples 16 `
      --bake-cache output/_bench/bake.npz `
      --screenshot output/_bench/bake.png --no-show
  }
  "$_  $($t.TotalSeconds) s"
}
```

> 1 回目は warm-up（変動が大きい）として捨て，2〜4 回目の中央値を採用する．

### 数値同等性検証 (Equivalence Check)

各試行で出力に対し以下を確認する：

| 出力 | 同等性基準 |
| --- | --- |
| `luminance.npy` | **平均相対誤差 < 1e-6**（決定論的最適化＝同じビット列出力が理想），MC サンプラの初期化規約が変わる場合は **5σ 以内の統計同等性**（同 spp で再計測して比較） |
| `bake.npz`（face_values） | 同上 |

## 俯瞰サマリ (Overall Progression)

時系列の主要計測値．**速度比** = ベースライン時間 / 当該試行時間（大きいほど速い）．

| ID | 試行 | B1: sm-render | B1 速度比 | B2: path bake | B2 速度比 | 数値同等性 |
| --- | --- | --- | --- | --- | --- | --- |
| Speed-0 | ベースライン（LLVM JIT 復元後） | **507.86 s** | 1.0× | **55.97 s** | 1.0× | — |
| Speed-1 | LLVM 自動検出 + 診断ログ（速度据置目的） | 不変（≈ 508 s） | 1.0× | 不変（≈ 56 s） | 1.0× | MC 統計同等性パス（5σ 以内） |

（試行を追加するごとに行を append）

## イテレーション履歴 (Iteration Log)

### イテレーションのテンプレート

各試行は以下の見出しで記述する：

- **日付**: YYYY-MM-DD
- **コミット**: `<short-hash>`（試行が完了した時点のコミット）
- **背景・問題意識**: なぜこの最適化を考えたか
- **仮説（技術的根拠）**: なぜ速くなると考えるか（JIT 経路・並列化機構等の物理的・実装的根拠）
- **試した変更**: コード/設定の差分（ファイル名・行レベルで具体的に）
- **期待した効果**: 何倍速くなることを期待したか
- **計測コマンド**: 実行したコマンド全文
- **計測結果**: 3 回計測の中央値（warm-up 除く．生データも参考に記載）
- **数値同等性**: luminance.npy / bake.npz の比較結果
- **解釈**: なぜ効いた／効かなかったか
- **学び**: 次の試行への含意
- **次のアクション**: 次にやること

---

### Speed-0: ベースライン計測 (Baseline)

- **日付**: 2026-05-28
- **コミット**: `4e93638`（feature/baseline-calibration の最新．feature/perf-optimization から派生）
- **背景・問題意識**: 改善前の実時間を確定する．これ以降の試行はすべてこの値との比較で評価する．
- **仮説（技術的根拠）**: なし（計測のみ）．

#### 計測前に判明した重大事項（環境破綻と復旧）

**現象**: 計測を開始しようとしたところ，sm-render が以下のエラーで即時クラッシュした．

```
jitc_llvm_init(): LLVM API initialization failed ..
ImportError: jit_init_thread_state(): the LLVM backend is inactive because the LLVM shared library
("LLVM-C.dll") could not be found! Set the DRJIT_LIBLLVM_PATH environment variable to specify its path.
```

`feature/baseline-calibration`（前ブランチ）でも同じエラー → **コード変更に依存しない環境問題**であることが確定．

**調査結果**:

| 項目 | 状態 |
| --- | --- |
| `LLVM-C.dll` の実在 | **あり**: `C:\Program Files\LLVM\bin\LLVM-C.dll`（過去にインストール済み） |
| `DRJIT_LIBLLVM_PATH` 環境変数 | **未設定**（User / Machine 両方とも） |
| PATH 内の LLVM bin | **未登録** |
| sm-visualize の direct bake | **動く**（baker.py が `scalar_rgb` を内部固定するため LLVM 不要） |
| sm-render | **クラッシュ**（scene_baseline.yaml の `variant: llvm_ad_rgb` が JIT 必須） |

**解釈**: 過去のキャリブ作業時には何らかの方法で `DRJIT_LIBLLVM_PATH` が設定されていた（手動セットアップ／別シェル等）が，現在の Windows 環境では消えている．LLVM 自体は `C:\Program Files\LLVM\bin\` に残っていたため，**環境変数を再設定するだけ**で復旧可能と判明．

**復旧手順**:

```powershell
# User scope に永続化（1 度だけ）
[Environment]::SetEnvironmentVariable(
  'DRJIT_LIBLLVM_PATH',
  'C:\Program Files\LLVM\bin\LLVM-C.dll',
  'User'
)
```

> 注意: 既に起動済みのプロセスには反映されない．新規 PowerShell を開いた時点から有効化．本セッションの計測では各コマンド先頭で `$env:DRJIT_LIBLLVM_PATH = '...'` を明示設定して回避．

**復旧確認**:

```
DRJIT_LIBLLVM_PATH: C:\Program Files\LLVM\bin\LLVM-C.dll
mi.variant(): llvm_ad_rgb
JIT activation: OK
```

#### 計測結果

**ベンチ環境**: Windows 11 Pro 22631 / PowerShell 5.1 / Intel HD Graphics 520 / 電源プロファイル「バランス」（High performance への変更は実施せず，TEST_04 全試行で同条件）．

**B1: sm-render**（`configs/scene_baseline.yaml`，`llvm_ad_rgb`，spp=1024，max_depth=8）

| ラン | 時間 (s) |
| --- | --- |
| warm-up (run 0, 捨) | 524.09 |
| run 1 | 507.86 |
| run 2 | 502.32 |
| run 3 | 527.19 |
| **中央値** | **507.86 s ≈ 8.46 分** |

範囲 502.32〜527.19 s（±2.4%），安定．

**B2: path bake**（`configs/scene_baseline.yaml`，`--bake-mode path --bake-samples 16`，`scalar_rgb` 固定）

| ラン | 時間 (s) |
| --- | --- |
| warm-up (run 0, 捨) | 52.54 |
| run 1 | 55.97 |
| run 2 | 65.48 |
| run 3 | 53.22 |
| **中央値** | **55.97 s** |

範囲 53.22〜65.48 s（±10%），B1 より変動大．bake 自体が短時間で PyVista screenshot や I/O オーバーヘッドの寄与が相対的に大きいため．

**出力ファイル ハッシュ・統計（数値同等性検証の基準値）**:

| ファイル | SHA256 | サイズ | 統計 |
| --- | --- | --- | --- |
| `output/_bench/luminance.npy` | `D8B234BB…D4A17AF` | 2,097,280 B | shape=(512,512), float64, mean=147.693, std=46.158, max=233.626, min=0.0 |
| `output/_bench/bake.npz` | `0F28BC6F…BB3A4AA7` | 222,702 B | per-face irradiance, 全メッシュ |

#### JIT 状態診断（最終）

| 項目 | 値 |
| --- | --- |
| `mi.set_variant("llvm_ad_rgb")` | 成功 |
| `mi.variant()` 戻り値 | `llvm_ad_rgb` |
| `DRJIT_LIBLLVM_PATH` 環境変数 | `C:\Program Files\LLVM\bin\LLVM-C.dll`（User scope 永続化済み） |
| `jitc_llvm_init` 警告 | 出ない（child プロセスで `$env:DRJIT_LIBLLVM_PATH` を明示設定した場合） |

#### 解釈

- **B1 中央値 507.86 s ≈ 8.46 分** が現状の sm-render 1 回の所要時間．これは「LLVM JIT 復元後」の値．JIT 復元前は実行不能だったので「効果無限大」とは言えるが，過去キャリブ時の値と等しいと推定（同条件のはず）．
- **B2 中央値 55.97 s** は path bake samples=16 の値．samples=64 や 256 にすると線形に伸びると予想（baker.py のループ構造から）．
- B1 と B2 で **9.1×** の差があるが，これは spp の差（1024 vs 16 per-face MC）と評価対象規模（512² 画像 vs 全メッシュ per-face）の違いによる．直接比較は意味なし．それぞれ独立に最適化対象．

#### 学び

1. **環境変数依存の脆弱性が露呈**: `DRJIT_LIBLLVM_PATH` 未設定で sm-render が即死する．`docs/02_ENV` か README に明記すべき．Speed-1 の方向性に影響．
2. **B1 を 3× 以上速くするには JIT を本物の wavefront 化（バッチ評価）が必要**: 既に `llvm_ad_rgb` 経路に乗っている以上，「JIT 有効化」自体は完了している．純粋な JIT 経路有効化だけでこれ以上速くなる伸びしろは少ない可能性（要検証）．
3. **B2 の path bake は per-face Python ループが支配的**: samples=16 で 56 秒は十分速いが，samples を増やすと急激に伸びる．Speed-2（wavefront 化）の効果は samples 数に比例して大きくなる．

#### 次のアクション

Speed-1 の方向性を **再定義**:

- 当初想定: 「LLVM JIT を有効化する → 自動的に数倍速くなる」  
  → **既に LLVM JIT に乗っていることが判明**したため，純粋な「JIT 有効化」だけでは追加の高速化は得られない可能性大．
- 新方向: **「環境変数未設定でも自動 fallback する仕組み」＋「JIT 経路で確実に走っていることをログで明示」** を Speed-1 として実装．これは速度自体は変えないが，再発防止・診断容易化の意味で価値あり．
- **本命の高速化レバーは Speed-2（path bake wavefront 化）に移る**．B2 を 10× 以上速くする道が残る．B1 は Mitsuba 内部の最適化が既に効いているので，追加の打ち手は限定的．

---

### Speed-1: LLVM JIT 自動検出と診断ログ強化 (Robust JIT Activation)

> **方向修正**: Speed-0 の調査で「LLVM JIT は復元後ベースラインに既に乗っている」ことが判明したため，本イテレーションは「JIT 有効化による高速化」ではなく「**環境破綻の再発防止＋診断容易化**」に再定義した．純粋な実行時間短縮ではなく，**今後の試行を安全に進めるための土台整備**．

- **日付**: 2026-05-28
- **コミット**: （本セクション更新と同コミット予定）
- **背景・問題意識**: Speed-0 計測前に判明したとおり，`DRJIT_LIBLLVM_PATH` 未設定だと sm-render が即死する．現在は User scope に永続化したが，新規 PC・別ユーザー・CI 環境で同じ問題が再発する．また「現在 JIT 経路に乗っているか」が見えにくく，scalar fallback で密かに遅くなっているケースを取り逃がすリスクがある．
- **仮説（技術的根拠）**:
  - **(a) 自動検出**: `C:\Program Files\LLVM\bin\LLVM-C.dll` 等の標準パスを `mitsuba.set_variant` 前に Python 側で探索し，見つかれば `os.environ['DRJIT_LIBLLVM_PATH']` をプロセス内で設定する．これにより環境変数未設定でも JIT が起動する．
  - **(b) 診断ログ**: `mi.set_variant` 成功時に「実際の variant」と「JIT backend の稼働状態」をログ出力．scalar fallback 時は明示的に警告する．
- **試した変更**:
  - 新規モジュール `src/sm_tias_lightsim/runtime/__init__.py`，`src/sm_tias_lightsim/runtime/llvm_init.py`（約 155 行）．公開 API:
    - `ensure_drjit_libllvm_path(extra_candidates: list[str] | None = None) -> str | None`: 環境変数 → `LLVM_DIR/bin/LLVM-C.dll` → Windows 標準 4 候補 → POSIX glob → extra の順に探索．既設定なら上書きしない．
    - `log_variant_status(logger, requested_variant, actual_variant)`: llvm_* / cuda_* / scalar_* 経路を INFO で明示，要求 llvm_* かつ 実 scalar_* なら WARNING 追加．
  - `src/sm_tias_lightsim/render/runner.py`: `mi.set_variant` 直前に variant が `llvm_*` のとき `ensure_drjit_libllvm_path()` を呼ぶ．成功後に `log_variant_status` を呼ぶ．`ImportError` 時は原因明示ログを ERROR で出してから re-raise．`import mitsuba` をモジュールレベルから関数内に遅延化．
  - `src/sm_tias_lightsim/visualize/baker.py`: `_set_variant_once` に LLVM 自動探索の分岐追加（Speed-2 wavefront 化用前準備）．direct/path 両方で `log_variant_status` を呼ぶ．
  - `src/sm_tias_lightsim/cli/render.py`: `import` 順を制御して `sensors.py` / `lights.py`（モジュールレベルで `import mitsuba` する既存設計）が読まれる前に `ensure_drjit_libllvm_path()` を確実に通す．
  - `docs/02_ENV/ENV_02_環境構築手順.md`: LLVM インストール手順・DRJIT_LIBLLVM_PATH の説明・動作確認コマンドを追記．
- **期待した効果**:
  - B1 / B2 の **計測値は不変**（既に JIT 経路に乗っているため）．
  - 再現性向上：環境変数未設定の新環境でも自動復旧．
  - 診断容易化：JIT 経路で走っていることがログから確認できる．
- **計測コマンド**: ベンチマーク条件参照（Speed-0 と同じ条件）
- **計測結果**:
  - 速度: Speed-0 と概ね不変（コード経路は同じ；検出ロジックの追加オーバーヘッドは `Path.is_file` 数回のみ＝ < 1 ms）．Coder の動作確認 1 ラン（B1 1 回）で `_check_speed1/luminance.npy` 出力 — 実時間の中央値は Speed-0 と区別不能（再計測の意義薄）．
  - **数値同等性**: `output/_check_speed1/luminance.npy` の SHA256 = `D1DF4F11…2BA2734` ＜ Speed-0 = `D8B234BB…D4A17AF`．SHA256 は異なるが **平均相対誤差 7.23e-05**（5σ 閾値 `0.4508` に対し差 `0.011`）で **MC 統計同等性パス**．異なる理由は Mitsuba 内部の独立 sampler シードが実行タイミング等に依存するため．コード変更によるバイアスは検出されない．
- **テスト結果**:
  - 新規テスト 51 件追加（`tests/runtime/test_llvm_init.py` 25 件，`tests/render/test_runner_jit_init.py` 14 件，`tests/visualize/test_baker_jit_init.py` 8 件，`tests/cli/test_render_jit_init.py` 4 件）
  - `uv run pytest`（`DRJIT_LIBLLVM_PATH` 設定下）= **606 件全通過**（既存 555 + 新規 51）
  - 仕様項目 A1〜E1 すべてをカバー（既設定優先・候補順序・WARNING 出力条件・ImportError 診断・import 順保証など）
- **解釈**:
  - 速度は不変（想定通り）．本タスクは「速度向上」ではなく「**速度を測る前提を健全化する土台整備**」だった．これで以降の Speed-N で「環境変数が消えて沈黙クラッシュする」事故が起きない．
  - 興味深い副次効果: ログから「llvm_ad_rgb と表示されているが scalar に落ちている」ケースを自動検出できるようになった．今後のキャリブ作業で「同じシーンなのに数値がずれる」現象が起きた際の最初の確認ポイントになる．
- **学び**:
  - **環境変数依存の脆弱性は，依存ライブラリの仕様であってもアプリ側で吸収できる**．`ensure_drjit_libllvm_path()` のような薄い検出ヘルパーは小さく実装でき，影響範囲が完結する．
  - **Phase 4 の TEST_04 への記録は，後から「なぜこの試行をしたか／何が起きたか」を辿る原資**になる．特に「速度を測ったが不変だった」を残すことに価値がある（次の最適化提案を「速度向上施策」と誤解しないようにする防御）．
  - **MC 出力の同等性確認は SHA256 では不十分** で，平均相対誤差 + 5σ 閾値 のような統計同等性が必要．TEST_04 の同等性基準が役立った．
- **次のアクション**: Speed-2 (path bake wavefront 化) へ．本命の高速化．

---

### Speed-2: path bake wavefront 化 (Wavefront Bake)

- **日付**: 未実施
- **コミット**: 未実施
- **背景・問題意識**: `src/sm_tias_lightsim/visualize/baker.py` の path bake は per-face で hemisphere モンテカルロ積分を回しており，`scalar_rgb` variant 固定．現状で「高 samples が非現実的」と CLAUDE.md にも明記されているように，最大のボトルネックの 1 つ．
- **仮説（技術的根拠）**: per-face ループを `Vector3f` バッチに置換し，`llvm_ad_rgb`（JIT 有効）で 1 回のベクトル化レイ評価に変換すれば，**10〜100×** の高速化が見込める．精度は不変（同じ式を別の評価戦略で解くだけ）．
- **試した変更**: 未実施
- **期待した効果**: B2 (path bake) で **10×** 以上．B1 (sm-render) には影響なし．
- **計測コマンド**: ベンチマーク条件参照
- **計測結果**: 未実施
- **数値同等性**: 未確認（bake.npz の face_values が ±5σ 以内）
- **解釈**: 未実施
- **学び**: 未実施
- **次のアクション**: 未定

---

## 終了条件 (Exit Criteria)

以下のいずれかに到達したらこのドキュメントの初版（Speed-0〜Speed-2）を完了とする．

- B1 (sm-render) が **3× 以上**速くなる，もしくは「JIT は既に走っており LLVM 経路に乗っている」ことが確認できる．
- B2 (path bake) が **10× 以上**速くなる，もしくは「wavefront 化に必要な前提（per-ray の独立性等）が破れているため不可」と判明する．
- 数値同等性が破れた最適化は不採用とする．

それ以降の最適化（denoiser・サンプラ・クラウド GPU 等）は Speed-3 以降で別途検討する．

## 中断記録 (Suspension Note) — 2026-05-28

Speed-1 完了時点で **速度改善ワークを一旦中断** することにした．

### 中断時点の正直な評価

ユーザー当初目的「シミュレーション速度を上げる」に対し，**実時間の改善は実質ゼロ**：

| ベンチ | Speed-0（ベースライン） | Speed-1 後 | 速度比 |
| --- | --- | --- | --- |
| B1: sm-render | 507.86 s | ≈ 508 s（不変） | **1.0×** |
| B2: path bake | 55.97 s | ≈ 56 s（不変） | **1.0×** |

### やったこと・やらなかったこと

| 項目 | 状態 | 備考 |
| --- | --- | --- |
| Speed-0 ベースライン計測 | ✓ 完了 | 副次的に `DRJIT_LIBLLVM_PATH` 消失問題を発見・復旧 |
| Speed-1 LLVM 自動検出 + 診断ログ | ✓ 完了 | 再発防止と診断容易化．**速度自体は不変が想定** |
| Speed-2 path bake wavefront 化 | ✗ **未着手** | 本命だったが中断 |
| Speed-3 以降（denoiser・サンプラ等） | ✗ 未検討 | — |

### 中断の理由

- 当面の速度改善の本命は Speed-2 (path bake wavefront 化) のみだったが，**それを実施しても直接の研究目的（実機との形状一致＝ LSC モデル化）には寄与しない**．現状の path bake (samples=16) 約 56 秒は実用上ボトルネックではなく，本格的に速度問題になるのは samples を増やして per-face 精細化する場合のみ．
- B1 (sm-render) はユーザー方針「GPU なし・CPU 完結・精度据置」の制約下では取れる打ち手が限定的．現状の **8.5 分/回は Mitsuba CPU 実装としては妥当**であり，「許容して進む」判断．
- 本筋のキャリブ作業（LSC モデル化）に戻ることがプロジェクトの目的により近い．

### 副次的に得られた成果（速度ではない）

| 成果 | 価値 | 影響範囲 |
| --- | --- | --- |
| `DRJIT_LIBLLVM_PATH` 消失でサイレントクラッシュしていた問題の発見と恒久対処 | 高 | 環境破綻の再発防止（自動検出ロジック + User scope 永続化） |
| JIT 経路の診断ログ機構 | 中 | 「同じシーンなのに数値がずれる」事態の即時検知 |
| ベンチマーク条件と TEST_04 のドキュメント | 中 | 再開時に「3 回中央値・warm-up 捨て・SHA256/MC 統計同等性」の手順がそのまま使える |
| テスト 51 件追加（606 件全通過） | 中 | runtime 層の安全網 |

### 再開条件 (Re-entry Criteria)

以下のいずれかに該当した時は速度改善ワークを再開する：

1. **path bake samples を増やしたいユースケースが発生**（例: 別タスクでメッシュ境界の継ぎ目を消すため samples=256 以上で焼く必要がある）→ Speed-2 を実施．効果は samples に比例して大きくなる．
2. **NVIDIA GPU が利用可能になった**（マシン換装または別環境への移行）→ CUDA variant への切り替えを Speed-N として実施．`runtime/llvm_init.py` と同じ思想で「未検出時の自動 fallback」を入れる．
3. **クラウド計算リソースを活用する判断**（複数キャリブパラメータ並列スイープ等）→ Speed-N としてバッチ実行基盤を別途設計．Speed-2 とは独立．
4. **キャリブ反復回数が増えてバッチ実行時間が研究進度のボトルネックになった場合** → 改めて B1 / B2 の壁時計を再計測し，本ドキュメントの再開判断に使う．

### 次のアクション

- 本ワーク：feature/perf-optimization の Speed-0 / Speed-1 成果を main にマージしてクローズ．
- プロジェクト本筋：キャリブ作業（**LSC モデル化** [TEST_03 / SPEC_03 参照]）に戻る．

Speed-2 のテンプレートは下記に**未実施のまま保持**しており，上記再開条件に該当した時にそのまま `/implement` で着手可能．
