Files
hakmem/archive/analysis/3LAYER_FAILURE_ANALYSIS.md
Moe Charm (CI) 52386401b3 Debug Counters Implementation - Clean History
Major Features:
- Debug counter infrastructure for Refill Stage tracking
- Free Pipeline counters (ss_local, ss_remote, tls_sll)
- Diagnostic counters for early return analysis
- Unified larson.sh benchmark runner with profiles
- Phase 6-3 regression analysis documentation

Bug Fixes:
- Fix SuperSlab disabled by default (HAKMEM_TINY_USE_SUPERSLAB)
- Fix profile variable naming consistency
- Add .gitignore patterns for large files

Performance:
- Phase 6-3: 4.79 M ops/s (has OOM risk)
- With SuperSlab: 3.13 M ops/s (+19% improvement)

This is a clean repository without large log files.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 12:31:14 +09:00

11 KiB
Raw Blame History

3層アーキテクチャ失敗分析 (2025-11-01)

📊 結果サマリー

実装 スループット 命令数/op 変化率
ベースライン(既存) 199.43 M ops/s ~100 -
3層 (Small Magazine) 73.17 M ops/s 221 -63%

結論: 3層アーキテクチャは完全に失敗。パフォーマンスが63%悪化


🔍 根本原因分析

問題1: ホットパスの構造変更が裏目に

既存コード(速い):

// 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層実装遅い:

// 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):

#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):

// 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):

// 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 完全バッチ化):

// 一括カーブ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