## 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(現行) - C4–C7 ULTRA。C7 は 2MiB Segment + 64KiB Page + TLS freelist(C7 ULTRA Box)。 - Mixed / C7-only で十分な性能が出ており、**FROZEN(完成世代)** とみなす。 - L1: HotBox v2 世代 - Tiny front v3 + TinyHeap v1(小クラス)。 - MID v3(257–768B の mid/smallmid を TLS heap で扱う)。 - V6 C6-only headerless core(RegionId + Segment + TLS lane)の研究箱。 - L2: Segment / Superslab / Warm / Remote - L3: Policy/Learner + Stats + ENV(ACE/ELO/CAP 等) この世代では、各帯に特化した箱(ULTRA / MID v3 / V6)を積み上げることで Mixed 16–1024B を ~30M → ~44M ops/s まで底上げしたが、 small〜mid を一体で見る「共通の SmallObject コア」は存在しない。 ### 1-2. v7 世代の狙い v7 は L1 に新しく追加する **SmallObjectHotBox_v7** として設計する: ```text Front (size→class→route LUT) | +-- L0: ULTRA lanes (C4–C7, 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 世代(C4–C7)は 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-Fast/legacy/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 の hint(size→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 path(fast 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 (さらに大きい / 特殊用途) ``` 方針: - **共通化するもの**: - RegionIdBox(ptr→region_kind + segment/page_idx) - Segment geometry API(small 用と mid 用に派生しても良い) - PageStats 基本構造(class_idx / alloc/free/remote / live/peak など) - **専用箱にするもの**: - SmallHeapCtx_v7(small 特有の TLS/prefetch/クラス配置) - MidHeapCtx_v7(mid 特有の 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 stub(route だけ 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 allocation(micro, 但し 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 v3(mimalloc に追いつく世代)のターゲット像 この節は、「いま以降の HAKMEM 開発は何を目指すか」をブレないようにするためのメモです。 ### 9-1. 性能ターゲット(Mixed 16–1024B) - 評価プロファイル: - `HAKMEM_PROFILE=MIXED_TINYV3_C7_SAFE` - `ws=400`, `iters=1,000,000`, size range `16–1024B` - 比較対象: - mimalloc: 現状 ~ 110–120M ops/s - HAKMEM v2 世代: ~ 40–45M ops/s(ULTRA + 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 (769–1024B): - L0 C7 ULTRA が本命。v7 small/mid コアからは除外し、ULTRA Box を前提に最適化を続ける。 - C6 (513–768B): - v7 SmallHeapCtx の本命クラス。 - C6-only v7 を最初のターゲットとして、legacy/MID/v6 を相手に A/B しながらコアの形を固める。 - C5 (257–512B): - 現状は MID v3 + ULTRA でそこそこ速く、本線としては安定。 - v7 への移行は C6 v7 が legacy/MID を上回るめどが立ってから、段階的に検討する。 - C4 (129–256B): - C4 ULTRA(寄生型)が効いている帯。v7 に乗せるかは慎重に検討。 - 当面は ULTRA を前提とし、v7 small コアには無理に載せない。 - C3–C0 (≤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-dependent(C5-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]; // C0–C7 } 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/L0(ULTRA / 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