Files
hakmem/docs/analysis/TINY_HEAP_V2_DESIGN.md
Moe Charm (CI) 8f18963ad5 Phase 36-37: TinyHotHeap v2 HotBox redesign and C7 current_page policy fixes
- Redefine TinyHotHeap v2 as per-thread Hot Box with clear boundaries
- Add comprehensive OS statistics tracking for SS allocations
- Implement route-based free handling for TinyHeap v2
- Add C6/C7 debugging and statistics improvements
- Update documentation with implementation guidelines and analysis
- Add new box headers for stats, routing, and front-end management
2025-12-08 21:30:21 +09:00

20 KiB
Raw Blame History

TinyHeap v2 Design (入口メモ)

現状の v2 (Phase32 時点)

  • C7 専用で「1 枚 lease + current/freelist を v2 で握る」状態。ページ供給・meta/ss_active/Remote/Stats はすべて v1 TinyHeap/C7 SAFE に委譲。
  • A/B でいつでも v1 に戻せる(HAKMEM_TINY_HOTHEAP_V2 / HAKMEM_TINY_HOTHEAP_CLASSES)。性能はまだ v1 と同等を維持するのが目的。

Phase33: C7 v2 ON/OFF A/B現状評価

  • 条件: C7 SAFE (PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_LARSON_FIX=1), class mask 0x80, HEAP_STATS=ON。
  • C7-only (ws=64, iters=20k): v2 OFF 39.42M ops/s / v2 ON 43.55M ops/scls7 fast=11015 / slow=1、v2 alloc/free カウンタ増加)。
  • Mixed 161024B (ws=256, iters=20k): v2 OFF 40.44M ops/s / v2 ON 36.58M ops/scls7 fast=5691 / slow=1、v2 カウンタ増加)。
  • 現状の v2 は current/freelist を自前化しつつ、pop/free は v1 の tiny_heap_page_pop/free_local を呼ぶラッパ。C7-only はわずかにプラスだが、Mixed では lease 判定や v1 呼び出し重複のオーバーヘッドが目立つ。Phase34 で枝/ロード削減を検討。

ゴール

  • mimalloc の heap→page→block に近い形で C5/C6/C7 を 1 つの TinyHotHeap に統合し、Gate/UC/TLS-SLL をさらに薄くする。
  • v1 で得た C7 SAFE の current 固定・delta/Stats Box をクラス共通のポリシーとして一般化し、Tiny 層全体の命令数を半分近く狙う。
  • 学習層はこれまで通り「外の箱」Policy Snapshotに閉じ込め、ホットパスはポリシー値を読むだけにする。

前提

  • Cold Stats/Guard/Tier は v1 の Cold Stats Box を土台にし、バッチ/集計を Cold 側で吸収する。
  • C6 TinyHeap は v1 では凍結したまま。v2 では C5C7 を最初から設計し直す前提で扱う。

方針候補(箇条書き)

  • C5C7 を 1 つの TinyHotHeap にまとめ、class ごとの current_page ポリシーC7 SAFE を一般化)を持たせる。
  • Gate/Route を「size→class→hotheap/legacy」の 1 LUT + 1 分岐に統一し、C6/C7 の直線フロントをデフォルト化。
  • Stats Box を前提に、meta->used / ss_active_* は Hot 側で直接触らず、イベント + バッチ更新に寄せる。
  • C6 は v1 で封印したまま、v2 で C7 SAFE 流の current/delta を最初から組み込む。
  • 学習/Policy は Snapshot Box 化し、ホットパスはポリシー値読み取りのみ(学習の ON/OFF でホットパスの命令数が変わらないようにする)。

ゴール / 非ゴールv2 スコープの固定)

  • ゴール:
    • C5C7 を 1 つの TinyHotHeapHotHeap v2に統合し、heap→page→block のみに集中するホット層を構築する。
    • C7-only / Mixed で mimalloc に 1.5〜2× 近づける “理論上” の構造(フロントと Cold 更新の命令数を半減させる)。
    • Hot 層は current 固定delta/Stats Box バッチを前提とし、学習層は Snapshot Box に閉じ込めてホットパスから排除する。
  • 非ゴール:
    • v2 では C0C4 や巨大サイズ帯は触らないTiny 以外は対象外)。
    • 学習層のポリシー/ブの仕様変更は行わないSnapshot の読み取りだけを継続)。
    • v1 の C7 SAFE プロファイルを壊さないv1/v2 の A/B 共存を前提)。

