Phase FREE-DISPATCHER-OPT-1: free dispatcher 統計計測

**目的**: free dispatcher(29%)の内訳を細分化して計測。

**実装内容**:
- FreeDispatchStats 構造体追加(ENV: HAKMEM_FREE_DISPATCH_STATS, default 0)
- カウンタ: total_calls / domain (tiny/mid/large) / route (ultra/legacy/pool/v6) / env_checks / route_for_class_calls
- hak_free_at / tiny_route_for_class / tiny_route_snapshot_init にカウンタ埋め込み
- 挙動変更なし(計測のみ、ENV OFF 時は overhead ゼロ)

**計測結果**:

Mixed 16-1024B (1M iter, ws=400):
- total=8,081, route_calls=267,967, env_checks=9
- BENCH_FAST_FRONT により大半は早期リターン
- route_for_class は主に alloc 側で呼ばれる(267k calls vs 8k frees)
- ENV check は初期化時の 9回のみ(snapshot 効果)

C6-heavy (257-768B, 1M iter, ws=400):
- total=500,099, route_calls=1,034, env_checks=9
- fg_classify_domain に到達する free が多い
- route_for_class 呼び出しは極小(snapshot 効果)

**結論**:
- ENV check は既に十分最適化されている(初期化時のみ)
- route_for_class は alloc 側での呼び出しが主で、free 側は snapshot で O(1)
- 次フェーズ(OPT-2)では別のアプローチを検討

**ドキュメント追加**:
- docs/analysis/FREE_DISPATCHER_ANALYSIS.md(新規)
- CURRENT_TASK.md に Phase FREE-DISPATCHER-OPT-1 セクション追加

🤖 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-11 21:21:40 +09:00
parent 11dc9d390a
commit 118c0e4857
15 changed files with 647 additions and 6 deletions

View File

