Files
hakmem/docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.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

399 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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_slowv1 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 161024B:
- 当面は 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 161024B (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 161024B (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 ポリシーの再修正に戻る。