箱構造HotHeap v2 の 1 枚図イメージ)

  • TinyHotHeapBox (per-thread):
    • hot.cls[5..7] に current_page / partial / full / stride を持つ。
    • API:
      • hot_alloc(ci) / hot_free(ci, p)
      • hot_refill(ci)cold へ 1 箇所だけ触れる境界)
  • ColdSuperslabBox / ColdStatsBox / PolicySnapshotBox / LearningBox外側の箱:
    • Hot からは「イベント/要求」を 1 回だけ投げる。
    • ColdStatsBox は delta→バッチ→meta/ss_active_* 反映を担うv1 Stats Box 互換インタフェースを維持)。

移行戦略と Gate/ENV

  • フラグ:
    • HAKMEM_TINY_HOTHEAP_V2=1 で v2 を有効化(デフォルト OFF
    • HAKMEM_TINY_HOTHEAP_CLASSES でクラスマスク(初期は 0x80=C7 のみ、段階的に 0xE0=C5C7 へ)。
  • A/B 方針:
    • v1 TinyHeapC7 SAFEと v2 HotHeap を完全に切り替え可能にする。
    • Gate/Route は snapshot LUT で「class→route(v1/v2/legacy)」を決定し、1 LUT + 1 分岐の形を維持。

v2 で“変えない”もの

  • Learning/ACE/ELO: PolicySnapshot の更新だけを学習側が持ち、ホットパスは snapshot 値を読むだけ。
  • Cold Stats Box 呼び出しインタフェース: v1 と互換flush イベントの場所は同じ)。
  • Fail-Fast 方針: 範囲外・magic 不一致は即 abort。Superslab/Tier/Guard の不変条件を崩さない。

実装ステップ3 分割プラン)

  1. Step1: v2 用 TinyHotHeapBox 型と APIhot_alloc/free/refillを追加し、コンパイルパスだけ通すまだ未使用
  2. Step2: C7-only を v2 に載せる A/BHOTHEAP_V2=1 & mask=0x80 で C7 だけ新経路、v1 と並走)。
  3. Step3: Mixed のクラス分布を見ながら C6→C5 の順で HotHeap へ移すか判断(マスク 0xC0→0xE0 を段階的に開ける)。

Phase30 (done): 骨組みだけコードに追加

  • core/box/tiny_hotheap_v2_box.h に v2 用の page/class/ctx 型と stub APIalloc/free/tlsを追加。
  • ENV gate だけ先行HAKMEM_TINY_HOTHEAP_V2、HAKMEM_TINY_HOTHEAP_CLASSESを用意し、既存経路とは未接続。
  • TLS スロットも確保したが、alloc/free は現時点では NULL/no-op。フロント配線は Phase31 以降で A/B 導入予定。

Phase31: C7-only を v2 ラッパ経由で A/B 可能に

  • Gate: tiny_c7_hotheap_v2_enabled()HAKMEM_TINY_HOTHEAP_V2=1 かつ mask bit7を追加。
  • Route snapshot: bit7 が立っていれば g_tiny_route_class[7]=TINY_ROUTE_HOTHEAP_V2 に設定(デフォルトは Legacy/HEAP のまま)。
  • Front: malloc_tiny_fast / free_tiny_fast の C7 直線パスで v2→v1→legacy slow の順に試行v2 が NULL のとき v1 にフォールバック)。
  • Impl: v2 alloc/free は現時点で v1 の薄ラッパ実際の挙動は変えない。TLS ctx で C7 stride を初期化し、簡易カウンタで v2 パスのヒットを把握できるようにした。他クラスは v2 未対応のまま。Superslab/Remote/Stats など Cold 処理はすべて v1 に委譲。
  • A/BRelease, HEAP_STATS=ON: C7-only 43.28Mv2 ON/OFF 差なし、Mixed 161024B は LEGACY 42.18M / C7_SAFE v2 OFF 41.15M / v2 ON 40.74Mcls7 fast=5691 / slow=1 で一致、v2 カウンタ増加のみ)。

Phase32: C7 で current_page+freelist を v2 側に持ちつつ、ページ供給は v1 から lease

  • v1 に tiny_heap_c7_lease_page_for_v2() を置き、C7 SAFE が保持しているページ情報meta/ss/base/capacityを lease する薄い境界を追加。
  • v2 TLS ctx に C7 用の storage_page を持たせ、current_page が空のとき lease した v1 page をラップして保持。Hot 部分の pop/push は v1 の tiny_heap_page_pop/free_local を直接叩き、meta/ss_active の整合は v1 に任せる。
  • Free もまず v2 の current_page を優先し、範囲外や meta 不一致は従来の C7 free にフォールバック。Superslab/Remote/Stats など Cold 処理は依然 v1 に委譲lease を返却しない簡易版)。
  • まだ性能は見ず、v2 の Hot 部分が自前で current_page を握れることだけ確認する段階。