@ -0,0 +1,115 @@
# FREE_DISPATCHER_ANALYSIS
## 目的
free dispatcherhak_free_at → fg_classify_domain → fg_tiny_gate → ...)のルーティング層を分析し、最適化ポイントを特定する。
## 現状フロー
```
free(ptr)
hak_free_at(ptr, sz)
fg_classify_domain(ptr, sz)
├→ FG_DOMAIN_TINY → fg_tiny_gate → tiny_free_gate_try_fast
├→ FG_DOMAIN_MID → mid free path
└→ FG_DOMAIN_LARGE → large free path
```
### tiny domain 内のルート分岐
```
tiny_free_gate_try_fast(base, class_idx)
tiny_route_for_class(class_idx) を呼び出し
├→ ULTRA (C4-C7) → tiny_cX_ultra_free_fast()
├→ Tiny legacy → existing tiny free
├→ pool v1 → pool free
└→ core v6 (C6) → small_free_fast_v6()
```
### ENV 判定箇所(推定)
- tiny_route_for_class() 内部
- HAKMEM_SMALL_HEAP_V6_ENABLED
- HAKMEM_SMALL_HEAP_V3_ENABLED
- HAKMEM_POOL_V2_ENABLED / V1_ENABLED
- HAKMEM_TINY_CX_ULTRA_FREE_ENABLED (C4/C5/C6/C7)
- fg_classify_domain() 内部
- サイズ閾値判定tiny/mid/large 分岐)
## Phase FREE-DISPATCHER-OPT-1: 統計計測
実装内容:
- FreeDispatchStats 構造体ENV: HAKMEM_FREE_DISPATCH_STATS, default 0
- カウンタ:
- total_calls: free 全体呼び出し回数
- domain_tiny / domain_mid / domain_large: domain 分岐の内訳
- route_ultra / route_tiny_legacy / route_pool_v1 / route_core_v6: tiny domain 内の route 内訳
- env_checks: ENV 読み回数(概算)
- route_for_class_calls: tiny_route_for_class() 呼び出し回数
計測結果は次のセクションに記載。
## Phase FREE-DISPATCHER-OPT-1 計測結果
### Mixed 16-1024B (1M iter, ws=400)
```
[FREE_DISPATCH_STATS] total=8081 tiny=0 mid=8081 large=0 ultra=0 tiny_legacy=7 pool=0 v6=0 route_calls=267967 env_checks=9
Throughput = 44.7M ops/s
```
**分析**:
- total_calls: 8,081 (hak_free_at 呼び出し回数、BENCH_FAST_FRONT で早期リターンしている分を除く)
- domain_mid: 100% (8,081/8,081) - BENCH_FAST_FRONT により tiny domain は早期リターン済み
- route_for_class_calls: 267,967 (alloc 側でも呼ばれるため total より大きい)
- env_checks: 9 (tiny_route_snapshot_init() で 1回だけ ENV を読む設計が機能)
**重要な発見**:
1. BENCH_FAST_FRONT により大半の free は早期リターンしており、fg_classify_domain に到達するのは少数のみ
2. route_for_class は主に alloc 側で呼ばれている267k calls vs 8k frees
3. ENV check は初期化時の 9回のみで、ホットパスでは完全に排除されている
### C6-heavy (257-768B, 1M iter, ws=400)
```
[FREE_DISPATCH_STATS] total=500099 tiny=0 mid=500099 large=0 ultra=0 tiny_legacy=7 pool=0 v6=0 route_calls=1034 env_checks=9
```
**分析**:
- total_calls: 500,099 (1M operations の約半分が free)
- domain_mid: 100% (C6 は mid domain)
- route_for_class_calls: 1,034 (Mixed より大幅に少ない、C6-heavy では alloc が単純)
- env_checks: 9 (同様に初期化時のみ)
**コメント**:
- C6-heavy では BENCH_FAST_FRONT の効果が少なく、大半の free が fg_classify_domain に到達
- route_for_class の呼び出しが非常に少ない1k callsのは、C6 が既に snapshot 済みで LUT lookup のみで完結しているため
## Phase FREE-DISPATCHER-OPT-2 候補施策
### 候補 A: tiny domain 早期 return
**条件**: tiny domain が 90% 以上
**施策**: fg_classify_domain から tiny 判定を早期 return にし、mid/large 分岐を cold に追いやる
**期待**: domain 判定コスト削減、分岐予測向上
### 候補 B: route snapshot 化
**条件**: route_for_class_calls ≈ total_calls毎回呼ばれている
**施策**: route_kind[class_idx] を snapshot にして、free では table lookup のみ
**期待**: ENV check 削減、route 判定の O(1) 化
### 候補 C: mid/pool free gate 最適化
**条件**: mid domain がそこそこある10% 以上)
**施策**: mid/pool の free gate 分岐も route snapshot 化
**期待**: C6-heavy での改善
### 判断基準
- tiny > 90% → 候補 A + B
- route_calls ≈ total → 候補 B 優先
- mid > 10% → 候補 C も検討

View File

