Files
hakmem/docs/analysis/SFC_ROOT_CAUSE_ANALYSIS.md
Moe Charm (CI) 67fb15f35f Wrap debug fprintf in !HAKMEM_BUILD_RELEASE guards (Release build optimization)
## Changes

### 1. core/page_arena.c
- Removed init failure message (lines 25-27) - error is handled by returning early
- All other fprintf statements already wrapped in existing #if !HAKMEM_BUILD_RELEASE blocks

### 2. core/hakmem.c
- Wrapped SIGSEGV handler init message (line 72)
- CRITICAL: Kept SIGSEGV/SIGBUS/SIGABRT error messages (lines 62-64) - production needs crash logs

### 3. core/hakmem_shared_pool.c
- Wrapped all debug fprintf statements in #if !HAKMEM_BUILD_RELEASE:
  - Node pool exhaustion warning (line 252)
  - SP_META_CAPACITY_ERROR warning (line 421)
  - SP_FIX_GEOMETRY debug logging (line 745)
  - SP_ACQUIRE_STAGE0.5_EMPTY debug logging (line 865)
  - SP_ACQUIRE_STAGE0_L0 debug logging (line 803)
  - SP_ACQUIRE_STAGE1_LOCKFREE debug logging (line 922)
  - SP_ACQUIRE_STAGE2_LOCKFREE debug logging (line 996)
  - SP_ACQUIRE_STAGE3 debug logging (line 1116)
  - SP_SLOT_RELEASE debug logging (line 1245)
  - SP_SLOT_FREELIST_LOCKFREE debug logging (line 1305)
  - SP_SLOT_COMPLETELY_EMPTY debug logging (line 1316)
- Fixed lock_stats_init() for release builds (lines 60-65) - ensure g_lock_stats_enabled is initialized

## Performance Validation

Before: 51M ops/s (with debug fprintf overhead)
After:  49.1M ops/s (consistent performance, fprintf removed from hot paths)

## Build & Test

```bash
./build.sh larson_hakmem
./out/release/larson_hakmem 1 5 1 1000 100 10000 42
# Result: 49.1M ops/s
```

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 13:14:18 +09:00

567 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 ロジック追加