## 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 を壊さない形で徐々に適用範囲を広げていく。