Phase 11: ENV Snapshot maybe-fast API - NO-GO (-8.35%)

Phase 11 attempted to consolidate ENV snapshot overhead by:
- Adding hakmem_env_snapshot_maybe_fast() API
- Caching front_v3_snap pointer in HakmemEnvSnapshot
- Replacing separate calls with single API at call sites

Result: -8.35% regression (51.65M → 47.33M ops/s)

Root cause:
- maybe_fast() called in inline hot path functions
- ctor_mode check accumulated on every call
- Compiler optimization inhibited
- Even 2-3 instructions are expensive at high frequency

Lesson: ENV gate optimization should target gate itself, not call sites.

All changes rolled back. Phase 11 FROZEN.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Moe Charm (CI)
2025-12-14 20:44:42 +09:00
parent 44273693cc
commit ad73ca5544

View File

@ -0,0 +1,206 @@
# Phase 11: ENV Snapshot "maybe-fast" API - A/B テスト結果
## 判定: **NO-GO** (-8.35%)
Phase 11 の実装は **大幅な性能低下 (-8.35%)** を引き起こしたため、**rollback 済み**。
---
## 0. 目的Phase 11
Phase 10 までで `free_tiny_fast()` 側の policy/route 固定費は削減されたが、PerfPhase 10 後, Mixedでは次が目立っていた:
- `hakmem_env_snapshot` / `hakmem_env_snapshot_enabled`(合計 ~2%
- `tiny_front_v3_snapshot_get`~1%
**Phase 11 の狙い**: ENV gate を維持したまま、hot path の参照回数を 1 回に集約し、同時に `tiny_front_v3_snapshot_get()` の ready-check を hot から消すこと。
**設計**:
1. `hakmem_env_snapshot_maybe_fast()` API を追加enabled + snapshot を 1 回で取得)
2. `HakmemEnvSnapshot``front_v3_snap` ポインタをキャッシュready-check を消す)
3. hot call sites (`tiny_legacy_fallback_box.h`, `malloc_tiny_fast.h`, `tiny_metadata_cache_hot_box.h`) を maybe-fast API に移行
---
## 1. A/B テスト結果Mixed 10-run, clean env
### Baseline (Phase 11 前)
```
Run 1/10: 52.40 M ops/s
Run 2/10: 51.44 M ops/s
Run 3/10: 52.21 M ops/s
Run 4/10: 51.94 M ops/s
Run 5/10: 51.49 M ops/s
Run 6/10: 50.86 M ops/s
Run 7/10: 51.59 M ops/s
Run 8/10: 51.38 M ops/s
Run 9/10: 51.29 M ops/s
Run 10/10: 51.90 M ops/s
Mean: 51.65 M ops/s
Median: 51.54 M ops/s
Stdev: 0.46 M ops/s
```
### Phase 11 後(最終版, CTOR=1
```
Run 1/10: 47.56 M ops/s
Run 2/10: 47.45 M ops/s
Run 3/10: 47.62 M ops/s
Run 4/10: 47.97 M ops/s
Run 5/10: 47.59 M ops/s
Run 6/10: 45.89 M ops/s
Run 7/10: 47.96 M ops/s
Run 8/10: 47.23 M ops/s
Run 9/10: 47.33 M ops/s
Run 10/10: 46.72 M ops/s
Mean: 47.33 M ops/s
Median: 47.51 M ops/s
Stdev: 0.62 M ops/s
```
### 性能差分
| 指標 | Phase 11 前 | Phase 11 後 | 差分 |
|------|-------------|-------------|------|
| 平均 | 51.65 M ops/s | 47.33 M ops/s | **-4.32 M ops/s (-8.35%)** |
| 中央値 | 51.54 M ops/s | 47.51 M ops/s | -4.03 M ops/s (-7.82%) |
| 標準偏差 | 0.46 M ops/s | 0.62 M ops/s | +0.16 M ops/s |
**判定**: **NO-GO** (目標: +1.0% 以上、実際: **-8.35%**)
---
## 2. 根本原因分析
### 2.1 設計の致命的な欠陥
Phase 11 の `hakmem_env_snapshot_maybe_fast()` API は、以下の理由で **逆に遅くなった**:
1. **ctor_mode チェックのオーバーヘッド**
- 既存の `hakmem_env_snapshot_enabled()` は **1 回だけ**呼ばれ、結果が分岐に使われる
- Phase 11 では、`maybe_fast()`**複数の call site で毎回**呼ぶため、**ctor_mode チェックが累積**
- 特に `tiny_legacy_fallback_free_base()` は free path で **毎回**呼ばれるため、オーバーヘッドが顕著
2. **call site の変更による最適化の阻害**
- 元のコード: `if (hakmem_env_snapshot_enabled()) { ... } else { legacy }`
- コンパイラが分岐予測を最適化しやすいenabled==0 が大半)
- Phase 11: `env = hakmem_env_snapshot_maybe_fast(); if (env) { ... } else { legacy }`
- `maybe_fast()` が **無条件に呼ばれる**ため、gate チェックが毎回発生
- `ctor_mode` の読み取りが毎回発生TLS read または global read
3. **HAKMEM_ENV_SNAPSHOT_CTOR の未設定**
- 初期実装では `HAKMEM_ENV_SNAPSHOT_CTOR=1` をプロファイルに追加していなかった
- `ctor_mode == -1` のとき、lazy init path が毎回走るgetenv 呼び出しは不要だが、gate チェックは発生)
- `CTOR=1` を追加しても性能は改善せず(-8.35%
### 2.2 試行錯誤の履歴
1. **Version 1**: `ctor_mode` チェックなし、gate チェックのみ
- 結果: -7.6% (NO-GO)
- 原因: gate チェックが毎回発生
2. **Version 2**: `ctor_mode` チェックなし、gate==0 を LIKELY に設定
- 結果: -8.6% (NO-GO)
- 原因: 分岐予測ヒントが MIXED プロファイルENV_SNAPSHOT=1で逆効果
3. **Version 3**: `ctor_mode` チェックを復活(既存の `hakmem_env_snapshot_enabled()` と同等)
- 結果: -5.9% (NO-GO)
- 原因: `ctor_mode` チェック自体がオーバーヘッド
4. **Final**: `HAKMEM_ENV_SNAPSHOT_CTOR=1` をプロファイルに追加
- 結果: **-8.35% (NO-GO)**
- 原因: `ctor_mode==1` でも、**`maybe_fast()` 呼び出し自体がオーバーヘッド**
---
## 3. 正しいアプローチPhase 11 では不可能)
Phase 11 の設計は **根本的に誤り**。正しくは:
1. **`maybe_fast()` を関数の入口で 1 回だけ呼ぶ**
- 複数の call site で再利用する(ローカル変数にキャッシュ)
2. **しかし、`tiny_legacy_fallback_free_base()` は free path で毎回呼ばれる**
- そこで `maybe_fast()` を呼ぶと、毎回 `ctor_mode` チェックが発生
- 既存のコードでは、`hakmem_env_snapshot_enabled()` が **1 回だけ**呼ばれる設計になっている
3. **結論**: **Phase 11 の maybe-fast API は構造的に不可能**
- ENV snapshot 参照の固定費削減は、**call site の変更ではなく、ENV snapshot 自体の最適化**(例: TLS → register cacheが必要
---
## 4. Perf 確認(未実施)
Phase 11 が -8.35% の大幅な性能低下を引き起こしたため、perf 確認は実施せず。
---
## 5. 健康診断(未実施)
NO-GO のため、健康診断は実施せず。
---
## 6. Rollback 状況
Phase 11 の全変更を rollback 済み:
```bash
git checkout -- core/bench_profile.h core/box/hakmem_env_snapshot_box.c \
core/box/hakmem_env_snapshot_box.h core/box/tiny_legacy_fallback_box.h \
core/box/tiny_metadata_cache_hot_box.h core/front/malloc_tiny_fast.h
```
---
## 7. 次のステップ
Phase 11 は **設計ミス** のため、放棄。
ENV snapshot 参照の固定費削減(~2-3%)を狙う場合、以下の代替アプローチを検討:
1. **ENV snapshot を TLS → レジスタにキャッシュ**
- 関数入口で 1 回だけ TLS read し、関数内で再利用
- ただし、`tiny_legacy_fallback_free_base()` などの **inline 関数では効果が薄い**
2. **ENV snapshot をコンストラクタで確定**
- `HAKMEM_ENV_SNAPSHOT_CTOR=1` を常に有効化
- gate を **const に昇格**ただし、ENV の動的変更が不可能になる)
3. **ENV snapshot 自体を廃止し、個別 gate に戻す**
- Phase 4 E1 の ENV snapshot は +3.92% の効果があったが、その後のフェーズでオーバーヘッドが累積している可能性
- ただし、rollback は大規模
4. **別の最適化ターゲットを探す**
- Perf の最新プロファイルで、次の高コストパスを特定
---
## 8. 教訓
1. **inline 関数で毎回呼ばれる helper は、1 命令でも重い**
- `hakmem_env_snapshot_maybe_fast()` は 2-3 命令だが、free path で毎回呼ばれると -8% の劣化
2. **ENV gate の最適化は、call site ではなく gate 自体で行うべき**
- call site を変更すると、コンパイラの最適化を阻害する可能性が高い
3. **Constructor init (CTOR=1) は万能ではない**
- gate が確定しても、**呼び出しコスト**は残る
4. **A/B テストは必須**
- Phase 11 は "理論的には速くなるはず" だったが、実測で -8.35% の劣化
- 小さな変更でも、必ず A/B テストで検証すること
---
## まとめ
Phase 11 の ENV Snapshot "maybe-fast" API は、**-8.35% の大幅な性能低下**を引き起こし、**NO-GO** と判定。
**原因**: `maybe_fast()` 呼び出しが free path で毎回発生し、`ctor_mode` チェックのオーバーヘッドが累積。
**結論**: Phase 11 は**設計ミス**であり、rollback 済み。ENV snapshot 参照の固定費削減は、別のアプローチTLS → register cache, constructor 確定など)を検討する必要がある。