Files
hakmem/docs/analysis/SMALLOBJECT_V7_DESIGN.md
Moe Charm (CI) 39a3c53dbc Phase v7-2: SmallObject v7 C6-only implementation with RegionIdBox integration
- SmallSegment_v7: 2MiB segment with TLS slot and free page stack
- ColdIface_v7: Page refill/retire between HotBox and SegmentBox
- HotBox_v7: Full C6-only alloc/free with header writing (HEADER_MAGIC|class_idx)
- Free path early-exit: Check v7 route BEFORE ss_fast_lookup (separate mmap segment)
- RegionIdBox: Register v7 segment for ptr->region lookup
- Benchmark: v7 ON ~54.5M ops/s (-7% overhead vs 58.6M legacy baseline)

v7 correctly balances alloc/free counts and page lifecycle.
RegionIdBox overhead identified as primary cost driver.

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

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2025-12-12 03:12:28 +09:00

12 KiB
Raw Blame History

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 として設計する:

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 用フィールドを分離する。

// 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 を持つ。

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 を定義する。

#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_SHIFTpage_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 を定義する:

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:

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 は別箱に分けるのが現実的:

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