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

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

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 に配置し、「どのクラスをどの層に送るか」を一元管理:

typedef struct SmallPolicyV7 {
    SmallRouteKind route_kind[8];  // C0-C7
} SmallPolicyV7;

const SmallPolicyV7* small_policy_v7_snapshot(void);

フロントの責務

フロントは Policy Snapshot を読んで route を選ぶだけ:

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 拡張:

    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 を学習

// 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〜C7HAKMEM_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_liveoptional
  2. Learner 集約構造:

    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:

    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 時に書く方式を維持
// 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 共有モデル

// 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 拡張

// 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)

// 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

# 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 が読むべきフィールド

// 既存 (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 側の集約構造案

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 シグネチャ

// 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