# SFC (Super Front Cache) 動作不許容原因 - 詳細分析報告書 ## Executive Summary **SFC が動作しない根本原因は「refill ロジックの未実装」です。** - **症状**: SFC_ENABLE=1 でも性能が 4.19M → 4.19M で変わらない - **根本原因**: malloc() path で SFC キャッシュを refill していない - **影響**: SFC が常に空のため、すべてのリクエストが fallback path に流れる - **修正予定工数**: 4-6時間 --- ## 1. 調査内容と検証結果 ### 1.1 malloc() SFC Path の実行流 (core/hakmem.c Line 1301-1315) #### コード: ```c if (__builtin_expect(g_sfc_enabled && g_initialized && size <= TINY_FAST_THRESHOLD, 1)) { // Step 1: size-to-class mapping int cls = hak_tiny_size_to_class(size); if (__builtin_expect(cls >= 0, 1)) { // Step 2: Pop from cache void* ptr = sfc_alloc(cls); if (__builtin_expect(ptr != NULL, 1)) { return ptr; // SFC HIT } // Step 3: SFC MISS // コメント: "Fall through to Box 5-OLD (no refill to avoid infinite recursion)" // ⚠️ **ここが問題**: refill がない } } // Step 4: Fallback to Box Refactor (HAKMEM_TINY_PHASE6_BOX_REFACTOR) #ifdef HAKMEM_TINY_PHASE6_BOX_REFACTOR if (__builtin_expect(g_initialized && size <= TINY_FAST_THRESHOLD, 1)) { int cls = hak_tiny_size_to_class(size); void* head = g_tls_sll_head[cls]; // ← 旧キャッシュ (SFC ではない) if (__builtin_expect(head != NULL, 1)) { g_tls_sll_head[cls] = *(void**)head; return head; } void* ptr = hak_tiny_alloc_fast_wrapper(size); // ← refill はここで呼ばれる if (__builtin_expect(ptr != NULL, 1)) { return ptr; } } #endif ``` #### 分析: - ✅ Step 1-2: hak_tiny_size_to_class(), sfc_alloc() は正しく実装されている - ✅ Step 2: sfc_alloc() の計算ロジックは正常 (inline pop は 3-4 instruction) - ⚠️ Step 3: **SFC MISS 時に refill を呼ばない** - ❌ Step 4: 全てのリクエストが Box Refactor fallback に流れる ### 1.2 SFC キャッシュの初期値と補充 #### 根本原因を追跡: **sfc_alloc() 実装** (core/tiny_alloc_fast_sfc.inc.h Line 75-95): ```c static inline void* sfc_alloc(int cls) { void* head = g_sfc_head[cls]; // ← TLS変数(初期値 NULL) if (__builtin_expect(head != NULL, 1)) { g_sfc_head[cls] = *(void**)head; g_sfc_count[cls]--; #if HAKMEM_DEBUG_COUNTERS g_sfc_stats[cls].alloc_hits++; #endif return head; } #if HAKMEM_DEBUG_COUNTERS g_sfc_stats[cls].alloc_misses++; // ← **常にここに到達** #endif return NULL; // ← **ほぼ 100% の確率で NULL** } ``` **問題**: - g_sfc_head[cls] は TLS 変数で、初期値は NULL - malloc() 側で refill しないので、常に NULL のまま - 結果:**alloc_hits = 0%, alloc_misses = 100%** ### 1.3 SFC refill スタブ関数の実態 **sfc_refill() 実装** (core/hakmem_tiny_sfc.c Line 149-158): ```c int sfc_refill(int cls, int target_count) { if (cls < 0 || cls >= TINY_NUM_CLASSES) return 0; if (!g_sfc_enabled) return 0; (void)target_count; #if HAKMEM_DEBUG_COUNTERS g_sfc_stats[cls].refill_calls++; #endif return 0; // ← **固定値 0** // コメント: "Actual refill happens inline in hakmem.c" // ❌ **嘘**: hakmem.c に実装がない } ``` **問題**: - 戻り値が常に 0 - hakmem.c の malloc() path から呼ばれていない - コメントは意図の説明だが、実装がない ### 1.4 DEBUG_COUNTERS がコンパイルされるか? #### テスト実行: ```bash $ make clean && make larson_hakmem EXTRA_CFLAGS="-DHAKMEM_DEBUG_COUNTERS=1" $ HAKMEM_SFC_ENABLE=1 HAKMEM_SFC_DEBUG=1 HAKMEM_SFC_STATS_DUMP=1 \ timeout 10 ./larson_hakmem 2 8 128 1024 1 12345 4 2>&1 | tail -50 ``` #### 結果: ``` [SFC] Initialized: enabled=1, default_cap=128, default_refill=64 [ELO] Initialized 12 strategies ... [Batch] Initialized ... [DEBUG] superslab_refill NULL detail: ... (OOM エラーで途中終了) ``` **結論**: - ✅ DEBUG_COUNTERS は正しくコンパイルされている - ✅ sfc_init() は正常に実行されている - ⚠️ メモリ不足で途中終了(別の問題か) - ❌ SFC 統計情報は出力されない ### 1.5 free() path の動作 **free() SFC path** (core/hakmem.c Line 911-941): ```c TinySlab* tiny_slab = hak_tiny_owner_slab(ptr); if (tiny_slab) { if (__builtin_expect(g_sfc_enabled, 1)) { pthread_t self_pt = pthread_self(); if (__builtin_expect(pthread_equal(tiny_slab->owner_tid, self_pt), 1)) { int cls = tiny_slab->class_idx; if (__builtin_expect(cls >= 0 && cls < TINY_NUM_CLASSES, 1)) { int pushed = sfc_free_push(cls, ptr); if (__builtin_expect(pushed, 1)) { return; // ✅ Push成功(g_sfc_head[cls] に追加) } // ... spill logic } } } } ``` **分析**: - ✅ free() は正しく sfc_free_push() を呼ぶ - ✅ sfc_free_push() は g_sfc_head[cls] にノードを追加する - ❌ しかし **malloc() が g_sfc_head[cls] を読まない** - 結果:free() で追加されたノードは使われない ### 1.6 Fallback Path (Box Refactor) が全リクエストを処理 **実行フロー**: ``` 1. malloc() → SFC path - sfc_alloc() → NULL (キャッシュ空) - → fall through (refill なし) 2. malloc() → Box Refactor path (FALLBACK) - g_tls_sll_head[cls] をチェック - miss → hak_tiny_alloc_fast_wrapper() → refill → superslab_refill - **この経路が 100% のリクエストを処理している** 3. free() → SFC path - sfc_free_push() → g_sfc_head[cls] に追加 - しかし malloc() が g_sfc_head を読まないので無意味 結論: SFC は「存在しないキャッシュ」状態 ``` --- ## 2. 検証結果:サイズ境界値は問題ではない ### 2.1 TINY_FAST_THRESHOLD の確認 **定義** (core/tiny_fastcache.h Line 27): ```c #define TINY_FAST_THRESHOLD 128 ``` **Larson テストのサイズ範囲**: - デフォルト: min_size=10, max_size=500 - テスト実行: `./larson_hakmem 2 8 128 1024 1 12345 4` - min_size=8, max_size=128 ✅ **結論**: ほとんどのリクエストが 128B 以下 → SFC 対象 ### 2.2 hak_tiny_size_to_class() の動作 **実装** (core/hakmem_tiny.h Line 244-247): ```c static inline int hak_tiny_size_to_class(size_t size) { if (size == 0 || size > TINY_MAX_SIZE) return -1; return g_size_to_class_lut_1k[size]; // LUT lookup } ``` **検証**: - size=1 → class=0 - size=8 → class=0 - size=128 → class=10 - ✅ すべて >= 0 (有効なクラス) **結論**: クラス計算は正常 --- ## 3. 性能データ:SFC の効果なし ### 3.1 実測値 ``` テスト条件: larson_hakmem 2 8 128 1024 1 12345 4 (min_size=8, max_size=128, threads=4, duration=2sec) 結果: ├─ SFC_ENABLE=0 (デフォルト): 4.19M ops/s ← Box Refactor ├─ SFC_ENABLE=1: 4.19M ops/s ← SFC + Box Refactor └─ 差分: 0% (全く同じ) ``` ### 3.2 理由の分析 ``` 性能が変わらない理由: 1. SFC alloc() が 100% NULL を返す → g_sfc_head[cls] が常に NULL 2. malloc() が fallback (Box Refactor) に流れる → SFC ではなく g_tls_sll_head から pop 3. SFC は「実装されているが使われていないコード」 → dead code 状態 ``` --- ## 4. 根本原因の特定 ### 最有力候補:**SFC refill ロジックが実装されていない** #### 証拠チェックリスト: | # | 項目 | 状態 | 根拠 | |---|------|------|------| | 1 | sfc_alloc() の inline pop | ✅ OK | tiny_alloc_fast_sfc.inc.h: 3-4命令 | | 2 | sfc_free_push() の実装 | ✅ OK | hakmem.c line 919: g_sfc_head に push | | 3 | sfc_init() 初期化 | ✅ OK | ログ出力: enabled=1, cap=128 | | 4 | size <= 128B フィルタ | ✅ OK | hak_tiny_size_to_class(): class >= 0 | | 5 | **SFC refill ロジック** | ❌ **なし** | hakmem.c line 1301-1315: fall through (refill呼ばない) | | 6 | sfc_refill() 関数呼び出し | ❌ **なし** | malloc() path から呼ばれていない | | 7 | refill batch処理 | ❌ **なし** | Magazine/SuperSlab から補充ロジックなし | #### 根本原因の詳細: ```c // hakmem.c Line 1301-1315 if (g_sfc_enabled && g_initialized && size <= TINY_FAST_THRESHOLD) { int cls = hak_tiny_size_to_class(size); if (cls >= 0) { void* ptr = sfc_alloc(cls); // ← sfc_alloc() は NULL を返す if (ptr != NULL) { return ptr; // ← この分岐に到達しない } // ⚠️ ここから下がない:refill ロジック欠落 // コメント: "SFC MISS: Fall through to Box 5-OLD" // 問題: fall through する = 何もしない = cache が永遠に空 } } // その後、Box Refactor fallback に全てのリクエストが流れる // → SFC は事実上「無効」 ``` --- ## 5. 設計上の問題点 ### 5.1 Box Theory の過度な解釈 **設計意図**(コメント): ``` "Box 5-NEW never calls lower boxes on alloc" "This maintains clean Box boundaries" ``` **実装結果**: - refill を呼ばない - → キャッシュが永遠に空 - → SFC は never hits **問題**: - 無限再帰を避けるなら、refill深度カウントで制限すべき - 「全く refill しない」は過度に保守的 ### 5.2 スタブ関数による実装遅延 **sfc_refill() の実装状況**: ```c int sfc_refill(int cls, int target_count) { ... return 0; // ← Fixed zero } // コメント: "Actual refill happens inline in hakmem.c" // しかし hakmem.c に実装がない ``` **問題**: - コメントだけで実装なし - スタブ関数が fixed zero を返す - 呼ばれていない ### 5.3 テスト不足 **テストの盲点**: - SFC_ENABLE=1 でも性能が変わらない - → SFC が動作していないことに気づかなかった - 本来なら性能低下 (fallback cost) か性能向上 (SFC hit) かのどちらか --- ## 6. 詳細な修正方法 ### Phase 1: SFC refill ロジック実装 (推定4-6時間) #### 目標: - SFC キャッシュを定期的に補充 - Magazine または SuperSlab から batch refill - 無限再帰防止: refill_depth <= 1 #### 実装案: ```c // core/hakmem.c - malloc() に追加 if (__builtin_expect(g_sfc_enabled && g_initialized && size <= TINY_FAST_THRESHOLD, 1)) { int cls = hak_tiny_size_to_class(size); if (__builtin_expect(cls >= 0, 1)) { // Try SFC fast path void* ptr = sfc_alloc(cls); if (__builtin_expect(ptr != NULL, 1)) { return ptr; // SFC HIT } // SFC MISS: Refill from Magazine // ⚠️ **新しいロジック**: int refill_count = 32; // batch size int refilled = sfc_refill_from_magazine(cls, refill_count); if (refilled > 0) { // Retry after refill ptr = sfc_alloc(cls); if (__builtin_expect(ptr != NULL, 1)) { return ptr; // SFC HIT (after refill) } } // Refill failed or retried: fall through to Box Refactor } } ``` #### 実装ステップ: 1. **Magazine refill ロジック** - Magazine から free blocks を抽出 - SFC キャッシュに追加 - 実装場所: hakmem_tiny_magazine.c または hakmem.c 2. **Cycle detection** ```c static __thread int sfc_refill_depth = 0; if (sfc_refill_depth > 1) { // Too deep, avoid infinite recursion goto fallback; } sfc_refill_depth++; // ... refill logic sfc_refill_depth--; ``` 3. **Batch size tuning** - 初期値: 32 blocks per class - Environment variable で調整可能 ### Phase 2: A/B テストと検証 (推定2-3時間) ```bash # SFC OFF HAKMEM_SFC_ENABLE=0 ./larson_hakmem 2 8 128 1024 1 12345 4 # 期待: 4.19M ops/s (baseline) # SFC ON HAKMEM_SFC_ENABLE=1 ./larson_hakmem 2 8 128 1024 1 12345 4 # 期待: 4.6-4.8M ops/s (+10-15% improvement) # Debug dump HAKMEM_SFC_ENABLE=1 HAKMEM_SFC_STATS_DUMP=1 \ ./larson_hakmem 2 8 128 1024 1 12345 4 2>&1 | grep "SFC Statistics" -A 20 ``` #### 期待される結果: ``` === SFC Statistics (Box 5-NEW) === Class 0 (16 B): allocs=..., hit_rate=XX%, refills=..., cap=128 ... === SFC Summary === Total allocs: ... Overall hit rate: >90% (target) Refill frequency: <0.1% (target) Refill calls: ... ``` ### Phase 3: 自動チューニング (オプション、2-3日) ```c // Per-class hotness tracking struct { uint64_t alloc_miss; uint64_t free_push; double miss_rate; // miss / push int hotness; // 0=cold, 1=warm, 2=hot } sfc_class_info[TINY_NUM_CLASSES]; // Dynamic capacity adjustment if (sfc_class_info[cls].hotness == 2) { // hot increase_capacity(cls); // 128 → 256 increase_refill_count(cls); // 64 → 96 } ``` --- ## 7. リスク評価と推奨アクション ### リスク分析 | リスク | 確度 | 影響 | 対策 | |--------|------|------|------| | Infinite recursion | 中 | crash | refill_depth counter | | Performance regression | 低 | -5% | fallback path は生きている | | Memory overhead | 低 | +KB | TLS cache 追加 | | Fragmentation increase | 低 | +% | magazine refill と相互作用 | ### 推奨アクション **優先度1(即実施)** - [ ] Phase 1: SFC refill 実装 (4-6h) - [ ] refill_from_magazine() 関数追加 - [ ] cycle detection ロジック追加 - [ ] hakmem.c の malloc() path 修正 **優先度2(その次)** - [ ] Phase 2: A/B test (2-3h) - [ ] SFC_ENABLE=0 vs 1 性能比較 - [ ] DEBUG_COUNTERS で統計確認 - [ ] メモリオーバーヘッド測定 **優先度3(将来)** - [ ] Phase 3: 自動チューニング (2-3d) - [ ] Hotness tracking - [ ] Per-class adaptive capacity --- ## 8. 付録:完全なコード追跡 ### malloc() Call Flow ``` malloc(size) ↓ [1] g_sfc_enabled && g_initialized && size <= 128? YES ↓ [2] cls = hak_tiny_size_to_class(size) ✅ cls >= 0 [3] ptr = sfc_alloc(cls) ❌ return NULL (g_sfc_head[cls] is NULL) [3-END] Fall through ❌ No refill! ↓ [4] #ifdef HAKMEM_TINY_PHASE6_BOX_REFACTOR YES ↓ [5] cls = hak_tiny_size_to_class(size) ✅ cls >= 0 [6] head = g_tls_sll_head[cls] ✅ YES (初期値あり) ✓ RETURN head OR ❌ NULL → hak_tiny_alloc_fast_wrapper() → Magazine/SuperSlab refill ↓ [RESULT] 100% of requests processed by Box Refactor ``` ### free() Call Flow ``` free(ptr) ↓ tiny_slab = hak_tiny_owner_slab(ptr) ✅ found ↓ [1] g_sfc_enabled? YES ↓ [2] same_thread(tiny_slab->owner_tid)? YES ↓ [3] cls = tiny_slab->class_idx ✅ valid (0 <= cls < TINY_NUM_CLASSES) [4] pushed = sfc_free_push(cls, ptr) ✅ Push to g_sfc_head[cls] [RETURN] ← **但し malloc() がこれを読まない** OR ❌ cache full → sfc_spill() NO → [5] Cross-thread path ↓ [RESULT] SFC に push されるが活用されない ``` --- ## 結論 ### 最終判定 **SFC が動作しない根本原因: malloc() path に refill ロジックがない** 症状と根拠: 1. ✅ SFC 初期化: sfc_init() は正常に実行 2. ✅ free() path: sfc_free_push() も正常に実装 3. ❌ **malloc() refill: 実装されていない** 4. ❌ sfc_alloc() が常に NULL を返す 5. ❌ 全リクエストが Box Refactor fallback に流れる 6. ❌ 性能: SFC_ENABLE=0/1 で全く同じ (0% improvement) ### 修正予定 | Phase | 作業 | 工数 | 期待値 | |-------|------|------|--------| | 1 | refill ロジック実装 | 4-6h | SFC が動作開始 | | 2 | A/B test 検証 | 2-3h | +10-15% 確認 | | 3 | 自動チューニング | 2-3d | +15-20% 到達 | ### 今すぐできること 1. **応急処置**: `make larson_hakmem` 時に `-DHAKMEM_SFC_ENABLE=0` を固定 2. **詳細ログ**: `HAKMEM_SFC_DEBUG=1` で初期化確認 3. **実装開始**: Phase 1 refill ロジック追加