C7 HotBox Design Memo ===================== 目的(C7-only、mimalloc 風 TinyHeap、1 本の直線パス) ------------------------------------------------------- - C7 (≈1KiB) 専用の最短経路を用意し、Gate から 1 本の直線で「取れるなら即返す」を実現する。 - mimalloc 風の「1 ページ=1 個の free list」を C7HotBox 内に閉じ込め、Superslab/Warm との境界は slow 1 箇所に集約する。 - Box 化により、HAKMEM_TINY_C7_HOT=0 で完全に元の経路へ戻せる(Fail-Fast + Revertable)。 新しく導入する struct / 関数名の候補 ------------------------------------ - `tiny_c7_page_t` : C7 ページのローカルメタ - `void* free_list;` // ページ内 free list 先頭(BASE) - `uint16_t used;` // 現在使用数 - `uint16_t capacity;` // ページ内の最大ブロック数 - `TinySlabMeta* meta;` / `SuperSlab* ss;` // 下層判定に必要なら保持 - `tiny_c7_heap_t` : スレッド専用の C7 ヒープ箱 - `tiny_c7_page_t* current_page;` - `tiny_c7_page_t* partial_pages;` // まだ空きがあるページのリング/リスト - `tiny_c7_page_t* full_pages;` // full になったページ - API(全て static inline で HotBox に閉じ込める) - `tiny_c7_heap_t* tiny_c7_heap_for_thread(void);` // TLS からヒープを取得 - `void* tiny_c7_alloc_fast(size_t size);` // 1024 確定サイズ前提のホットパス - `void* tiny_c7_alloc_slow_from_heap(tiny_c7_heap_t*);` // Superslab/Warm 境界はここ 1 箇所 - `void tiny_c7_free_fast(void* p);` // C7 free ホットパス - `tiny_c7_page_t* tiny_c7_page_of(void* p);` // ポインタ→ページの helper(クラス判定は既存メタを利用) - `void tiny_c7_page_becomes_empty(tiny_c7_page_t*);` // 全 free 判定後にのみ下層へ返す フロー図(境界は 1 箇所) ------------------------ - alloc: `Gate → (size==1024 && HOT=1) → C7HotBox → (足りなければ) Superslab/Warm 経路` - free : `Gate → C7HotBox(page 内で完結)→ (全 free なら) Superslab/Tier/Guard に返す` A/B 切替ポリシー(HAKMEM_TINY_C7_HOT) -------------------------------------- - `HAKMEM_TINY_C7_HOT=1` : C7HotBox を有効化。Gate で `class_idx==7` を検出したときだけ `tiny_c7_alloc_fast` / `tiny_c7_free_fast` を経由する。 - `HAKMEM_TINY_C7_HOT=0` : 完全に従来経路へフォールバック(Unified Cache / Warm / Superslab の既存ルート)。 - ENV で即時戻せるようにし、Box 境界は slow helper(`tiny_c7_alloc_slow_from_heap` / `tiny_c7_page_becomes_empty`)1 箇所に集約する。 メモ ---- - Remote Queue / Ownership / Publish/Adopt は触らず、C7HotBox は「C7 専用 TinyHeap」だけを責務とする。 - 可視化はワンショットまたは軽いカウンタのみ(常時ログは禁止)。 Phase 1.1: lookup 削減メモ -------------------------- - free ホットパスから二重 lookup を除去: - `tiny_c7_free_fast_with_meta(ss, slab_idx, base)` を追加し、Gate が持っている Superslab/スラブ index をそのまま渡す経路を用意。 - Larson fix (`HAKMEM_TINY_LARSON_FIX!=0`) で owner==self を確認できた場合のみ、この meta 渡し free を使う。cross-thread は従来通り remote queue へ。 - fallback 用の `tiny_c7_free_fast()` は安全側として残し、lookup が必要な場合だけ slow 経路へ倒す。 - `tiny_c7_page_of()` を TLS fast-first 化: - TLS C7 slab の範囲内であれば `hak_super_lookup`/`slab_index_for` を呼ばずに即 attach。 - 範囲外のみ従来の Superslab lookup にフォールバック。 Phase 2: 可視化と Tiny lane 整合 --------------------------------- - `HAKMEM_TINY_C7_HEAP_STATS` を追加し、C7 TinyHeap 内のステップ別カウンタ(alloc_fast_current / alloc_slow_prepare / free_fast_local / free_slow_fallback / alloc_prepare_fail / alloc_fail)を計測できるようにした。`HAKMEM_TINY_C7_HEAP_STATS_DUMP=1` で終了時にダンプ。 - meta 軽量化の足場として `HAKMEM_TINY_C7_META_LIGHT` を追加(Phase 3 でベンチ用実装を追加)。 - Gate (`hak_alloc_at`) で size==1024 かつ TinyHeap front ON の場合は Tiny lane 失敗扱いにせず、最後まで TinyHeapBox 経路として扱うようにして警告を抑止。 - 参考値(C7-only 20k, stats ON): alloc_fast_current=10052 / alloc_slow_prepare=7681 / free_fast_local=10053 / free_slow_fallback=0 / alloc_prepare_fail=0 / alloc_fail=0。 Phase 3: stride キャッシュ + meta-light(bench) ------------------------------------------------ - `tiny_heap_class_t` に stride キャッシュを持たせ、ctx 初期化時に全クラスの stride を前計算。alloc/pop は hcls->stride を直接参照。 - free 側で class7 は「free した page を current_page に優先」するように変更し、alloc_slow_prepare を減らす方向へ調整。 - `HAKMEM_TINY_C7_META_LIGHT=1` で meta->used / ss_active_* の per-alloc 更新をスキップする実験モードを実装(デフォルト OFF、Superslab/Tier stats は本番向けでは緩むため bench 専用)。 - ベンチ(C7-only 20k/ws=64, Release): - legacy (HEAP_BOX=0 HOT=1): ≈42.5M ops/s - TinyHeap front (HEAP_BOX=1 HOT=1 LARSON_FIX=1, META_LIGHT=0): ≈43.2M ops/s、stats=alloc_fast_current=10052 / alloc_slow_prepare=7681 / free_fast_local=10053 - TinyHeap front + meta-light (META_LIGHT=1): ≈48.1M ops/s、stats=alloc_fast_current=5837 / alloc_slow_prepare=5179 / free_fast_local=8727 Phase 4: meta-light をページ境界バッチ flush 化(bench 用) ------------------------------------------------ - `tiny_heap_page_t` に C7 delta (`c7_used_delta` / `c7_active_delta`) を持たせ、meta-light ON では per-alloc で meta/active を触らず delta のみ更新。 - ページが empty になる/ノード解放時に `tiny_c7_meta_flush_page()` で delta をまとめて meta->used / total_active_blocks に反映(負 delta は `ss_active_dec_one` ループで処理する素朴版)。 - 依然として bench/研究用フラグでデフォルト OFF(本番 stats は OFF 時の挙動を維持)。 - ベンチ(C7-only 20k/ws=64, Release): - META_LIGHT=0: ≈41.9M ops/s(alloc_fast_current=10052 / alloc_slow_prepare=7681 / free_fast_local=10053) - META_LIGHT=1(バッチ flush): ≈53.5M ops/s(alloc_fast_current=11013 / alloc_slow_prepare=3 / free_fast_local=9294) Phase 5: delta debug フック(meta-light 検証用) --------------------------------------------- - `HAKMEM_TINY_C7_DELTA_DEBUG` を追加し、meta-light ON で `tiny_c7_heap_debug_dump_deltas()`(core/box/tiny_heap_box.h)から class7 ノードの delta を stderr に出力できるようにした。 - `core/hakmem_tiny.c` の destructor でも同 helper を呼び、`HAKMEM_TINY_C7_META_LIGHT=1 HAKMEM_TINY_C7_DELTA_DEBUG=1` のときベンチ終了時に自動ダンプ。 - 長時間 C7-only ベンチ例 (ws=64, Release): - 100k: META_LIGHT=1+DELTA_DEBUG ≈51.3M ops/s、delta 残 = idx0 used_delta=7669 active_delta=7669 used=6。 - 200k: META_LIGHT=1+DELTA_DEBUG ≈48.1M ops/s、delta 残 = idx0 used_delta=14727 active_delta=14727 used=6。 - delta が live page に残り続ける(empty/release でのみ flush する設計のため)ことが見えた。今後は閾値 flush や current/partial の入替で長時間ランでも delta を抑える改善を検討。 Phase 6: delta 閾値 flush + attach clamp(bench) ------------------------------------------------ - `tiny_c7_delta_should_flush()` を追加し、C7 meta-light ON かつ `|delta| >= max(256, capacity*16)` のときホットパスから `tiny_c7_meta_flush_page()` を呼び出すようにした。per-alloc atomic を避けつつ delta を capacity 数倍にバウンド。 - `tiny_heap_attach_page()` で C7 meta-light 有効時は `used` を `capacity` へ clamp + c7_delta を 0 クリアし、過去ランの meta->used が膨らんでいても TLS ノードを安全に再利用できるようにした。 - ベンチ (C7-only 20k/ws=64, Release): - Legacy HEAP_BOX=0 HOT=1: ≈42.5M ops/s - TinyHeap HEAP_BOX=1 HOT=1 LARSON_FIX=1 META_LIGHT=0: ≈43.1M ops/s - TinyHeap META_LIGHT=1 (閾値 flush/clamp): ≈42.6M ops/s - 長時間 C7-only (ws=64, DELTA_DEBUG=1): - 100k: `[C7_DELTA_SUMMARY] nonzero_pages=0 used_delta_sum=0 active_delta_sum=0` - 200k: 同上 (delta 0) → delta が無制限に積もらないことを確認。 Phase 7: クラス選択式 TinyHeap(C6/C5 拡張のためのゲート) ------------------------------------------------------ - ENV `HAKMEM_TINY_HEAP_CLASSES` を追加(bitmask、デフォルト 0x80=C7 のみ)。`tiny_heap_class_route_enabled(cls)` で TinyHeap front を使うクラスを判定し、C6/C5 も段階的に TinyHeap へ載せ替え可能にした。 - Gate: `malloc_tiny_fast` / `free_tiny_fast` がクラスごとに TinyHeap 経路を選択。C7 は `tiny_c7_heap_mode_enabled()`(`HAKMEM_TINY_C7_HOT` 連動)を維持しつつ、他クラスは `tiny_heap_alloc/free_class_fast()` を使う薄ラッパで扱う。 - TLS SLL 側もクラス単位で分離し、`sll_refill_small_from_ss` / `sll_refill_batch_from_ss` / `hak_tiny_prewarm_tls_cache` が TinyHeap クラスを早期 return/skip。C7 は「TinyHeapBox ↔ Superslab/Tier/Guard」だけを踏む二層構造のまま。 TinyHeapBox への載せ替え(Phase 1.0 構造) ------------------------------------------ - C7HotBox の実体を `core/box/tiny_heap_box.h` の汎用 TinyHeapBox 上に配置し、型は `tiny_heap_ctx_t` / `tiny_heap_page_t` へ統一。 - ENV `HAKMEM_TINY_HEAP_BOX=1` かつ `HAKMEM_TINY_C7_HOT=1` のとき、Gate から class7 の alloc/free を TinyHeap front 経由に切り替える。 - TinyHeapBox は class ごとに current/partial/full と固定ノードを TLS に保持し、下層 Box (Warm/Superslab/Tier/Guard) との接続は `tiny_heap_alloc_slow_from_class()` / `tiny_heap_page_becomes_empty()` の 1 箇所に集約。 - C7 固有 API は薄いラッパのみ (`tiny_c7_alloc_fast` / `tiny_c7_free_fast_with_meta` など) とし、今後 C5〜C6 も同じ基盤に載せ替えられる構造にした。 - C7 + TinyHeap front では TLS SLL 経路を完全に無効化し、refill/prewarm/push/slow path すべてを TinyHeapBox↔Superslab/Tier/Guard の二層に固定した(`HAKMEM_TINY_SLL_LOG_ANY=1` でも C7 push ログ 0、20k ループ完走)。