Files
hakmem/docs/analysis/V7_ARCHITECTURE_DECISION_MATRIX.md

470 lines
16 KiB
Markdown
Raw Normal View History

# V7 Architecture Decision Matrix
**Date**: 2025-12-12
**Purpose**: mimalloc 競争力評価 + v7-5 方向性の決定基準を明確化
---
## 1. mimalloc vs HAKMEM v7 比較表
### 1-1. アーキテクチャ比較
| 要素 | mimalloc | HAKMEM v7 (C6-only) | Gap | 対策 |
|------|----------|---------------------|-----|------|
| **Free List 構造** | Intrusive LIFO (ブロック内 next ptr) | Page metadata explicit freelist | **-2~3%** | Intrusive LIFO 採用検討 |
| **Size Class 数** | 128 (fine-grained) | 8 (C0-C7) | **-1~2%** | 現状維持 (overhead 分摊で対応) |
| **Page Size** | 8KB (L1 cache fit) | 64KB (ULTRA 互換) | **±0%** | tradeoff 異なる (検証必要) |
| **Segment Size** | 4MB on-demand | 2MB fixed | **±0%** | 同等 |
| **Lookup Cost** | Direct array O(1) | RegionIdBox binary search O(log N) | **-1~2%** | TLS fast path で緩和済み |
| **Header** | なし (headerless) | 薄く残す (1 byte) | **-0.5%** | v6 headerless パターン適用検討 |
| **TLS Access** | 1 read + array index | TLS bounds check + page_meta | **-1%** | TLS hint 最適化済み |
| **Remote Free** | Lock-free MPSC | Lock-free MPSC (同等) | **±0%** | 同等 |
| **Stats Overhead** | Batched/deferred | Atomic increment on hot path | **-0.5~1%** | Stats を cold path へ移動 |
### 1-2. Hot Path 比較 (14 ns vs 現状)
**mimalloc hot path** (14 ns):
```c
mi_heap_t* heap = mi_get_default_heap(); // 2 ns: TLS read
int cls = mi_size_to_class(size); // 3 ns: LUT + BSR
mi_page_t* page = heap->pages[cls]; // 1 ns: array index
void* p = page->free; // 3 ns: load free head
page->free = *(void**)p; // 3 ns: update free
return p; // 2 ns
```
**HAKMEM v7 hot path** (推定 18-22 ns):
```c
SmallHeapCtx_v7* ctx = small_heap_ctx_v7(); // 2 ns: TLS read
SmallClassHeap_v7* h = &ctx->cls[class_idx]; // 1 ns: array index
SmallPageMeta_v7* p = h->current; // 2 ns: load current
void* base = p->free_list; // 3 ns: load free head
p->free_list = *(void**)base; // 3 ns: update free
p->used++; // 1 ns: counter
((uint8_t*)base)[0] = HEADER_MAGIC | class; // 2 ns: header write
small_v7_stat_alloc(); // 2 ns: atomic stats
return (uint8_t*)base + 1; // 2 ns: USER ptr
```
**Gap 内訳**:
| Step | mimalloc | v7 | Diff | 削減可能? |
|------|----------|-----|------|----------|
| TLS access | 2 ns | 2 ns | 0 | - |
| Class lookup | 3 ns | 1 ns | -2 ns | ✅ v7 の方が速い |
| Page lookup | 1 ns | 2 ns | +1 ns | 改善可能 |
| Free list op | 6 ns | 6 ns | 0 | - |
| Counter update | 0 ns | 1 ns | +1 ns | cold path へ移動 |
| Header write | 0 ns | 2 ns | +2 ns | headerless 化 |
| Stats | 0 ns | 2 ns | +2 ns | cold path へ移動 |
| Return | 2 ns | 2 ns | 0 | - |
| **Total** | **14 ns** | **18 ns** | **+4 ns** | **-3~4 ns 可能** |
### 1-3. 競争力評価
**現状 v7 の立ち位置**:
- Legacy baseline: 58.6M ops/s
- v7 Phase v7-3: 56.3M ops/s (-4.3%)
- mimalloc 推定: 71M ops/s (+21% vs legacy)
**v7 が mimalloc に追いつくには**:
1. Header write 削除: +2 ns 改善
2. Stats を cold path へ: +2 ns 改善
3. Counter を cold path へ: +1 ns 改善
4. **合計**: 18 ns → 13-14 ns (mimalloc と同等)
**結論**: 設計上 mimalloc と **同等まで到達可能**。ただし intrusive LIFO 採用が鍵。
---
## 2. v7-5 方向性の決定基準
### 2-1. v7-5a vs v7-5b 比較表(実績付き)
| 項目 | v7-5a (C6 極限最適化) | v7-5b (Multi-class 拡張) |
|------|----------------------|-------------------------|
| **目標** | C6-only で -4.3% → ±0% | C5+C6 で overhead 分摊 |
| **作業量** | 小 (hotbox 微調整) | 中 (Segment 複数化, TLS hint 拡張) |
| **リスク** | 効果薄い可能性 (diminishing returns) | 複雑度増加 |
| **成功時の効果** | C6 で mimalloc 同等 | C5+C6 で -2% (予測) |
| **失敗時の影響** | 時間の無駄 (1-2日) | 設計変更のコスト (数日) |
| **検証方法** | A/B bench (C6-heavy) | Mixed bench + per-class stats |
### 2-2. 決定フレームワーク
**v7-5a を選ぶ条件**:
1. C6 の hot path に明確なボトルネックがある
2. Header write / Stats を cold path へ移動する具体策がある
3. 1-2 日で A/B 検証可能
**v7-5b を選ぶ条件**:
1. C6-only では overhead 削減に限界がある (既に TLS 最適化済み)
2. C5/C4 の allocation が実 workload で significant
3. overhead 分摊の理論値 (-4.3% → -2%) が魅力的
### 2-3. 実績: v7-5a 完了C6 v7 Hot path stats 削除)
**理由**:
1. **低コスト**: 1-2 日で結果が出る
2. **具体的改善点が明確**: Header write + Stats 移動
3. **失敗しても学び**: 限界が分かれば v7-5b へ移行
4. **成功すれば**: C6 で mimalloc 同等 → 強力な武器
**実装内容 (Phase v7-5a, 完了済み)**:
1. C6 v7 Hot path から per-page stats 更新を削除。
- `alloc_count++ / free_count++ / live_current++/--` を ColdIface 経路refill/retire 時)に移動。
- Hot path での stats は `HAKMEM_V7_HOT_STATS=1` のときだけ ENV ゲートで有効(デフォルト OFF
2. Header-at-carve-time の完全移行は見送り。
- freelist が block[0] を next pointer として使っており、「carve 時だけ header write」は v7 現行構造では安全にできないため、ヘッダは引き続き alloc 時に 1 byte 書く前提を維持。
**A/B 結果 (C6-heavy)**:
| Metric | v7 OFF (MID v3) | v7 ON (v7-5a) |
|---------|-----------------|---------------|
| Throughput (avg) | 9.26M ops/s | 9.27M ops/s |
| Delta | baseline | +0.15% |
→ 目標だった `-4.3% → ±2%` を達成し、C6-only v7 は MID v3 とほぼ同等±0%)になった。
**次の判断軸**:
- v7-5bMulti-class 拡張に進むかどうかは、「C5/C4 を v7 に載せて overhead を分摊する価値があるか」の評価次第。
- Intrusive LIFO / headerless / Stats cold path などは v7-5a の結果を踏まえて、別フェーズで改めて検討する。
### 2-4. 実績: v7-5b 完了C5+C6 multi-class 拡張)
**実装内容 (Phase v7-5b, 完了済み)**:
- SmallSegment_v7 / SmallObjectColdIface_v7 / SmallObjectHotBox_v7 を C5+C6 の 2 クラス対応に拡張。
- `SMALL_V7_CLASS_SUPPORTED()` に C5 を追加。
- `small_v7_block_size()` を C5/C6 両クラスを扱う switch に拡張。
- HotBox 側の alloc/free class validation を C5+C6 対応に更新。
- TLS 構造は C6 lane を維持しつつ、C5 については v7 small コアに乗せるが、C6 TLS のキャッシュ線を肥大化させない形をキープ。
**A/B 結果 (C5+C6 v7 プロファイル)**:
| Config | Avg Throughput | Delta |
|------------|----------------|----------|
| C6-only v7 | 7.64M ops/s | baseline |
| C5+C6 v7 | 7.97M ops/s | +4.3% |
- C6 性能維持: ✅C6-only v7 と比べて劣化なし)
- C5 net positive: ✅(+4.3%
- TLS bloat: ✅ なしC6 TLS lane のヒット率に悪影響なし)
**今後の方向性**:
- C4 は現在 ULTRA 本線で十分速いため、v7 に載せるかどうかは別途慎重に判断Phase v7-5c 候補)。
- v7 small/mid コアは C5+C6 2 クラス対応でも破綻しないことが分かったので、次は Mixed 161024B の A/B や Learner 連携(動的 route 選択)の設計・検証に進む。
---
## 3. C5 ルート選択と Learner 連携の必要性
Mixed / C5+C6 専用での A/B から、C5 v7 の利得が workload-dependent であることが判明した:
| Workload | C6-only v7 | C5+C6 v7 | 推奨設定 |
|----------------------|------------|----------|----------------|
| C5+C6 専用 (257768B) | baseline | +4.3% | `CLASSES=0x60` |
| Mixed 161024B | +0.5% | -8.0% | `CLASSES=0x40` |
結論:
- C5 は C5-heavy な専用 workload では v7 が有利だが、一般的な Mixed では MID v3+ULTRA の方が全体として速い。
- 「ENV で C5 を常に v7 にする / 常に MID v3 にする」ような固定方針は現実的でなく、**Learner で C5 の route を動的に切り替える設計**が必要。
設計メモv7-7 以降):
- Stats → Learner → Policy route_kind 更新の最小パス:
- `SmallPageStatsV7`ColdIface で retire 時に生成)
`SmallLearnerStatsV7`per-class 累積 alloc/free/remote
→ PolicyLearnerC5 alloc 比率が閾値を超えているか判定)
`SmallPolicyV7.route_kind[C5]``SMALL_ROUTE_V7` or `SMALL_ROUTE_MID_V3` に更新。
- 最初の版では C5 だけを対象とし、他クラスC4/C6 など)の動的ルート選択は後続フェーズで検討する。
---
## 3. Intrusive LIFO 採用検討
### 3-1. 現状 (Explicit Freelist)
```c
// SmallPageMeta_v7
void* free_list; // page metadata に freelist head
// Alloc
void* base = p->free_list;
p->free_list = *(void**)base;
// Free
*(void**)base = p->free_list;
p->free_list = base;
```
**問題**: free_list が page_meta 内にあり、間接参照が必要。
### 3-2. Intrusive LIFO (mimalloc 方式)
```c
// Block 内に next pointer を埋め込む
// Free block:
// +0: next_ptr (8 bytes) - 次の free block へのポインタ
// +8: (unused)
// Alloc
void* p = page->free; // free head (block 先頭)
page->free = *(void**)p; // block 内の next を読む
// Free
*(void**)p = page->free; // block 内に next を書く
page->free = p;
```
**利点**:
- **Zero metadata per block**: free 時に block 自身に next を書く
- **Cache locality**: 直前に free した block は L1 cache にある
- **Minimal indirection**: page→free だけで完結
### 3-3. v7 への適用可能性
**現状 v7 の構造**:
```
SmallPageMeta_v7
├── free_list (void*) ← page_meta 内
├── used (uint32_t)
├── capacity (uint32_t)
└── ...
Block (512B for C6)
├── [0]: header (1 byte) ← USER ptr = base + 1
└── [1-511]: user data
```
**Intrusive 化案**:
```
SmallPageMeta_v7
├── free (void*) ← 直接 block を指す (base ptr)
├── used (uint32_t)
└── ...
Free Block (512B)
├── [0-7]: next_ptr ← intrusive next pointer
└── [8-511]: (unused when free)
Allocated Block (512B)
├── [0]: header (optional)
└── [1-511]: user data
```
**変更点**:
1. `free_list``free` にリネーム (semantic 変更)
2. free 時: `*(void**)base = page->free; page->free = base;`
3. alloc 時: `void* p = page->free; page->free = *(void**)p;`
4. Header は USER ptr 計算時のみ使用 (hot path から削除可能)
### 3-4. 採用判断
**採用する場合のメリット**:
- +2 ns 改善 (page_meta 間接参照削減)
- mimalloc と同じデータ構造 → 同等性能の可能性
**採用しない場合の理由**:
- 既存 v7 コードの変更が必要
- Header との互換性を慎重に検討必要
- v6 headerless との設計統合を先に検討すべき
**結論**: **v7-5a の後で検討**。まず Header/Stats 削除で効果を測る。
---
## 4. TLS Cache Hit Rate 目標
### 4-1. 現状の TLS Fast Path
```c
// Phase v7-3 で追加
if (addr >= ctx->tls_seg_base && addr < ctx->tls_seg_end) {
// TLS hit: RegionIdBox skip
size_t page_idx = (addr - ctx->tls_seg_base) >> SMALL_PAGE_V7_SHIFT;
SmallPageMeta_v7* page = &seg->page_meta[page_idx];
// ... free logic
} else {
// TLS miss: RegionIdBox fallback
RegionLookupV6 lk = region_id_lookup_v6(ptr);
// ...
}
```
### 4-2. Hit Rate 目標
| Workload | TLS Hit Rate 目標 | 現状推定 | 根拠 |
|----------|------------------|----------|------|
| C6-heavy | 99%+ | 95-98% | ほぼ同一 segment |
| Mixed | 90%+ | 80-90% | 複数 class で分散 |
| Multi-thread | 85%+ | 75-85% | Remote free 混在 |
### 4-3. Hit Rate 測定方法
```c
// ENV: HAKMEM_V7_TLS_STATS=1
static uint64_t g_v7_tls_hit = 0;
static uint64_t g_v7_tls_miss = 0;
// In free path
if (addr >= ctx->tls_seg_base && addr < ctx->tls_seg_end) {
__sync_fetch_and_add(&g_v7_tls_hit, 1);
// ...
} else {
__sync_fetch_and_add(&g_v7_tls_miss, 1);
// ...
}
// At exit
fprintf(stderr, "[V7_TLS] hit=%lu miss=%lu rate=%.2f%%\n",
g_v7_tls_hit, g_v7_tls_miss,
100.0 * g_v7_tls_hit / (g_v7_tls_hit + g_v7_tls_miss));
```
### 4-4. 改善策 (Hit Rate < 目標の場合)
1. **Multi-segment TLS hint** (v7-5b で必要):
```c
struct {
uintptr_t seg_base;
uintptr_t seg_end;
SmallSegment_v7* seg;
} tls_seg[5]; // C3-C7 用
```
2. **Last-page cache** (既に設計あり):
```c
uintptr_t last_page_base;
uintptr_t last_page_end;
SmallPageMeta_v7* last_page_meta;
```
3. **Segment 再利用**: 同一 class の allocation が同一 segment に集中するよう誘導
---
## 5. Overhead 内訳の実測計画
### 5-1. 現状の仮説 (-4.3% 内訳)
| 要因 | 推定 | 対策 |
|------|------|------|
| Page metadata 間接参照 | ~2% | Intrusive LIFO |
| Extra validation | ~1% | Branch 最適化 |
| RegionIdBox fallback | ~1% | TLS cache 強化 |
| Header write | ~0.3% | Headerless 化 |
| **合計** | **~4.3%** | |
### 5-2. 実測方法
**Step 1: Header write 削除**
```bash
# ENV で headerless 有効化
HAKMEM_V7_HEADERLESS=1 ./bench_random_mixed_hakmem
# 効果測定: -0.3% 改善なら仮説検証
```
**Step 2: Stats 削除**
```bash
# Stats を compile-time で無効化
make clean && make CFLAGS="-DHAKMEM_V7_NO_STATS"
# 効果測定: -0.5% 改善なら仮説検証
```
**Step 3: TLS hit rate 測定**
```bash
HAKMEM_V7_TLS_STATS=1 ./bench_random_mixed_hakmem
# Hit rate < 90% なら TLS 改善が必要
```
**Step 4: Page metadata 間接参照**
```bash
# Intrusive LIFO 試験実装
# 効果測定: -2% 改善なら大きな勝利
```
### 5-3. 検証完了基準
| 仮説 | 実測結果 | 判定 |
|------|----------|------|
| Header write ~0.3% | 実測値 | ±0.2% なら OK |
| Stats ~0.5% | 実測値 | ±0.3% なら OK |
| TLS fallback ~1% | Hit rate × miss cost | 計算で検証 |
| Page metadata ~2% | Intrusive 後の差分 | ±0.5% なら OK |
---
## 6. 次のアクション
### 6-1. 即時 (v7-5a: 1-2 日)
1. **Header write 条件化**:
- `smallobject_hotbox_v7_box.h``#ifdef HAKMEM_V7_HEADERLESS` 追加
- Free path で header validation をスキップ
2. **Stats を cold path へ**:
- `small_v7_stat_alloc()``#ifdef HAKMEM_V7_STATS` で囲む
- Default OFF で bench
3. **A/B bench**:
- C6-heavy: `HAKMEM_BENCH_MIN_SIZE=400 HAKMEM_BENCH_MAX_SIZE=510`
- v7 ON/OFF 比較
### 6-2. 短期 (v7-5a 評価後: 2-3 日)
4. **TLS hit rate 測定**:
- Stats 追加して hit/miss ratio 確認
- < 90% なら multi-segment hint 検討
5. **Intrusive LIFO 試験**:
- small_heap_alloc_fast_v7 を intrusive 版に書き換え
- A/B bench で効果測定
### 6-3. 中期 (v7-5b 検討: 1 週間)
6. **Multi-class 拡張設計**:
- C5 対応の Segment 設計
- TLS context 拡張 (tls_seg[5])
7. **v6 headerless 統合**:
- v6 と v7 の headerless パターン統合
- RegionIdBox で class_idx 取得を共通化
---
## 7. 結論
### mimalloc に勝てるか?
**Yes, 設計上は可能**。
条件:
1. Intrusive LIFO 採用 (+2 ns)
2. Header write 削除 (+2 ns)
3. Stats を cold path へ (+2 ns)
4. TLS hit rate 90%+ 維持
これで v7 は 13-14 ns (mimalloc 同等) に到達可能。
### v7-5 の推奨
**v7-5a (C6 極限最適化) を先に実施**。
理由:
- 低コスト (1-2 日)
- 具体的改善点が明確
- 成功すれば v7-5b は不要かもしれない
- 失敗しても学びがある
### 最終目標
| Phase | 目標 | 達成基準 |
|-------|------|----------|
| v7-5a | C6 で legacy ±2% | C6-heavy bench |
| v7-5b | C5+C6 で legacy ±0% | Mixed bench |
| v7-6 | mimalloc 同等 | Intrusive LIFO + Headerless |
---
**Document Status**: COMPLETE
**Next Action**: v7-5a 実装開始
**Decision Owner**: User