@ -237,3 +237,75 @@ Throughput: **12.39M ops/s**DEBUG/-O0 相当)
- ULTRA free 群も5.41%と無視できないがalloc が約1.4倍重いためalloc を先に削るのが効率的
- gate/front 前段は以前のフェーズより改善されており現時点での優先度は下がった
- header 書き込みが上位20に入っていないのはULTRA 経路での削減効果が出ている可能性がある
## Phase PERF-ULTRA-REBASE-2: C4-C7 ULTRA free最適化後の hotpath分析 (2025-12-11)
### 計測条件
- **ENV**: `HAKMEM_PROFILE=MIXED_TINYV3_C7_SAFE`
- **ULTRA**: C4-C7 全て ON
- **v4/v5/v6/free-front-v3**: OFF
- **ワークロード**: Mixed 16-1024B (1M iter, ws=400)
- **perf コマンド**: `perf record -F 5000 --call-graph dwarf -e cycles:u`
- **Throughput**: **42.63M ops/s**前回 31.61M から大幅改善ws=400 vs 8192の差
### Mixed 16-1024B ホットパスself% 上位)
| 順位 | 関数 | self% | 分類 |
|------|------|-------|------|
| #1 | free | 27.88% | ベンチマーク wrapper |
| #2 | tiny_alloc_gate_fast | 23.57% | alloc gate/front |
| #3 | main | 17.66% | ベンチマーク harness |
| #4 | malloc | 6.94% | ベンチマーク wrapper |
| #5 | tiny_region_id_write_header.lto_priv.0 | 5.30% | header 書き込み |
| #6 | tiny_region_id_write_header.constprop.0 | 2.77% | header 書き込み |
| #7 | hak_super_lookup.lto_priv.1.lto_priv.0 | 0.69% | Superslab 判定 |
| #8 | hak_pool_free | 0.64% | pool free |
### 分析コメント
**WARNING: サンプル数が極めて少ない238 samples**
- perf 計測時のサンプル数が238と非常に少なく統計的信頼性が低い
- ベンチマーク時間が0.023s23msと極めて短くiters=1M / ws=400 の組み合わせでワークロードが軽すぎた可能性
- 結果としてallocator 本体のホットパスULTRA alloc/freeがほとんど可視化されていない
**可視化されたボトルネック**:
- **tiny_alloc_gate_fast**: 23.57% - alloc gate/front 入口が最大のホットスポット
- **header 書き込み合計**: 8.07%lto_priv.0: 5.30% + constprop.0: 2.77%
- **ULTRA 関連関数が不可視**: tiny_c7_ultra_alloc ULTRA free 群が上位20に入っていない
- **so_alloc/so_free も不可視**: v3 backend 処理も検出されず
**前フェーズPERF-ULTRA-REBASE-1との比較**:
- 前回ws=8192, iters=10M: C7 ULTRA alloc 7.66%, ULTRA free群 5.41%, so_alloc 3.60%
- 今回ws=400, iters=1M: これらが全て不可視< 0.63%
- **サンプル数**: 前回1842 samples vs 今回238 samples約1/8
**結論**:
- **計測条件の再検討が必要**: ワークロードを重くするiters 10M に増やすまたは ws を拡大
- 現状の結果では次の最大ボトルネックを確定できない
- gate/front23.57% header8.07%が可視範囲での上位だがこれは軽すぎるワークロードで allocator 本体が見えていない可能性が高い
### C6-heavy ホットパスself% 上位)
**計測条件**:
- **ENV**: `HAKMEM_PROFILE=C6_HEAVY_LEGACY_POOLV1`
- **Throughput**: **26.49M ops/s**
- **Samples**: 292 samplesやはり少ない
| 順位 | 関数 | self% | 分類 |
|------|------|-------|------|
| #1 | hak_pool_free_v1_impl.part.0 | 19.89% | pool v1 free |
| #2 | hak_pool_try_alloc_v1_impl.part.0 | 14.46% | pool v1 alloc |
| #3 | free | 11.80% | ベンチマーク wrapper |
| #4 | hak_free_at.constprop.0 | 9.26% | 解放処理 |
| #5 | pthread_once@GLIBC_2.2.5 | 8.21% | 初期化同期 |
| #6 | hak_super_lookup.lto_priv.1.lto_priv.0 | 7.07% | Superslab 判定 |
| #7 | malloc | 6.81% | ベンチマーク wrapper |
| #8 | __GI___pthread_self | 2.62% | TLS アクセス |
| #9 | hak_pool_free | 1.08% | pool free wrapper |
**分析**:
- **pool v1 が支配的**: alloc 14.46% + free 19.89% = 34.35%
- **ULTRA は可視化されず**: C6/C5/C4 ULTRA 関数が上位に入っていない
- **pthread_once が8.21%**: 初期化同期のオーバーヘッドが目立つワークロードが軽い証拠
**所感**: C6-heavy でも pool v1 が主要経路として機能しているがULTRA の効果測定には不十分なサンプル数