# TinyHotHeap v2 Implementation Guide (A案 → C案の入口) 日付: 2025-12-05 対象: 実装担当 AI / 開発者向け指示書 関連ドキュメント: - `docs/analysis/TINY_HEAP_V2_DESIGN.md` (Phase36+ 設計 / A/B/C 案・箱構造) - `docs/analysis/TINY_HEAP_BOX_DESIGN.md` (v1 TinyHeapBox / C7 HotBox) - `docs/analysis/C7_HOTBOX_DESIGN.md` (既存 C7 HotBox 設計) - `CURRENT_TASK.md` (Phase36 状況メモ) - ステータス: Phase36 で C7-only の Hot 部(current_page+freelist)を v2 で自前管理する暫定実装を投入済み。Superslab/Tier/Stats は v1 に委譲する lease/refill/retire 境界で A/B できる状態。 --- ## 0. ゴールと非ゴール - ゴール - TinyHotHeap v2 を **per-thread Hot Box** として実装し、C7-only から段階的に C6/C5 へ拡張できる骨格を作る。 - Hot Box 側は **heap→page→block のみ** を扱い、Superslab/Tier/Remote/Stats/Learning との境界は - alloc 側: `tiny_hotheap_v2_refill_slow(...)` - free 側: `tiny_hotheap_v2_page_retire_slow(...)` の 1 箇所に集約する(Box Theory: 境界 1 箇所)。 - A/B: `HAKMEM_TINY_HOTHEAP_V2` / `HAKMEM_TINY_HOTHEAP_CLASSES` / Route Snapshot により、 C7-only で v1 C7_SAFE と v2 Hot Box を即時切り替え可能にする。 - 非ゴール(このガイドの範囲外) - Superslab/Tier/Guard/Remote のフル再設計(C 案の Segment+Page+Block までは行かない)。 - C0〜C4 や mid/large サイズ帯の設計変更。 - 学習層 (ACE/ELO) の仕様変更。Hot path は従来どおり Snapshot を読むだけとする。 --- ## 1. 箱の対応関係(概念 → 既存型へのマッピング) 設計ドキュメントでは便宜的に: - `TinyPageMeta` / `TinyClassHeap` / `TinyHeapCtx` という名称で説明しているが、実装では既に存在する v2 skeleton を利用する: - ファイル: `core/box/tiny_hotheap_v2_box.h` - `tiny_hotheap_page_v2` … 概念上の `TinyPageMeta` - `tiny_hotheap_class_v2` … 概念上の `TinyClassHeap` - `tiny_hotheap_ctx_v2` … 概念上の `TinyHeapCtx` 実装では **この既存ヘッダを Hot Box の中核** とし、 他のファイルからは基本的に `tiny_hotheap_v2_*` API 経由でしか触らない。 --- ## 2. Hot Box 実装ステップ ### Step 2-1: 型の見直しと拡張 (core/box/tiny_hotheap_v2_box.h) 目的: `tiny_hotheap_page_v2` / `tiny_hotheap_class_v2` / `tiny_hotheap_ctx_v2` を Phase36 設計の A 案(Hot Box)に揃える。 作業指示: - `tiny_hotheap_page_v2` - すでに `freelist/used/capacity/base/meta/ss/slab_idx/next` を持っているので、 **ページ内 freelist と Cold 側リンク用の最小限メタ** として継続利用する。 - 必要であれば `flags`(HOT/PARTIAL/FULL)などの軽量フラグを追加してよい。 - `lease_page` フィールドは「v1 page への橋渡し用」なので、 Phase1 では残しておき、将来的には A 案が安定した時点で削除/無効化を検討する。 - `tiny_hotheap_class_v2` - `current_page` / `partial_pages` / `full_pages` が A 案の `TinyClassHeap` に相当する。 - `storage_page` は Phase32 の「C7 1 枚 lease」専用だが、Phase1 では - C7-only: storage を current_page として使う - 複数ページ化後: storage は「最初の page」または「固定 1 枚キャッシュ」として扱う 形で再利用してよい(完全削除は後のフェーズでも可)。 - `stride` はクラスごとの block サイズ(バイト数)として維持。 - `tiny_hotheap_ctx_v2` - `cls[TINY_HOTHEAP_MAX_CLASSES]` の配列で全 Tiny class をカバー済み。 Phase1 では **C7-only を有効** にし、C6/C5 は未使用のままでよい。 このステップでは **まだ関数実装をいじらず、型だけを A 案に整合** させる。 ### Step 2-2: TLS 取得 API の実装 (core/hakmem_tiny.c など) 目的: Hot Box の入口を 1 箇所に揃える。 作業指示: - `tiny_hotheap_v2_tls_get()` の実装: - `g_tiny_hotheap_ctx_v2`(`__thread`)を lazily `malloc`/`calloc` し、`memset` でゼロ初期化。 - 初期化時に `tiny_hotheap_v2_page_reset(&cls[i].storage_page)` など、 最低限のリセットを行う(Box 内に初期化を閉じ込める)。 - 返り値は `tiny_hotheap_ctx_v2*`。以降、Hot Box の関数は **必ず ctx を通して状態にアクセス** する。 ### Step 2-3: alloc Hot パスの実装 (tiny_hotheap_v2_box.h) 目的: C7-only の `tiny_hotheap_v2_alloc()` を **page 内 freelist + current/partial** だけで完結させる。 推奨構造: - `static inline void* tiny_hotheap_v2_alloc_fast(tiny_hotheap_ctx_v2* ctx, uint8_t class_idx);` - `void* tiny_hotheap_v2_alloc(uint8_t class_idx);` は Gate から呼ばれる薄いラッパとして、 - `ctx = tiny_hotheap_v2_tls_get();` - `return tiny_hotheap_v2_alloc_fast(ctx, class_idx);` だけを行う。 Hot パスのロジック(C7 専用版): - `tiny_hotheap_class_v2* hc = &ctx->cls[class_idx];` - 優先順位: 1. `hc->current_page` があり、その `freelist` が非 NULL → 1 block pop して `used++`。 2. `hc->partial_pages` リストがあれば 1 枚取り出し、`current_page` に昇格させてから pop。 3. どちらも空なら `tiny_hotheap_v2_refill_slow(ctx, class_idx)` へ降りる。 注意: - Phase1 では **C7-only** を対象とし、`class_idx == 7` 以外は即座に v1 経路へフォールバックしてよい。 - `tiny_hotheap_v2_refill_slow` は次節の Cold Box との境界関数として後から実装する。 ### Step 2-4: free Hot パスの実装 (tiny_hotheap_v2_box.h) 目的: C7-only の `tiny_hotheap_v2_free()` を page 内 freelist push + empty 判定だけにする。 推奨構造: - `static inline void tiny_hotheap_v2_free_fast(tiny_hotheap_ctx_v2* ctx, uint8_t class_idx, void* p, struct TinySlabMeta* meta);` - `void tiny_hotheap_v2_free(uint8_t class_idx, void* p, void* meta);` は Gate 用ラッパとして、 - `ctx = tiny_hotheap_v2_tls_get();` - `tiny_hotheap_v2_free_fast(ctx, class_idx, p, (struct TinySlabMeta*)meta);` のみ行う。 Hot パスのロジック(C7 専用版): - `tiny_hotheap_page_v2* page = tiny_hotheap_v2_page_of(ctx, class_idx, p, meta);` - Phase1 では meta/base/slab_idx から page を特定するヘルパを 1 箱に閉じ込める(実装場所はこのヘッダ or 専用 C ファイル)。 - `*(void**)p = page->freelist; page->freelist = p; page->used--;` - `page->used == 0` になったら `tiny_hotheap_v2_page_retire_slow(ctx, class_idx, page);` を呼ぶ。 備考: - Phase1 では **Remote Queue / cross-thread free は従来経路にフォールバック** してよい。 具体的には: - 所有スレッド以外からの free であれば v1 の remote 経路を即呼び出す。 - 所有スレッド判定には既存の TLS/owner 情報を使う(Ownership Box を汚さない)。 --- ## 3. Cold Box 境界実装ステップ ### Step 3-1: refill_slow(v1 TinyHeap / Superslab からのページ供給) 目的: Hot Box から見て **唯一の「ページ供給」入口** を 1 箇所に集約する。 作業指示: - 関数案(実装ファイルは `core/hakmem_tiny.c` か、専用の `core/box/tiny_hotheap_v2_cold_box.h` でもよい): ```c tiny_hotheap_page_v2* tiny_hotheap_v2_refill_slow(tiny_hotheap_ctx_v2* ctx, uint8_t class_idx); ``` - Phase1 では実装を **既存 v1 TinyHeap/C7 SAFE に橋渡しするだけ** に留める: - `tiny_heap_c7_lease_page_for_v2()` など、既存の C7 lease API を利用して 1 枚 page をもらう。 - 取得した `TinySlabMeta*` / `SuperSlab*` / `base` / `capacity` を `tiny_hotheap_page_v2` にコピーし、`current_page` or `partial_pages` に追加。 - freelist の初期化は **Hot Box 側で実施** し、以降の pop/push は v2 が握る(v1 に再委譲しない)。 ### Step 3-2: page_retire_slow(全 free になったページの返却) 目的: ページが empty になった瞬間だけ Cold Box に touch する。 作業指示: - 関数案: ```c void tiny_hotheap_v2_page_retire_slow(tiny_hotheap_ctx_v2* ctx, uint8_t class_idx, tiny_hotheap_page_v2* page); ``` - Phase1 の挙動: - page を `current_page` / `partial_pages` / `full_pages` から unlink。 - v1 側に「ページ free」イベントとして渡す: - Superslab / Warm Pool / Tier / Guard の扱いは v1 に任せる(v2 は触らない)。 - Cold Stats Box があれば、ここで delta を flush する。 - 将来フェーズ: - Warm Pool / Superslab への返却ロジックを v2 専用に寄せることで、 C 案(Segment+Page+Block)への移行パスとして利用する。 --- ## 4. Gate / Route / ENV の配線ステップ ### Step 4-1: Route Snapshot への v2 統合 作業指示: - ファイル: `core/box/tiny_route_box.h` 付近(`g_tiny_route_class[]` の決定ロジック)。 - ENV: - `HAKMEM_TINY_HOTHEAP_V2` / `HAKMEM_TINY_HOTHEAP_CLASSES` を読み、 - bit7 が立っていれば `g_tiny_route_class[7] = TINY_ROUTE_HOTHEAP_V2;` - それ以外は従来通り `TINY_ROUTE_HEAP_V1` / `TINY_ROUTE_LEGACY` を設定する。 - これにより front 側は `class_idx` 決定後に - `route = g_tiny_route_class[class_idx];` - `switch (route) { HOTHEAP_V2 / HEAP_V1 / LEGACY }` だけで経路を決定できる。 ### Step 4-2: front からの呼び出し(malloc_tiny_fast / free_tiny_fast) 作業指示: - ファイル: `core/hakmem_tiny.c` + 各種 `tiny_alloc_fast*.inc.h` / `tiny_free_fast_v2.inc.h`。 - C7-only モードの Gate で: - `route == TINY_ROUTE_HOTHEAP_V2` かつ `size` が C7 クラスにマップされる場合にのみ `tiny_hotheap_v2_alloc(7)` / `tiny_hotheap_v2_free(7, p, meta)` を呼ぶ。 - それ以外は従来通り v1 TinyHeap / legacy 経路へフォールバックする。 - Free 側では: - C7 ptr かつ Route=HOTHEAP_V2 かつ owner==self の場合のみ `tiny_hotheap_v2_free` を試す。 - 条件に当てはまらない場合は Remote Queue / v1 free 経路にすぐ落とす。 --- ## 5. 可視化・Fail-Fast・A/B の運用 ### Step 5-1: v2 専用 Stats の追加(任意だが推奨) 作業指示: - 既存の `tiny_stats_box` / `tiny_class_stats_box` に倣って、 - `alloc_calls / alloc_fast / alloc_slow_refill / alloc_fb_v1`、 - `free_calls / free_fast / free_fb_v1` のような v2 専用カウンタを追加。 - ENV: - `HAKMEM_TINY_HOTHEAP_V2_STATS=1` のときだけ destructor で 1 行ログを出す。 ### Step 5-2: Fail-Fast ポリシー 作業指示: - 範囲外 ptr / page_of 失敗 / meta 不整合など、**Hot Box の前提が壊れた場合は即 abort** する。 - v2 実装中は「誤魔化しのフォールバック」を避け、 問題が出たら Fail-Fast で原因を特定する(AGENTS.md の方針通り)。 ### Step 5-3: 推奨ベンチコマンド(実装後に利用) 実装完了後、以下のようなプロファイルで A/B を行う(ここでは指示のみ、実行は別フェーズ): - C7-only: - v1 基準: `HAKMEM_BENCH_C7_ONLY=1 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=0 ...` - v2: `HAKMEM_BENCH_C7_ONLY=1 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=1 HAKMEM_TINY_HOTHEAP_CLASSES=0x80 ...` - Mixed 16–1024B: - 当面は v1 C7_SAFE を基準に残し、v2 は C7-only が十分安定してから評価する。 --- ## 6. 実装順序のまとめ(チェックリスト) - [ ] 型整理: `tiny_hotheap_page_v2` / `tiny_hotheap_class_v2` / `tiny_hotheap_ctx_v2` を Phase36 A 案に整合させる。 - [ ] TLS 初期化: `tiny_hotheap_v2_tls_get()` を実装し、Hot Box 内に状態を閉じ込める。 - [ ] alloc Hot パス: `tiny_hotheap_v2_alloc_fast()` / `tiny_hotheap_v2_alloc()` を C7-only で実装。 - [ ] free Hot パス: `tiny_hotheap_v2_free_fast()` / `tiny_hotheap_v2_free()` を C7-only で実装。 - [ ] Cold 境界: `tiny_hotheap_v2_refill_slow()` / `tiny_hotheap_v2_page_retire_slow()` を v1 TinyHeap への橋渡しとして実装。 - [ ] Route/Gate: `tiny_route_box` と front を更新し、C7-only で v1/v2/legacy を A/B 切り替え可能にする。 - [ ] Stats/Fail-Fast: v2 専用 stats と assert/abort を追加し、問題発生時にすぐ気付けるようにする。 このガイドは「箱」と「境界」の位置だけを固定するものであり、 細かな最適化(C7 超ホットレーン / C6/C5 への拡張 / C 案への橋渡し)は Phase36 以降のサブフェーズで順次詰める前提とする。 --- ## 7. Phase37: C7 current_page ポリシー修正指示書 ベンチ結果サマリ(現状 v2 の問題) ---------------------------------- Release, PROFILE=C7_SAFE での観測: - C7-only (ws=64, iters=20k) - v2 OFF: 39.64M ops/s, `HEAP_STATS[7] fast=11015 slow=1` - v2 ON : 26.89M ops/s, `HEAP_STATS[7] fast=97 slow=32758`(ほぼ全て slow_prepare) - Mixed 16–1024B (ws=256, iters=20k) - v2 OFF: 38.78M ops/s, `fast=5691 slow=1` - v2 ON : 33.26M ops/s, `fast=141 slow=16654` 結論: - v2 ON では `current_page` がほぼ活きておらず、**毎回 slow_prepare に落ちている**。 - 現状の v2 を運用で使うのは危険なため、当面のデフォルトは **v2 OFF (C7_SAFE + HOTHEAP_V2=0)** を維持する。 この Phase37 の目的 ------------------- - 目的: - C7-only で **`current_page` をきちんと保持・再利用し、slow_prepare をほぼゼロに抑える**。 - v1 C7 SAFE / TinyHeapBox の current_page ポリシーを v2 Hot Box に移植する。 - 非ゴール: - C6/C5 への展開。 - Cold Box (Superslab/Tier/Remote/Stats) の構造変更。 ざっくりタスク一覧 ------------------ - [ ] v2 専用 current_page デバッグ統計の追加 - [ ] refill_slow → current_page セット手順の見直し - [ ] free 側で current_page を再利用するポリシーの導入 - [ ] retire 条件の見直し(empty でもすぐ返さない) - [ ] v1 C7 SAFE current_page ポリシーとの比較・差分吸収 - [ ] C7-only / Mixed ベンチで slow_prepare を再確認 以下、それぞれの具体指示。 Step 7-1: current_page デバッグ統計の追加 ---------------------------------------- 目的: - v2 が「どこで current_page を失って slow_prepare に落ちているか」を**1 箇所で見える化**する。 作業指示: - 参考: `docs/analysis/TINY_HEAP_BOX_DESIGN.md` にある C7 current_page stats(`prepare_calls` など)。 - 新規 struct を追加(場所は `tiny_hotheap_v2_box.h` か、専用 stats ファイルでも良い): - `uint64_t prepare_calls;` - `uint64_t prepare_with_current_null;` - `uint64_t prepare_from_partial;` - `uint64_t free_made_current;` - `uint64_t page_retired;` - ENV `HAKMEM_TINY_HOTHEAP_V2_STATS=1` のときのみカウンタを更新・dump する。 - カウントポイント: - `tiny_hotheap_v2_alloc_fast` 内: - entry ごとに `prepare_calls++` - `hc->current_page==NULL` のとき `prepare_with_current_null++` - partial から current に昇格したとき `prepare_from_partial++` - `tiny_hotheap_v2_free_fast` 内: - free で page が current になる/保持される場合 `free_made_current++` - `tiny_hotheap_v2_page_retire_slow` 内: - `page_retired++` Step 7-2: refill_slow → current_page セットの修正 ----------------------------------------------- 目的: - refill 後に必ず「C7 用の current_page が 1 枚セットされる」ようにする。 作業指示: - `tiny_hotheap_v2_refill_slow` の振る舞いを以下のように揃える: 1. C7 用 lease API(`tiny_heap_c7_lease_page_for_v2()` など)から 1 枚 page を取得。 2. `tiny_hotheap_page_v2` へ: - `base / capacity / meta / ss / slab_idx / stride` をコピー。 - freelist を page 内 carving で初期化(pop は v2 が握る)。 3. `tiny_hotheap_class_v2* hc = &ctx->cls[7];` に対して: - `hc->current_page` が NULL なら **必ずここで current_page にセット**。 - 既に current がある場合は `hc->partial_pages` に push。 - refill 後に `hc->current_page` が NULL のままにならないよう、Fail-Fast assert を入れても良い(デバッグ時)。 Step 7-3: free 側で current_page を維持・再利用する ----------------------------------------------- 目的: - C7-only では「同じ page 内で alloc/free が揺れる」ケースが多いため、 free 側で current_page をなるべく保持・再利用する。 作業指示: - `tiny_hotheap_v2_free_fast` で、以下のようなポリシーを入れる(v1 TinyHeapBox SAFE と同等イメージ): - `page->used > 0` かつ `page->freelist != NULL` の page は **current_page 候補**。 - `hc->current_page` が NULL の場合は、free した page を current に据え直す。 - `hc->current_page` が他の page を指していても、C7-only フェーズでは「free された page に空きがあるなら current に切り替える」実験をして良い(後で safe なポリシーに絞る)。 - これにより、C7-only ベンチで `prepare_calls` に対して `prepare_with_current_null` がほぼ 0 に近づくことを期待する。 Step 7-4: retire 条件の見直し ----------------------------- 目的: - v2 ON で empty page を早々に Cold Box に返しすぎると、current_page を毎回失って slow_prepare 連発になる。 作業指示: - `tiny_hotheap_v2_page_retire_slow` を呼ぶ条件を見直す: - Phase37 では「C7-only ベンチでは page が empty になってもすぐには返さない」実験を許容する。 - 具体案: - `page->used == 0` でも、まず `hc->partial_pages` の一員として残す(一定枚数を超えたら返却)。 - C7-only フェーズでは `max_partial_pages` を 1〜2 に固定して試す。 - 本番運用向けには、後続フェーズで安全側にチューニングし直す前提で、「今は slow_prepare を殺すことを最優先」にしてよい。 Step 7-5: v1 C7 SAFE current_page ポリシーとの比較 ----------------------------------------------- 目的: - 「すでにうまく動いている v1 C7 SAFE / TinyHeapBox の current_page 設計」を v2 に再利用する。 作業指示: - 参照: - `docs/analysis/TINY_HEAP_BOX_DESIGN.md` - `docs/analysis/C7_HOTBOX_DESIGN.md` - この2つから: - alloc 時: `prepare_calls=1` に抑えるための current 固定ロジック - free 時: empty page の扱い(ULTRA/SAFE の違い) を抜き出し、v2 Hot Box の `alloc_fast` / `free_fast` / `page_retire_slow` に反映する。 - 差分: - v1 TinyHeapBox は meta/ss_active 更新も Hot 側が握っていたが、v2 では Cold Stats Box に寄せる方針。 - そのため meta/active の更新部分だけは v1 と同じにせず、**current_page に関わるポリシー部分だけをコピー** する。 Step 7-6: 再ベンチと判定基準 --------------------------- 作業指示: - C7-only (ws=64, iters=20k, PROFILE=C7_SAFE) で: - v2 OFF: 39〜40M ops/s(既存ベースライン) - v2 ON : 目標として **少なくとも v2 OFF と同等 ±5% 以内** を目指す。 - `HEAP_STATS[7] fast≈11015 slow≈1` に戻ることを確認。 - v2 current_page stats では: - `prepare_calls` に対して `prepare_with_current_null` が ≪1% 程度 - Mixed 16–1024B (ws=256, iters=20k) では: - v2 OFF の 38〜39M ops/s に対して **±5% 以内** を許容範囲とする。 - C7-only が十分安定・同等以上であれば、Mixed の調整は後続フェーズで良い。 判定: - 上記基準を満たせば「Phase37: current_page ポリシー修正完了」とし、 次フェーズで C6/C5 への拡張や C7 超ホットレーン(B案)を検討できる。 - 満たせない場合は v2 を引き続き **デフォルト OFF / 研究用箱** としたまま、 current_page / retire ポリシーの再修正に戻る。