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>
11 KiB
11 KiB
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: ベンチマークで検証
誤り:
- 実装後に初めてベンチマーク → 大幅な性能悪化を発見
- リフィルだけの問題と誤解 → 実際はホットパスの問題
正解:
- 段階的実装+ベンチマーク
- P0のみ実装(既存SLL + batch carve refill)
- ベンチマーク → 改善確認
- 次のステップ(P1, P2, ...)
教訓4: 「シンプル化」の罠
誤り:
- 「6-7層 → 3層」= シンプル化 → 実際は構造的変更
- レイヤー数だけでなく、各レイヤーの実装品質が重要
正解:
- 既存の層を統合・削除するのではなく、重複を削減
- 例: BENCH_FASTPATH + HotMag + g_hot_alloc_fn は重複 → どれか1つに統一
🎯 次のステップ(推奨)
Option A: ロールバック(推奨)
理由:
- 3層実装は失敗(-63%)
- 既存コードはすでに高速(199 M ops/s)
- リスク回避
アクション:
HAKMEM_TINY_USE_NEW_3LAYER = 0のまま- 3層関連コードを削除
- ブランチを破棄
Option B: P0のみ実装(リスク中)
理由:
- ChatGPT Pro P0(完全バッチ化)には価値がある
- 既存SLLを保持すれば、パフォーマンス改善の可能性
アクション:
- Small Magazine を削除
- 既存
sll_refill_small_from_ssを P0 スタイルに書き換え - ベンチマーク → 改善確認
リスク:
- リフィル頻度が低い(1.56%)ので、改善幅は小さい可能性
- 期待値: +10-20% → 実測: +5-10% の可能性
Option C: ハイブリッド(最も安全)
理由:
- 既存コードを保持
- class 0-2 のみ特化最適化(Bump allocator)
アクション:
- 既存コード(SLL + Magazine)を保持
- class 0-2 のみ Bump allocator 追加(既存の
superslab_tls_bump_fastを活用) - 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)