Phase33 以降の選択肢

  • A) v2 で current_pagefreelist の多ページ対応・ページ返却まで拡張し、Superslab への触れ方を v2 専用に寄せるv1 はページ供給だけの Box にする)。
  • B) 当面 v2 は C7 限定のラッパ単一ページ管理のまま据え置き、mid サイズや他クラスの箱に時間を回す。

Phase34: C7-only 専用モードに一旦限定& v2 用 stats 追加

  • 方針: HAKMEM_TINY_HOTHEAP_V2 は当面 C7-only ベンチ/実験専用。Mixed 161024B では v2 OFF 推奨v1 C7_SAFE を使用)。
  • v2 専用 statsENV: HAKMEM_TINY_HOTHEAP_V2_STATS=1)を追加:
    • alloc_calls / alloc_fast / alloc_lease / alloc_fb_v1
    • free_calls / free_fast / free_fb_v1
    • destructor で [HOTHEAP_V2_C7_STATS] を 1 行 dump。
  • これで Mixed 時に「v2 がどこまでヒットしているか」「どれだけ v1 へフォールバックしているか」を切り分ける予定。
  • 次フェーズ候補:
    • パターンA: Mixed でも v2 のヒット率が高い → 枝/チェックの純コスト削減hot path flattenを検討。
    • パターンB: Mixed で v1 fallback が多い → v2 を C7-heavy 専用に割り切る or C7 判定をさらに絞る。

Phase35: v2 をいったん棚上げ(標準は v1+C7_SAFE に集中)

  • 観測結果: C7-only/Mixed いずれも v2 ON で大幅に劣化alloc_fast がほぼ当たらず、lease→v1 fallback が支配)。
  • 運用方針:
    • HAKMEM_TINY_HOTHEAP_V2=0 を標準C7_SAFE v1 がデフォルト)。
    • v2 は C7-only の研究用に 明示的に ON したときだけ使用。Mixed では OFF 推奨。
  • 設計メモ:
    • 現行 v2 は v1 へのフォールバック前提で、Hot path の枝コストと fallback 多発が主因で負けている。
    • 続けるなら current/freelist を完全に自前管理し、Superslab との境界だけ v2 専用に切る再設計が必要。

Phase36+: TinyHotHeap v2 を「Hot Box」として再定義する

背景(棚上げからの再出発)

  • Phase35 までの v2 は「v1 TinyHeap/C7 SAFE の上に乗るラッパ」であり、
    • ページ供給・meta/ss_active/Remote/Stats はすべて v1 に委譲
    • current/freelist も最終的には v1 の API を介して触る構造 だったため、Mixed では構造的に v1 に勝てない 状態だった。
  • Box Theory 的に見ると、
    • v2 の Hot 部と v1 TinyHeap の境界が曖昧(箱が二重になっている)
    • Cold BoxSuperslab/Tier/Statsとの接点も v1/v2 の両方に分散 しており、「境界 1 箇所」の原則から外れている。
  • そこで Phase36 以降は:

    TinyHeap v2 自体を per-thread Hot Box として再定義し、
    Superslab / Tier / Remote / Stats / Learning をすべて外側の Cold Box に落とす
    という方針に切り替える。

3 つの設計案A/B/C

ここからの v2 には、Tiny 層をどこまで巻き取るかで 3 案ある。

  1. A案: C5〜C7 を 1 つの TinyHotHeap に統合
    • TinyHeapCtxper-threadTinyClassHeapper-classを定義し、
      current/partial/full/page freelist を 1 箱にまとめる標準案。
    • Superslab/Warm/Tier/Guard との境界は
      • alloc 側: tiny_heap_refill_slow(th, ci)
      • free 側: tiny_heap_page_retire_slow(th, page) だけに集約する。
  2. B案: C7 超ホットレーン + C5/C6 セミホットレーン
    • A案の上に「C7 専用 fast lane (c7_fastlist + c7_current)」を乗せて、
      mimalloc クラスの C7-only 性能を狙う二車線構造。
  3. C案: mimalloc 風 Segment+Page+Block をフル導入
    • Superslab/Tier/Guard を 1 つの Segment Box として再定義し、
      TinyHotHeap v2 の下に「Segment→Page→Block」の階層を敷き直す v3 相当案。

