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>
373 lines
11 KiB
Markdown
373 lines
11 KiB
Markdown
# 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)
|