Files
hakmem/docs/analysis/SMALLOBJECT_V7_DESIGN.md
Moe Charm (CI) 79674c9390 Phase v10: Remove legacy v3/v4/v5 implementations
Removal strategy: Deprecate routes by disabling ENV-based routing
- v3/v4/v5 enum types kept for binary compatibility
- small_heap_v3/v4/v5_enabled() always return 0
- small_heap_v3/v4/v5_class_enabled() always return 0
- Any v3/v4/v5 ENVs are silently ignored, routes to LEGACY

Changes:
- core/box/smallobject_hotbox_v3_env_box.h: stub functions
- core/box/smallobject_hotbox_v4_env_box.h: stub functions
- core/box/smallobject_v5_env_box.h: stub functions
- core/front/malloc_tiny_fast.h: remove alloc/free cases (20+ lines)

Benefits:
- Cleaner routing logic (v6/v7 only for SmallObject)
- 20+ lines deleted from hot path validation
- No behavioral change (routes were rarely used)

Performance: No regression expected (v3/v4/v5 already disabled by default)

Next: Set Learner v7 default ON, production testing

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 06:09:12 +09:00

909 lines
33 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.

## SmallObjectHeap v7 / HAKMEM v3 コア設計メモ2025-12-11
このドキュメントは、ULTRA + MID v3 + V6 世代の上に新しく載せる
**SmallObjectHeap v7= HAKMEM v3 small/mid コア)** の設計方針をまとめたものです。
当面は設計・型スケルトンのみで、挙動は一切変更しません。
---
## 1. 位置づけと層構造Box Theory
### 1-1. 既存世代のまとめ
- L0: ULTRA lanes現行
- C4C7 ULTRA。C7 は 2MiB Segment + 64KiB Page + TLS freelistC7 ULTRA Box
- Mixed / C7-only で十分な性能が出ており、**FROZEN完成世代** とみなす。
- L1: HotBox v2 世代
- Tiny front v3 + TinyHeap v1小クラス
- MID v3257768B の mid/smallmid を TLS heap で扱う)。
- V6 C6-only headerless coreRegionId + Segment + TLS laneの研究箱。
- L2: Segment / Superslab / Warm / Remote
- L3: Policy/Learner + Stats + ENVACE/ELO/CAP 等)
この世代では、各帯に特化した箱ULTRA / MID v3 / V6を積み上げることで
Mixed 161024B を ~30M → ~44M ops/s まで底上げしたが、
small〜mid を一体で見る「共通の SmallObject コア」は存在しない。
### 1-2. v7 世代の狙い
v7 は L1 に新しく追加する **SmallObjectHotBox_v7** として設計する:
```text
Front (size→class→route LUT)
|
+-- L0: ULTRA lanes (C4C7, FROZEN)
|
+-- L1: SmallObjectHotBox_v7 ← NEW small/mid コア
|
+-- L1': TinyHeap v1 / MID v3 / V6 (fallback/legacy)
|
+-- L2: SegmentBox_v7 / ColdIface_v7
|
+-- L3: PolicyBox_v7 / RegionIdBox / PageStatsBox
```
目的:
- small例: 16〜1KiB or 16〜2KiBと mid の一部を **1 個の thread-local heap + segment** で扱う土台を作る。
- ULTRA 世代C4C7は L0 としてそのまま残すC7 ULTRA は独立 box
- headerless/v6 の実験で得た「RegionId + Segment + TLS lane + PageStats」の物理層パターンをコア側に反映する。
---
## 2. 型設計SmallHeapCtx_v7 / SmallSegment_v7
v7 の基本構造は v6/V3 の経験を統合したものとする。
### 2-1. SmallPageMeta_v7
Hot path で頻繁に触るフィールドと Stats 用フィールドを分離する。
```c
// Hot line: alloc/free で触るフィールド
typedef struct SmallPageMeta_v7 {
// ---- hot fields (cache line 0 想定) ----
void *free_list; // LIFO freelist: block -> next
uint32_t used; // 現在の使用スロット数
uint32_t capacity; // この page にある block スロット数
uint16_t class_idx; // サイズクラス (C0..C?)
uint16_t flags; // HOT/PARTIAL/FULL/REMOTE_PENDING 等
uint16_t page_idx; // Segment 内 index (0..N-1)
uint16_t reserved0; // アラインメント用
struct SmallSegment_v7 *segment; // Segment への back pointer
// ---- cold fields (Stats/Policy, cache line 1〜) ----
uint64_t alloc_count; // 累積 alloc 数
uint64_t free_count; // 累積 free 数
uint64_t remote_free_count; // 累積 remote free 数
uint16_t live_current; // 現在の live
uint16_t peak_live; // lifetime 最大 live
uint16_t remote_burst_max; // 一度の drain で吸い上げた remote の最大
uint16_t reserved1;
uint32_t epoch_first_alloc; // coarse epoch (L3 用)
uint32_t epoch_last_free; // 同上
} SmallPageMeta_v7;
```
設計ポイント:
- Hot path (alloc/free) では `free_list / used / capacity / class_idx` だけを触る。
- Stats/Learner は L2 retire 時に cold fields を `SmallPageStatsV7` にまとめて L3 に渡す。
- `segment` を持たせて退役時の SegmentBox 更新を簡単にする(必要であれば将来削る)。
### 2-2. SmallClassHeap_v7 / SmallHeapCtx_v7
各クラスの現在/部分/満杯ページと、小さな TLS magazine を持つ。
```c
typedef struct SmallClassHeap_v7 {
SmallPageMeta_v7 *current; // いま alloc に使っている page
SmallPageMeta_v7 *partial_head; // まだ空きのあるページ
SmallPageMeta_v7 *full_head; // FULL 判定のページCold 側に寄せる用)
void *local_freelist; // オプション: mini-ULTRA (class ローカル TLS)
uint16_t local_freelist_count;
uint16_t local_freelist_cap;
uint16_t class_idx;
uint16_t flags; // class 側のポリシーフラグ (ULTRA禁止など)
} SmallClassHeap_v7;
#define HAK_SMALL_NUM_CLASSES_V7 /* 例: 16〜24 */
typedef struct SmallHeapCtx_v7 {
SmallClassHeap_v7 cls[HAK_SMALL_NUM_CLASSES_V7];
} SmallHeapCtx_v7;
```
設計ポイント:
- v7 第1版では `local_freelist` は無効 (`cap=0`) にしておき、必要なクラスだけ Learner で有効化してもよい。
- クラス数は最初は「small側16〜1KiB or 2KiBをカバーする程度」に抑え、
mid を扱う `MidHeapCtx_v7` は別箱とする(後述)。
### 2-3. SmallSegment_v7
ULTRA の 2MiB/64KiB パターンをベースに、small v7 用 SegmentBox を定義する。
```c
#define SMALL_SEGMENT_SIZE_V7 (2u * 1024u * 1024u) // 2MiB
#define SMALL_PAGE_SIZE_V7 (64u * 1024u) // 64KiB
#define SMALL_PAGES_PER_SEG_V7 (SMALL_SEGMENT_SIZE_V7 / SMALL_PAGE_SIZE_V7)
typedef struct SmallSegment_v7 {
uintptr_t base; // 実データ領域の先頭アドレス
uint32_t num_pages; // 実際に使うページ数
uint32_t owner_tid; // 所有スレッド id
uint32_t flags; // SEGMENT_IN_USE / RETIRED 等
uint32_t region_kind; // REGION_SMALL_V7 / REGION_ULTRA / REGION_POOL 等
uint32_t segment_idx; // RegionIdBox 上の index
uint32_t free_page_head; // free page stack head
uint32_t free_page_count;
SmallPageMeta_v7 page_meta[SMALL_PAGES_PER_SEG_V7];
} SmallSegment_v7;
```
設計ポイント:
- `ptr & ~(SEG_SIZE-1)` で Segment に直行し、
`(ptr - base) >> PAGE_SHIFT``page_idx` を求めて `page_meta[page_idx]` に行ける O(1) 構造。
- small/mid/pool で Segment geometry を変えたい場合も、API は共通で持てるSegmentBox_v7 small / mid 用の 2 種類も可)。
---
## 3. RegionIdBox / header / class 判定方針v7 世代)
### 3-1. header の扱い
v6 C6-only headerless 実験から:
- header 完全削除は「Region lookup コスト」で相殺されがちで、劇的な改善には繋がらないケースが多い。
- ただし RegionId + Segment + page_meta.class_idx のパターンは
ptr→segment→page_meta→class を O(1) にする物理層として非常に有用。
v7 では次の方針とする:
1. ヘッダは **薄く残す**Fail-Fastlegacy/pool bridgeデバッグ用
2. small/mid の fast path では、できるだけ header を触らない。
- C7 ULTRA / 一部 hot クラス(将来の C6 ULTRA lane など)は完全 headerless も許可。
- SmallHeapCtx_v7 の core は「carve/refill 時に 1 回だけ書く」程度に抑える。
3. free 時の class 判定は:
- front/gate の hintsize→class / header + RegionIdBox + page_meta.class_idx を併用。
- v7 small pathでは、最終的には `page_meta.class_idx` を真とし、hint は OBSERVE 検証用とする。
### 3-2. RegionIdBox API
small/mid/pool 共通の ptr 分類箱として RegionIdBox_v7 を定義する:
```c
typedef enum {
REGION_UNKNOWN = 0,
REGION_SMALL_V7,
REGION_ULTRA,
REGION_MID_V7,
REGION_POOL_V3,
REGION_LARGE,
} region_kind_t;
typedef struct RegionLookupResult_v7 {
region_kind_t kind;
union {
struct {
SmallSegment_v7 *segment;
uint16_t page_idx;
} small_v7;
struct {
void *segment; // C7 ULTRA / mid v7 / pool v3 等
} other;
} u;
} RegionLookupResult_v7;
static inline RegionLookupResult_v7
region_id_lookup_v7(void *ptr);
```
small v7 free pathfast path:
```c
RegionLookupResult_v7 lk = region_id_lookup_v7(ptr);
if (likely(lk.kind == REGION_SMALL_V7)) {
SmallPageMeta_v7* page =
&lk.u.small_v7.segment->page_meta[lk.u.small_v7.page_idx];
uint8_t class_idx = page->class_idx;
small_heap_free_fast_v7(ctx, page, class_idx, ptr);
} else {
// ULTRA / MID / POOL / LEGACY へ bridge
}
```
移行モード:
- Phase OBSERVE: header-based class と page_meta.class_idx を比較して log/assert。挙動はまだ v2 世代のまま。
- Phase FROZEN: small v7 管理ページでは header を見ず、RegionId + page_meta.class_idx だけで動かす。
---
## 4. mid/pool との関係SmallHeapCtx_v7 vs MidHeapCtx_v7
v7 世代では small と mid を同じ物理層RegionIdBox + SegmentBox + PageStatsBoxに乗せつつ、
HotBox は別箱に分けるのが現実的:
```text
RegionIdBox / SegmentBox_v7 / PageStatsBox
|
+--> SmallHeapCtx_v7 (small: 16〜1KiB or 2KiB)
|
+--> MidHeapCtx_v7 (mid: 2〜16KiB or 2〜32KiB)
|
+--> PoolCtx_v3/v7 (さらに大きい / 特殊用途)
```
方針:
- **共通化するもの**:
- RegionIdBoxptr→region_kind + segment/page_idx
- Segment geometry APIsmall 用と mid 用に派生しても良い)
- PageStats 基本構造class_idx / alloc/free/remote / live/peak など)
- **専用箱にするもの**:
- SmallHeapCtx_v7small 特有の TLS/prefetch/クラス配置)
- MidHeapCtx_v7mid 特有の page サイズ・クラス分割・remote ポリシー)
- PoolCtx_v3/v7巨大オブジェクト特殊用途
橋渡し:
- RegionIdBox で kind != REGION_SMALL_V7 を検出したときのみ、
mid/pool の bridge_box`small_mid_bridge_free()` 等)に渡す。
- small core / mid core / pool core は互いを直接呼ばず、「bridge 1 箇所」で繋ぐ。
---
## 5. フェーズ分割v7-0 / v7-1 / v7-2 の指針)
いきなり small/mid 全体を v7 にするのではなく、C6-only small 帯から段階的に導入する。
### Phase v7-0: 型とインフラだけ追加(挙動一切変更なし)
目的:
- struct と設計 doc だけ追加し、ビルドと Box 理論上の位置づけを固める。
タスク:
- `SmallPageMeta_v7` / `SmallClassHeap_v7` / `SmallHeapCtx_v7` / `SmallSegment_v7` struct をヘッダに追加。
- RegionIdBox に `REGION_SMALL_V7``RegionLookupResult_v7` を追加(実装はまだダミーで OK
- ENV:
- `HAKMEM_SMALL_HEAP_V7_ENABLED=0`
- `HAKMEM_SMALL_HEAP_V7_CLASSES=0x0`
- front/gate は v7 に一切 route しない。
### Phase v7-1: C6-only v7 stubroute だけ v7 に向ける)
目的:
- front/gate・ENV・RegionIdBox の配線が壊れていないか確認する。
タスク:
- `TINY_ROUTE_SMALL_HEAP_V7` を route kind に追加。
- プロファイル: C6-only v7 stub モード(`CLASSES=0x40` など)を追加。
- `small_heap_alloc_fast_v7_stub(size, ci)` / `small_heap_free_fast_v7_stub(ptr, ci)` を実装し、
中身は即座に v2 世代MID v3 / V6 / pool v1にフォールバックするだけにする。
- RegionIdBox は OBSERVE モードで `region_id_lookup_v7(ptr)` を呼んで統計取得のみ(挙動不変)。
### Phase v7-2: C6-only v7 本実装small帯だけ
目的:
- C6-only の alloc/free を SmallHeapCtx_v7 + SmallSegment_v7 で本当に回し、
C6-heavy / Mixed で v3/V6/v2 本線と比較する。
タスク:
- SegmentBox_v7 と ColdIface_v7 を実装し、C6 pages の refill/retire を Segment v7 経由にする。
- `small_heap_alloc_fast_v7(size, ci)` / `small_heap_free_fast_v7(ptr, ci)` を実装:
- alloc: current→partial→cold_refill の順で page/freelist を消費。
- free: RegionIdBox で small_v7 page を特定し、page_meta.free_list に push必要時 retire
- プロファイル:
- C6-only v7 ON他クラスは ULTRA + MID v3 + V6 のまま)。
- ベンチ:
- C6-heavy / Mixed で v7 vs MID v3 vs V6 vs v2 本線を測り、
C6-only v7 の価値を評価(十分なら次の v7 拡張フェーズへ)。
---
## 6. まとめ
- v2 世代ULTRA + MID v3 + V6 C6-onlyは、Box Theory に沿ってかなりやり切った世代とみなし、ここで一度締める。
- v3 世代SmallObjectHeap v7は、「small〜mid を 1 個の SmallHeapCtx + Segment + RegionIdBox で扱う」第2章として設計する。
- まずは C6-only small 帯から v7 を導入し、ULTRA/MID v3 を壊さない形で徐々に適用範囲を広げていく。
### Phase v7-3: C6 TLS Segment Fast Path + Page Metadata Cache
目的:
- Phase v7-2 で明らかになった RegionIdBox binary search のオーバーヘッド(-7% vs legacyを削減する。
- "ほとんどの" C6 free パスで RegionIdBox lookup を避ける設計に改善。
主な最適化:
1. **TLS segment fast hint**:
- SmallHeapCtx_v7 に `tls_seg_base / tls_seg_end / tls_seg` を追加。
- free 初期段階で `if (addr >= tls_seg_base && addr < tls_seg_end)` をチェックして、
ほとんどの C6 free は RegionIdBox を呼ばずに page_idx を計算できるように。
2. **same-page page_meta TLS cache**:
- SmallHeapCtx_v7 に `last_page_base / last_page_end / last_page_meta` を追加。
- 前回と同じページ内の free は page_meta 検索をスキップ1-2% 改善期待)。
3. **RegionIdBox は TLS 範囲外のみ**:
- RegionIdBox は「TLS segment が知らない alloc」POOL / LEGACY / ULTRA 由来)の分類専用に限定。
- C6-only v7 の hot path では RegionIdBox を通さない。
4. **C6-only 維持**:
- Phase v7-3 でも C6 のみに絞る。
- C5/C4 への拡張は、v7 の性能が legacy に追いついた後に検討。
期待結果:
- TLS fast path で RegionIdBox overhead 大部分回避 → -7% から ±0〜+α への改善を目指す。
- SegmentBox_v7 / ColdIface_v7 の API は不変(内部最適化のみ)。
---
## 7. Phase v7-4: Policy Box (L3 層の明確化)
### Policy Box の役割
SmallPolicyV7 Box を L3 に配置し、「どのクラスをどの層に送るか」を一元管理:
```c
typedef struct SmallPolicyV7 {
SmallRouteKind route_kind[8]; // C0-C7
} SmallPolicyV7;
const SmallPolicyV7* small_policy_v7_snapshot(void);
```
### フロントの責務
フロントは Policy Snapshot を読んで route を選ぶだけ:
```c
const SmallPolicyV7* policy = small_policy_v7_snapshot();
SmallRouteKind route = policy->route_kind[class_idx];
switch (route) {
case SMALL_ROUTE_ULTRA: // L0
case SMALL_ROUTE_V7: // L1
case SMALL_ROUTE_MID_V3: // L1'
case SMALL_ROUTE_LEGACY: // L1'
}
```
### ENV の一元化
ENV 変数は Policy init で一度だけ読む:
- `HAKMEM_TINY_C7_ULTRA_ENABLED`
- `HAKMEM_SMALL_HEAP_V7_ENABLED` + `HAKMEM_SMALL_HEAP_V7_CLASSES`
- `HAKMEM_MID_V3_ENABLED` + `HAKMEM_MID_V3_CLASSES`
優先順位: ULTRA > v7 > MID_v3 > LEGACY (固定)
将来的にはクラスごとの柔軟な優先順位設定や、Learner 連携による動的ルート選択も可能。
### 段階移行
Phase v7-4 では v7 関連のみ Policy box 経由に変更。
ULTRA/MID_v3/LEGACY は既存の `tiny_route_env_box.h` を併用(後で統合予定)。
---
## 8. v7 第2章への設計メモPhase v7-4 完了時点)
### 現状C6-only 研究箱)
- **性能**: 56.3M ops/s (Phase v7-3, -4.3% overhead)
- **設計完了**: SmallHeapCtx / Segment / ColdIface / RegionIdBox 統一
- **Policy統合**: 完了route 一元化)
### -4.3% Overhead の内訳(仮説)
| 要因 | 推定 | 対策 |
|------|------|------|
| Page metadata 間接参照 | ~2% | Multi-class で分摊 |
| Extra validation | ~1% | Branch優化 |
| RegionIdBox fallback | ~1% | TLS cache強化 |
### Multi-class 拡張時の検討項目
1. **Segment 設計**:
- Option A: Class ごとに独立 segment (SmallSegment_v7_C6, v7_C5, ...)
- Option B: 複数 class を 1 segment 内で共存
- Decision point: TLS hint の複数 segment 対応
2. **TLS Context 拡張**:
```c
typedef struct SmallHeapCtx_v7_multi {
SmallClassHeap_v7 cls[8];
// Multi-class TLS hints
struct {
uintptr_t seg_base;
uintptr_t seg_end;
SmallSegment_v7* seg;
} tls_seg[5]; // C3-C7
} SmallHeapCtx_v7_multi;
```
3. **Overhead 分摊の期待値**:
- C6-only: -4.3% (current)
- C5+C6: -2% (overhead 薄まる)
- C4+C5+C6: -1% (さらに薄まる)
### Learner 連携Phase v7-5 候補)
**概要**: SmallPageStatsV7 から実行時最適 route を学習
```c
// Policy Box update interface
void small_policy_v7_update_from_learner(
const SmallLearnerStats* stats,
SmallPolicyV7* policy_out
);
```
**学習要件**:
- Alloc/free count, peak_live, lifetime_ms
- v7 vs MID_v3 の速度比較
- Learner の信頼度 threshold
### HeaderLess 統一(将来検討)
v7-5 以降でも header を削除できるかの検証:
- v6 headerless: page->class_idx で header-free を実装済み
- v7 適用: free 時に page_meta から class_idx を取得
- Benefit: 1 byte 削減 per allocationmicro, 但し alloc density up
### 次世代開始チェックリスト
- [ ] HAKMEM_V2_GENERATION_SUMMARY.md と本ドキュメントから、v2→v7 の層構造と責務が一貫して読み取れること
- [ ] v7-4 時点の設計メモ(本セクション)が「どこを伸ばすか/どこは ULTRA/MID に任せるか」の地図として機能していること
- [ ] v7 small/mid コアを C6 以外にも広げる際のクラス割り当てC2〜C7が `HAKMEM_V2_GENERATION_SUMMARY.md` と齟齬なく定義されていること
---
## 9. HAKMEM v3mimalloc に追いつく世代)のターゲット像
この節は、「いま以降の HAKMEM 開発は何を目指すか」をブレないようにするためのメモです。
### 9-1. 性能ターゲットMixed 161024B
- 評価プロファイル:
- `HAKMEM_PROFILE=MIXED_TINYV3_C7_SAFE`
- `ws=400`, `iters=1,000,000`, size range `161024B`
- 比較対象:
- mimalloc: 現状 ~ 110120M ops/s
- HAKMEM v2 世代: ~ 4045M ops/sULTRA + MID v3 ベース)
- v3/v7 世代の目標:
- **短期**: 50M ops/s 台mimalloc の ~0.5×
- **中期**: 60M ops/s 近辺0.5〜0.6×)を安定して狙える設計
### 9-2. クラス別の役割v3 世代の前提)
`docs/analysis/HAKMEM_V2_GENERATION_SUMMARY.md` の整理を踏まえ、v3 世代では次の前提で進める:
- C7 (7691024B):
- L0 C7 ULTRA が本命。v7 small/mid コアからは除外し、ULTRA Box を前提に最適化を続ける。
- C6 (513768B):
- v7 SmallHeapCtx の本命クラス。
- C6-only v7 を最初のターゲットとして、legacy/MID/v6 を相手に A/B しながらコアの形を固める。
- C5 (257512B):
- 現状は MID v3 + ULTRA でそこそこ速く、本線としては安定。
- v7 への移行は C6 v7 が legacy/MID を上回るめどが立ってから、段階的に検討する。
- C4 (129256B):
- C4 ULTRA寄生型が効いている帯。v7 に乗せるかは慎重に検討。
- 当面は ULTRA を前提とし、v7 small コアには無理に載せない。
- C3C0 (≤64B):
- Tiny legacy/Tiny front v3 のまま。当座は v7 で触らない。
この前提により、「どのクラスを v7 small/mid コアで本気で攻めるか」「どこは ULTRA/MID/legacy に任せるか」が明確になる。
### 9-3. v7 コアで必ず守るルール
1. **Front は route するだけ**
- `size → class_idx → SmallPolicyV7.route_kind[class_idx] → switch` 以外の仕事ENV 読み・Region lookup・Segment 操作)は Front に置かない。
2. **HotBox は物理層に直接触らない**
- SmallObjectHotBox_v7 は `SmallSegment_v7` / `RegionIdBox` / `PageStatsBox` へのアクセスを ColdIface/API 経由に限定し、HotPath 内で ENV や Learner を直接参照しない。
3. **Learning は snapshot 経由のみ**
- L3 Policy/Learner は Stats を読んで `SmallPolicyV7` や per-class パラメータtls_cap, partial_limit 等)を更新する。
- L1/L0 は「いまの snapshot 値」を読むだけで、学習や ENV を意識しない。
### 9-4. v3 世代での「やってよい/やらない」境界
- やってよい(本筋):
- v7 small/mid コア(特に C6の alloc/free/segment/RegionId 経路の設計・実装・A/B。
- PolicyBox を使った route_kind の再配線ULTRA/MID/v7/LEGACY のクラスごとの切り替え)。
- RegionIdBox/Segment/PageStats の「共通物理層」としての整理と、small/mid/pool での再利用設計。
- やらない(この世代では控える):
- ULTRA 世代の大改造C7 ULTRA の構造変更など)。
- MID v3 のリフトアップを目的とした過度な再実装v7/mid コア側で吸収する)。
- header を全クラス・全経路から完全に消し去るような大手術(これは v8 以降のテーマとして温存)。
この節を「v3 / v7 世代で何を目指すか」の固定観念として扱い、今後の設計・実装・A/B で迷子になったときの基準とする。
---
## 10. C5 Learner 連携の最小設計v7-7 用メモ)
v7-5b/v7-6 の結果から、C5 の route は workload-dependentC5-heavy では v7 が有利だが、Mixed では MID v3+ULTRA が有利)であることが分かった。
そのため、C5 の route_kind は固定ではなく Learner で動的に切り替えられるように設計する。
### 10-1. データフロー: Stats → Learner → Policy
1. ColdIface_v7 / PageStatsBox:
- C5/C6 v7 管理ページの retire/refill 時に `SmallPageStatsV7` を生成し、L3 に publish する。
- 必要なフィールド(例):
- `class_idx`
- `alloc_count`, `free_count`
- `remote_free_count`
- `live_at_retire`, `peak_live`optional
2. Learner 集約構造:
```c
typedef struct SmallLearnerClassStatsV7 {
uint64_t total_allocs;
uint64_t total_frees;
uint64_t total_remote_frees;
} SmallLearnerClassStatsV7;
typedef struct SmallLearnerStatsV7 {
SmallLearnerClassStatsV7 per_class[8]; // C0C7
} SmallLearnerStatsV7;
```
3. Policy 更新 API:
```c
void small_policy_v7_update_from_learner(
const SmallLearnerStatsV7* stats,
SmallPolicyV7* policy_out);
```
- L3 の Learner が一定間隔(例: N ページ retire / N ミリ秒ごとで呼び出し、C5 の route_kind を `SMALL_ROUTE_V7` / `SMALL_ROUTE_MID_V3` のどちらかに切り替える。
- L1/L0ULTRA / v7 / MID v3は snapshot を読むだけで、Learner の存在を意識しない。
### 10-2. C5 route 切り替えの簡易ルール案
- 入力: `SmallLearnerStatsV7.per_class[5].total_allocs` と全クラス合計 alloc 数。
- ルール例:
- `C5_share = total_allocs[C5] / total_allocs_all`
- もし `C5_share >= 0.3` なら:
- `route_kind[C5] = SMALL_ROUTE_V7;` // C5-heavy → v7 small/mid コア
- それ以外なら:
- `route_kind[C5] = SMALL_ROUTE_MID_V3;` // Mixed 寄り → MID v3 + ULTRA に任せる
このルールは最初の近似であり、後続フェーズでしきい値やヒステリシス(しばらく動作を維持するクールダウン)を追加してもよい。
### 10-3. Box Theory 上の位置づけ
- Learner / Policy 更新処理は **L3 Box** に閉じ込める。
- StatsBox / Learner / PolicyBox は L3。
- L2 以下Segment / RegionId / HotBox / ULTRAは snapshot を読むだけ。
- C5 の route_kind 更新は snapshot 差し替え経由でのみ行う。
- 直接 front の分岐を書き換えず、常に `SmallPolicyV7` から読む。
- これにより:
- Hot path は `route_kind` の読み取りだけで終わり、
- 「いつ C5 を v7 にするか」という意思決定は上層L3に閉じ込められる。
---
## 10. Phase v7-5a: C6 v7 極限最適化 (Hot Path Stats Removal)
### 10-1. 目的
Phase v7-3 時点の -4.3% overhead を ±0% 以下に改善し、v7 を本線として使える状態にする。
### 10-2. v7-5a vs v7-5b の選択
| Option | 内容 | リスク | 期待効果 |
|--------|------|--------|----------|
| v7-5a | C6 micro-optimization (stats/header) | 低 | -4.3% → ±0% |
| v7-5b | Multi-class expansion (C5/C4) | 中 | overhead 分摊 |
**採用**: v7-5a を優先。低リスクで効果を確認してから v7-5b に進む。
### 10-3. v7-5a の最適化内容
#### 実施した最適化
1. **Per-page stats 削除 (hot path から)**:
- `alloc_count++` / `free_count++` / `live_current++/--` を hot path から除去
- Stats は retire 時に cold path で収集
2. **Global atomic stats の ENV-gating**:
- `HAKMEM_V7_HOT_STATS=1` で有効化デフォルトOFF
- Hot path での atomic counter を完全除去可能に
3. **Header-at-carve-time 最適化 → 断念**:
- 試行: 「refill 時にヘッダを書き、alloc 時には書かない」
- **失敗理由**: intrusive LIFO freelist が block[0..7] に next pointer を格納するため、
carve 時にヘッダを書いても alloc pop 前に上書きされる
- **対応**: ヘッダは alloc 時に書く方式を維持
```c
// freelist 構造の制約
// block[0]: header byte (1B)
// block[0..7]: freelist next pointer (8B)
// → 重複するため、carve 時にヘッダを書いても freelist が上書きする
```
### 10-4. A/B ベンチマーク結果
**ベンチ条件**:
- Range: 257-768B (C6-heavy)
- Benchmark: bench_mid_large_mt_hakmem
- 5 iterations each
| v7 OFF (MID v3) | v7 ON (v7-5a) |
|-----------------|---------------|
| 9,370,124 | 9,251,082 |
| 9,698,882 | 8,782,087 |
| 8,862,704 | 9,331,487 |
| 9,108,786 | 9,502,876 |
| 9,250,129 | 9,490,638 |
| **Avg: 9.26M** | **Avg: 9.27M** |
**結果: +0.15% (±0%, noise margin 内)**
### 10-5. 評価
| 指標 | Before (v7-3) | After (v7-5a) | 目標 |
|------|---------------|---------------|------|
| Overhead | -4.3% | ±0% | legacy ±2% |
| Stats impact | 常時 ON | ENV-gated | - |
| Header overhead | あり | あり (維持) | - |
**Phase v7-5a 達成**: hot path から per-page stats を除去し、overhead を ±0% まで改善。
### 10-6. 次のステップ候補
1. **v7-5b (Multi-class)**: C5/C4 へ v7 を拡張し、overhead をさらに分摊
2. **Headerless mode**: header 完全削除を再検討freelist 設計の変更が必要)
3. **Learner 連携**: Stats から動的 route 選択
---
## 11. Phase v7-5b: C5+C6 Multi-class Expansion
### 11-1. スコープ
**Target**: C5 限定C4 は ULTRA に残す)
**理由**:
- v4/v6 multi-class で TLS context 肥大 → L1 cache thrash の経験あり
- C5 の legacy share が大きいDecision Matrix: 53%)ので先に検証
**設計方針**:
```
C6: いまの TLS lane のまま(本命クラス)
C5: TLS lane なし or local_freelist 極小(数個)
C4: ULTRA に残す(触らない)
```
### 11-2. 実装構造
#### Segment 共有モデル
```c
// C5 と C6 は同じ 2MiB segment を共有
// page_meta[].class_idx で class を区別
SmallSegment_v7:
page_meta[0]: class_idx=6 (C6 page)
page_meta[1]: class_idx=5 (C5 page)
page_meta[2]: class_idx=6 (C6 page)
...
```
#### Block Size 拡張
```c
// smallsegment_v7_box.h
#define SMALL_V7_C5_CLASS_IDX 5
#define SMALL_V7_C5_BLOCK_SIZE 256
static inline size_t small_v7_block_size(uint32_t class_idx) {
switch (class_idx) {
case 5: return 256; // C5
case 6: return 512; // C6
default: return 0; // Unsupported
}
}
```
#### HotBox 拡張 (minimal)
```c
// C5/C6 両対応TLS 構造は変更なし)
static inline void* small_heap_alloc_fast_v7(size_t size, uint8_t class_idx) {
// v7-5b: C5 or C6
if (unlikely(class_idx != SMALL_V7_C6_CLASS_IDX &&
class_idx != SMALL_V7_C5_CLASS_IDX)) {
return NULL;
}
// ... 以下同じ
}
```
### 11-3. A/B 判断基準
| ベンチ | 条件 | 判定 |
|--------|------|------|
| C6-heavy | C6-only v7 vs C5+C6 v7 | C6 性能が落ちていないこと |
| Mixed 16-1024B | 同上 | C5 legacy コスト削減がトータルでプラス |
**採用ライン**: 「C6 を守りつつ C5 がトータルでプラスなら採用」
### 11-4. ENV/Profile
```bash
# C5+C6 v7 実験プロファイル
HAKMEM_SMALL_HEAP_V7_ENABLED=1
HAKMEM_SMALL_HEAP_V7_CLASSES=0x60 # bit5(C5) + bit6(C6)
```
### 11-5. A/B ベンチマーク結果
**ベンチ条件**:
- Range: 257-768B (C5+C6 mixed)
- Benchmark: bench_mid_large_mt_hakmem
- 5 iterations each
| Config | Run 1 | Run 2 | Run 3 | Run 4 | Run 5 | Avg |
|--------|-------|-------|-------|-------|-------|-----|
| C6-only v7 | 8.04M | 6.66M | 7.26M | 8.14M | 8.11M | **7.64M** |
| C5+C6 v7 | 8.35M | 8.34M | 7.87M | 7.81M | 7.49M | **7.97M** |
**結果: +4.3% improvement**
### 11-6. 評価
| 基準 | 結果 | 判定 |
|------|------|------|
| C6 性能維持 | C6-only と同等以上 | ✅ PASS |
| C5 net positive | +4.3% 改善 | ✅ PASS |
| TLS bloat | なしsegment 共有) | ✅ PASS |
**Phase v7-5b 達成**: C5 を v7 に載せて、C6 を守りつつトータル +4.3% 改善。
---
## 12. Phase v7-6: Mixed A/B + Learner 設計
### 12-1. Mixed 16-1024B A/B 結果
**ベンチ条件**:
- Profile: MIXED_TINYV3_C7_SAFE
- Benchmark: bench_random_mixed_hakmem 1000000 400 1
- Size range: 16-1024B (全クラス)
| Config | Runs | Avg Throughput | Delta vs OFF |
|--------|------|----------------|--------------|
| v7 OFF | 5 | **41.3M ops/s** | baseline |
| v7 C6-only (0x40) | 3 | **41.5M ops/s** | +0.5% |
| v7 C5+C6 (0x60) | 8 | **38.0M ops/s** | **-8.0%** |
### 12-2. 発見と考察
**重要な発見**: ワークロードによって最適な route が異なる
| ワークロード | C6-only v7 | C5+C6 v7 | 推奨 |
|-------------|------------|----------|------|
| C5+C6 専用 (257-768B) | baseline | **+4.3%** | C5+C6 v7 |
| Mixed 16-1024B | +0.5% | **-8.0%** | C6-only v7 |
**原因分析**:
- Mixed では C0-C4 の alloc/free が多く、C5/C6 の比率が低い
- C5 を v7 に送ると、MID v3 / legacy より遅い経路を通る
- C5+C6 専用ベンチでは C5 比率が高く v7 のメリットが出る
**結論**: C5 の route は workload-dependent。Learner で動的に切り替えるべき。
### 12-3. Policy/Learner 連携設計
#### Stats → Learner データフロー
```
SmallPageStatsV7 (retire時)
SmallLearnerStatsV7 (集約)
PolicyLearner (判定)
SmallPolicyV7.route_kind[] (更新)
```
#### SmallPageStatsV7 から Learner が読むべきフィールド
```c
// 既存 (smallobject_cold_iface_v7_box.h)
typedef struct SmallPageStatsV7 {
uint8_t class_idx; // ← クラス別集計のキー
uint64_t alloc_count; // ← Learner: クラスごとの alloc 頻度
uint64_t free_count; // ← Learner: クラスごとの free 頻度
uint64_t remote_free_count;// ← Learner: remote free 比率
uint16_t peak_live; // ← Learner: ピーク使用量
uint32_t lifetime_ms; // ← Learner: page 寿命
} SmallPageStatsV7;
```
#### Learner 側の集約構造案
```c
typedef struct SmallLearnerStatsV7 {
// Per-class counters (C0-C7)
struct {
uint64_t total_allocs; // 累積 alloc 数
uint64_t total_frees; // 累積 free 数
uint64_t v7_allocs; // v7 経由の alloc 数
uint64_t v7_frees; // v7 経由の free 数
uint64_t tls_hits; // TLS segment hit 数
uint64_t regionid_lookups; // RegionIdBox fallback 数
uint32_t avg_page_lifetime_ms;
} per_class[8];
// Global counters
uint64_t sample_count; // 集計サンプル数
uint64_t last_update_epoch; // 最終更新 epoch
} SmallLearnerStatsV7;
```
#### 更新頻度
- **オプション A**: N秒ごと例: 10秒
- **オプション B**: Nページ retire ごと(例: 100 pages
- **推奨**: オプション Bイベント駆動、低オーバーヘッド
#### Policy 更新 API シグネチャ
```c
// Learner → Policy 更新
void small_policy_v7_update_from_learner(
const SmallLearnerStatsV7* stats,
SmallPolicyV7* policy_out
);
// 判定ロジック例
// if (stats->per_class[5].v7_allocs / stats->per_class[5].total_allocs > 0.5
// && stats->per_class[5].tls_hits / stats->per_class[5].v7_frees > 0.8) {
// policy_out->route_kind[5] = SMALL_ROUTE_V7; // C5 → v7
// } else {
// policy_out->route_kind[5] = SMALL_ROUTE_MID_V3; // C5 → MID v3
// }
```
### 12-4. 層の分担(明文化)
```
L3 (Policy/Learner):
- Stats を集約
- route_kind[] を決定
- snapshot を差し替え
L1/L0 (ULTRA/v7/MID v3):
- snapshot を読むだけ
- ENV や Learner を直接参照しない
- route_kind[class_idx] に従って分岐
```
### 12-5. 保留事項
以下は v7-6 の結果を踏まえて別フェーズで検討:
1. **C4 v7 拡張**: Mixed A/B と Learner 設計の後に判断する
2. **Intrusive LIFO / 完全 headerless**: v7-6 の結果を見てから検討
### 12-6. 次のステップ候補
1. **Learner 実装 (Phase v7-7)**: SmallLearnerStatsV7 の実装と Policy 更新ロジック
2. **Workload Detection**: alloc/free パターンから workload を推定
3. **Dynamic Route Switching**: 実行時に C5 の route を v7 ↔ MID v3 で切り替え
---
**Document Updated**: 2025-12-12
**Phase v7-5a Status**: COMPLETE
**Phase v7-5b Status**: COMPLETE
**Phase v7-6 Status**: COMPLETE