このドキュメントでは A案TinyHotHeap v2 = Hot Boxを第一候補 とし、
実装を進めやすい形に具体化する。B/C 案は A が安定したあとに検討する。

TinyHotHeap v2 の箱構造A案

Box Theory 観点での分割は次の通り:

  • Hot Box: TinyHotHeapBox v2per-thread
    • 中身: TinyHeapCtx / TinyClassHeap / TinyPageMeta
    • 役割: heap→page→block のみを扱い、Tiny 層の命令数を支配する唯一のホット層
  • Cold Box: Superslab/Segment + Tier + Guard + Remote
    • 役割: 大きなチャンク管理、Tier/HOT/DRAINING/FREE 状態管理、Remote Queue、Guard/Budget。
    • Hot からは refill_slow / page_retire_slow でしか触らない。
  • Policy Box: TinyPolicySnapshot
    • 役割: クラスごとの heap_enabled / max_partial_pages / warm_cap などを保持。
    • Hot は「現在のスナップショットを読むだけ」で、学習の有無を意識しない。
  • Stats Box / Learning Box:
    • 役割: page 単位の delta を受け取り、Cold 側で集計・可視化・学習を行う。
    • Hot は「page を refill/retire するとき」にだけ delta を渡す。

Hot Box のデータ構造(案)

// ページ内メタデータ(ホット側が見る最小限)
typedef struct TinyPageMeta {
    void*     free_list;   // ページ内の block 単位 LIFO freelist
    uint16_t  used;        // 使用中 block 数
    uint16_t  capacity;    // page あたりの総 block 数
    uint16_t  class_idx;   // Tiny class (C5〜C7)
    uint16_t  flags;       // HOT/PARTIAL/FULL など
    void*     slab_ref;    // Superslab/Segment 側への back pointerCold Box 用)
} TinyPageMeta;

// クラスごとの Hot heap 状態
typedef struct TinyClassHeap {
    TinyPageMeta* current;      // 直近で使用中の page
    TinyPageMeta* partial_head; // 空きあり page のリスト
    TinyPageMeta* full_head;    // full pageレアイベントのみ参照
} TinyClassHeap;

// TLS ごとの TinyHotHeap コンテキスト
typedef struct TinyHeapCtx {
    TinyClassHeap cls[8];  // とりあえず C0〜C7。v2 では C5〜C7 を優先して有効化
} TinyHeapCtx;

Hot パス APIalloc/free

allocHot

void* tiny_heap_alloc_fast(TinyHeapCtx* th, uint32_t ci) {
    TinyClassHeap* h = &th->cls[ci];
    TinyPageMeta* p = h->current;

    // 1. current page から pop
    if (likely(p && p->free_list)) {
        void* block = p->free_list;
        p->free_list = *(void**)block;
        p->used++;
        return block;
    }

    // 2. partial list から page を 1 枚取って current に昇格
    if (h->partial_head) {
        p = h->partial_head;
        h->partial_head = p->next;   // intrusive list 想定
        h->current = p;
        // 取り直した current から改めて pop
        ...
    }

    // 3. ここで初めて Cold Box に降りる
    return tiny_heap_refill_slow(th, ci);
}

freeHot

void tiny_heap_free_fast(TinyHeapCtx* th, void* ptr) {
    TinyPageMeta* p = tiny_page_of(ptr);  // base から逆算 or side meta

    // ページ内 freelist に push
    *(void**)ptr = p->free_list;
    p->free_list = ptr;
    p->used--;

    if (unlikely(p->used == 0)) {
        // page 完全 free → Cold Box に返却
        tiny_heap_page_retire_slow(th, p);
    }
}

Hot→Cold 境界1 箇所ルール)

  • tiny_heap_refill_slow(th, ci)
    • Warm/Shared/Superslab/Segment/Tier/Guard に対して
      「class ci 用の page を 1〜数枚ください」と要求する唯一の関数。
    • 返ってきた page 群を current または partial_head に接続するだけで、
      meta->used / ss_active_* / Tier 状態は Cold Box 側が責任を持つ。
  • tiny_heap_page_retire_slow(th, p)
    • p->used == 0 のときだけ呼ぶ。
    • page の返却/再利用Warm Pool への push や Superslab への返却)と、
      Cold Stats/Guard の更新をまとめて処理する。

