# 3層アーキテクチャ失敗分析 (2025-11-01) ## 📊 結果サマリー | 実装 | スループット | 命令数/op | 変化率 | |------|------------|----------|-------| | **ベースライン(既存)** | 199.43 M ops/s | ~100 | - | | **3層 (Small Magazine)** | 73.17 M ops/s | 221 | **-63%** ❌ | **結論**: 3層アーキテクチャは完全に失敗。パフォーマンスが**63%悪化**。 --- ## 🔍 根本原因分析 ### 問題1: ホットパスの構造変更が裏目に #### 既存コード(速い): ```c // g_tls_sll_head を使用(単純なSLL) void* head = g_tls_sll_head[class_idx]; if (head != NULL) { g_tls_sll_head[class_idx] = *(void**)head; // ポインタ操作のみ return head; } // 4-5命令、キャッシュフレンドリー ``` #### 3層実装(遅い): ```c // g_tiny_small_mag を使用(配列ベース) TinySmallMag* mag = &g_tiny_small_mag[class_idx]; int t = mag->top; if (t > 0) { mag->top = t - 1; return mag->items[t - 1]; // 配列アクセス } // より多くの命令、インデックス計算 ``` **差分**: - SLL: ポインタ1個読み込み、ポインタ1個書き込み(2メモリアクセス) - Magazine: top読み込み、配列アクセス、top書き込み(3+メモリアクセス) - Magazine: 2048要素配列 → キャッシュラインをまたぐ可能性 ### 問題2: ChatGPT Pro の提案を誤解 **ChatGPT Pro P0の本質**: - 「SuperSlab→TLSへの**完全バッチ化**」= **リフィルの最適化** - **ホットパス自体は変えない** **私の実装の誤り**: - ❌ SLLを廃止して Small Magazine に置き換えた - ❌ ホットパスの構造を大幅変更 - ❌ 既存の最適化(BENCH_FASTPATH、g_tls_sll_head)を無効化 **正しいアプローチ**: - ✅ 既存の `g_tls_sll_head` を保持 - ✅ リフィルロジックだけバッチ化(batch carve) - ✅ ホットパスは既存のSLLポップのまま --- ## 📈 命令数の内訳分析 ### ベースライン: 100 insns/op **内訳(推定)**: - SLL hit (98%): 4-5命令 - SLL miss (2%): リフィル → ~100-200命令(償却後 ~2-4命令) - **平均**: 4-5 + 2-4 = **6-9命令/op**(実測: 100 insns/20M ops = 5 insns/op) ### 3層実装: 221 insns/op (+121%!) **内訳(推定)**: - Magazine hit (98.44%): 8-10命令(配列アクセス) - Slow path (1.56%): batch carve → ~500-1000命令(償却後 ~8-15命令) - **平均**: 8-10 + 8-15 = **16-25命令/op** - **実測**: 221 insns/op (9-14倍悪化!) **追加オーバーヘッド**: - Small Magazine 初期化チェック - Small Magazine の配列境界チェック - Batch carve の複雑なロジック(freelist + linear carve) - `ss_active_add` 呼び出し - `small_mag_batch_push` 呼び出し --- ## 🎯 なぜ既存コードが速いのか ### 1. BENCH_FASTPATH(ベンチマーク専用最適化) **コード** (`hakmem_tiny_alloc.inc:99-145`): ```c #ifdef HAKMEM_TINY_BENCH_FASTPATH void* head = g_tls_sll_head[class_idx]; if (__builtin_expect(head != NULL, 1)) { g_tls_sll_head[class_idx] = *(void**)head; if (g_tls_sll_count[class_idx] > 0) g_tls_sll_count[class_idx]--; HAK_RET_ALLOC(class_idx, head); } // Fallback: TLS Magazine TinyTLSMag* mag = &g_tls_mags[class_idx]; int t = mag->top; if (__builtin_expect(t > 0, 1)) { void* p = mag->items[--t].ptr; mag->top = t; HAK_RET_ALLOC(class_idx, p); } // Refill: sll_refill_small_from_ss if (sll_refill_small_from_ss(class_idx, bench_refill) > 0) { head = g_tls_sll_head[class_idx]; if (head) { g_tls_sll_head[class_idx] = *(void**)head; HAK_RET_ALLOC(class_idx, head); } } #endif ``` **特徴**: - ✅ SLL優先(超高速) - ✅ Magazine フォールバック - ✅ リフィルは `sll_refill_small_from_ss`(既存関数) - ✅ シンプルな2層構造(SLL → Magazine → Refill) ### 2. mimalloc スタイルの SLL **なぜSLLが速いのか**: - ポインタ操作のみ(インデックス計算なし) - フリーリストはアロケート済みメモリ内(キャッシュヒット率高い) - 分岐予測しやすい(ほぼ常にhit) ### 3. 既存のリフィルロジック `sll_refill_small_from_ss` (`hakmem_tiny_refill.inc.h:174-218`): ```c // 1個ずつループで取得(最大 max_take 個) for (int i = 0; i < take; i++) { // Freelist or linear allocation void* p = ...; *(void**)p = g_tls_sll_head[class_idx]; g_tls_sll_head[class_idx] = p; g_tls_sll_count[class_idx]++; taken++; } ``` **特徴**: - ループで1個ずつ取得(非効率だが、頻度が低い) - SLLに直接プッシュ(Magazine経由しない) --- ## ✅ ChatGPT Pro P0の正しい適用方法 ### P0の本質: 完全バッチ化 **Before (既存 `sll_refill_small_from_ss`)**: ```c // 1個ずつループ for (int i = 0; i < take; i++) { void* p = ...; // 個別取得 *(void**)p = g_tls_sll_head[class_idx]; g_tls_sll_head[class_idx] = p; g_tls_sll_count[class_idx]++; } ``` **After (P0 完全バッチ化)**: ```c // 一括カーブ(1回のループで64個) uint32_t need = 64; uint8_t* cursor = slab_base + ((size_t)meta->used * block_size); // バッチカーブ: リンクリストを一気に構築 void* head = (void*)cursor; for (uint32_t i = 1; i < need; ++i) { uint8_t* next = cursor + block_size; *(void**)cursor = (void*)next; // リンク構築 cursor = next; } void* tail = (void*)cursor; // 一括更新 meta->used += need; ss_active_add(tls->ss, need); // ← 64回 → 1回 // SLLに一括プッシュ *(void**)tail = g_tls_sll_head[class_idx]; g_tls_sll_head[class_idx] = head; g_tls_sll_count[class_idx] += need; ``` **効果**: - `ss_active_inc` を64回 → `ss_active_add` を1回 - ループ回数: 64回 → 1回 - 関数呼び出し: 64回 → 1回 **期待される改善**: - リフィルコスト: ~200-300命令 → ~50-100命令 - 全体への影響: 100 insns/op → **80-90 insns/op** (-10-20%) - スループット: 199 M ops/s → **220-240 M ops/s** (+10-20%) --- ## 🚨 失敗の教訓 ### 教訓1: 既存の最適化を尊重する **誤り**: - 「6-7層は多すぎる、3層に減らそう」→ 既存の高速パスを破壊 **正解**: - 既存の高速パス(SLL、BENCH_FASTPATH)を保持 - 遅い部分(リフィル)だけ最適化 ### 教訓2: ホットパスは触らない **誤り**: - Layer 2 として新しい Small Magazine を導入 - SLLより遅い構造に置き換え **正解**: - ホットパス(SLL pop)は現状維持 - リフィルロジックのみ改善 ### 教訓3: ベンチマークで検証 **誤り**: - 実装後に初めてベンチマーク → 大幅な性能悪化を発見 - リフィルだけの問題と誤解 → 実際はホットパスの問題 **正解**: - 段階的実装+ベンチマーク 1. P0のみ実装(既存SLL + batch carve refill) 2. ベンチマーク → 改善確認 3. 次のステップ(P1, P2, ...) ### 教訓4: 「シンプル化」の罠 **誤り**: - 「6-7層 → 3層」= シンプル化 → 実際は**構造的変更** - レイヤー数だけでなく、**各レイヤーの実装品質**が重要 **正解**: - 既存の層を統合・削除するのではなく、**重複を削減** - 例: BENCH_FASTPATH + HotMag + g_hot_alloc_fn は重複 → どれか1つに統一 --- ## 🎯 次のステップ(推奨) ### Option A: ロールバック(推奨) **理由**: - 3層実装は失敗(-63%) - 既存コードはすでに高速(199 M ops/s) - リスク回避 **アクション**: 1. `HAKMEM_TINY_USE_NEW_3LAYER = 0` のまま 2. 3層関連コードを削除 3. ブランチを破棄 ### Option B: P0のみ実装(リスク中) **理由**: - ChatGPT Pro P0(完全バッチ化)には価値がある - 既存SLLを保持すれば、パフォーマンス改善の可能性 **アクション**: 1. Small Magazine を削除 2. 既存 `sll_refill_small_from_ss` を P0 スタイルに書き換え 3. ベンチマーク → 改善確認 **リスク**: - リフィル頻度が低い(1.56%)ので、改善幅は小さい可能性 - 期待値: +10-20% → 実測: +5-10% の可能性 ### Option C: ハイブリッド(最も安全) **理由**: - 既存コードを保持 - class 0-2 のみ特化最適化(Bump allocator) **アクション**: 1. 既存コード(SLL + Magazine)を保持 2. class 0-2 のみ Bump allocator 追加(既存の `superslab_tls_bump_fast` を活用) 3. class 3+ は現状維持 **期待値**: - class 0-2: +20-30% - 全体: +10-15%(class 0-2 の割合による) --- ## 📋 技術的詳細 ### デバッグカウンター(最終テスト) ``` === 3-Layer Architecture Stats === Bump hits: 0 ( 0.00%) ← Bump未実装 Mag hits: 9843753 (98.44%) ← Magazine動作 Slow hits: 156253 ( 1.56%) ← Slow path Total allocs: 10000006 Refill count: 156253 Refill items: 9843922 (avg 63.0/refill) === Fallback Paths === SuperSlab disabled: 0 ← Batch carve動作中 No SuperSlab: 0 No meta: 0 Batch carve count: 156253 ← P0動作確認 ``` **分析**: - ✅ Batch carve は正常動作 - ✅ フォールバックなし - ❌ でもMagazine自体が遅い ### Perf統計 | Metric | Baseline | 3-Layer | 変化率 | |--------|----------|---------|--------| | **Instructions** | 2.00B | 4.43B | +121% | | **Instructions/op** | 100 | 221 | +121% | | **Cycles** | 425M | 1.06B | +149% | | **Branches** | 444M | 868M | +96% | | **Branch misses** | 0.14% | 0.11% | -21% ✅ | | **L1 misses** | 1.34M | 1.02M | -24% ✅ | **分析**: - ❌ 命令数2倍以上(+121%) - ❌ サイクル数2.5倍(+149%) - ❌ ブランチ数2倍(+96%) - ✅ Branch miss率は改善(予測しやすいコード) - ✅ L1 miss減少(局所性改善) → **キャッシュは問題ではない。命令数・分岐数が問題**。 --- ## 🤔 客観的評価 ユーザーの要求: "複雑で逆に重くなりそうなときは注意ね 客観的に判断おねがいね" **客観的判断**: - ❌ パフォーマンス: -63% (73 vs 199 M ops/s) - ❌ 命令数: +121% (221 vs 100 insns/op) - ❌ 複雑さ: 新規モジュール3個追加(Small Magazine, Bump, 新Alloc) - ❌ 保守性: 既存の最適化パスを無効化 **結論**: まさに「複雑で重くなった」ケース。**ロールバック推奨**。 --- ## 📚 参考資料 - ChatGPT Pro UltraThink Response: `docs/analysis/CHATGPT_PRO_ULTRATHINK_RESPONSE.md` - Baseline Performance: `docs/analysis/BASELINE_PERF_MEASUREMENT.md` - 3-Layer Comparison: `3LAYER_COMPARISON.md` - Existing refill code: `core/hakmem_tiny_refill.inc.h` - Existing alloc code: `core/hakmem_tiny_alloc.inc` --- **日時**: 2025-11-01 **ブランチ**: `feat/tiny-3layer-simplification` **推奨**: ロールバック(Option A)