この 2 関数だけが Hot→Cold の境界となるため、
Box Theory の「境界 1 箇所」に近い構造でデバッグ/A/B を行える。

Stats / Learning の配置

  • Hot Box:
    • TinyPageMeta に軽量なローカルカウンタのみを持たせる(例: uint16_t local_allocs; など)。
    • tiny_heap_refill_slow / tiny_heap_page_retire_slow のタイミングで
      page 単位の delta を Cold Stats Box に flush する。
  • Cold Stats Box:
    • Superslab/Segment/class 単位の累積 stats を保持。
    • Learning Box からのみ読まれ、Hot Box は直接読まない。
  • Learning Box:
    • 一定周期で Stats から TinyPolicySnapshot を更新し、
      heap_enabled[class] / max_partial_pages[class] / warm_cap[class] を調整する。
    • Hot Box は ci 決定後に snapshot を読むだけで、
      Learning の ON/OFF によって命令数が変わらないようにする。

ENV / Gate / A/B 方針v2 再開時のルール)

  • ENV:
    • HAKMEM_TINY_HOTHEAP_V2 … v2 Hot Box を有効化(デフォルト 0
    • HAKMEM_TINY_HOTHEAP_CLASSES … v2 適用クラスの bitmask。初期は C7-only (=0x80)、
      様子を見ながら 0xC0(C6+C7)→0xE0(C5〜C7) へ広げる。
  • Route Snapshot:
    • g_tiny_route_class[ci]TINY_ROUTE_HOTHEAP_V2 / TINY_ROUTE_HEAP_V1 / TINY_ROUTE_LEGACY を設定。
    • Gate は size→class→route を 1 LUT + 1 分岐で決める薄い front を維持する。
  • A/B:
    • C7-only: HAKMEM_TINY_HOTHEAP_V2=1 HAKMEM_TINY_HOTHEAP_CLASSES=0x80 で v2 経路に切り替え、
      =0 で v1 C7_SAFE に戻せるようにする。
    • Mixed 161024B: 当面は v1 C7_SAFE を基準とし、C7-only が安定してから C6/C5 を昇格させる。

将来の C 案Segment+Page+Blockへの橋渡し

  • 上記 A 案TinyHotHeap v2 = Hot Boxは、
    mimalloc 風の Segment+Page+Block 構造C 案)の Hot 部分 とほぼ一致している。
  • 今後 C 案へ進める場合も、
    • Hot Box の API (tiny_heap_alloc_fast/free_fast/refill_slow/page_retire_slow) は変えず、
    • Cold Box 側で Superslab/Tier/Guard を「Segment Box」として整理する ことで、段階的に v3 へ移行できる。
  • このため、Phase36 以降の v2 実装は 「C 案の Hot 部だけを先に完成させる」 方針で進める。

次にやること(実装タスクの箱化)

  • docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md に、
    実装手順C7-only → C5〜C7 への拡張)と関数名/API の具体案をまとめる。
  • CURRENT_TASK.md に Phase36 として「TinyHotHeap v2 再定義Hot Box 化)」を追加し、
    v2 の現状ステータスと今後のタスク窓口をこのドキュメントに集約する。

Phase36 (C7-only Hot Box 暫定実装)

  • 状態: v2 は C7-only 実験箱のまま。デフォルトは HAKMEM_TINY_HOTHEAP_V2=0v1 C7_SAFE が標準)。
  • 実装ポイント:
    • TLS 取得で全 class の storage_page を resetstride 初期化。page node は storage を優先し、足りなければ calloc。
    • Hot パス: tiny_hotheap_v2_alloc/free が current_page → partial → refill を処理し、lease した v1 page を pop/push して meta/ss を同期。used==0 は retire_slow でリセット。
    • Cold 境界: tiny_hotheap_v2_refill_slowtiny_heap_c7_lease_page_for_v2() 経由で 1 枚借りる)と tiny_hotheap_v2_page_retire_slow返却resetの 2 箇所のみ。Superslab/Tier/Stats は v1 が保持。
    • Route/Gate: Route Snapshot に TINY_ROUTE_HOTHEAP_V2 を設定し、C7 直線パス/汎用パスとも route==v2 のときだけ HotHeap v2 を呼ぶ。その他は v1 → legacy slow へフォールバック。
  • 今後: v2 は C7-only bench 用の A/B 前提。Mixed では v2 OFF 推奨。より本格的に進めるなら、v1 依存の lease/pop/push を減らし、Cold Stats Box 経由の更新に寄せる必要がある。