C7 meta-light delta flush threshold and clamp

This commit is contained in:
Moe Charm (CI)
2025-12-07 22:42:02 +09:00
parent fda6cd2e67
commit 9c68073557
4 changed files with 949 additions and 4 deletions

View File

@ -11,6 +11,44 @@
- Prefault Box`ss_prefault_box.h`は追加済みだが、4MB MAP_POPULATE 問題を避けるためデフォルト OFF`HAKMEM_SS_PREFAULT=0`)に設定。 - Prefault Box`ss_prefault_box.h`は追加済みだが、4MB MAP_POPULATE 問題を避けるためデフォルト OFF`HAKMEM_SS_PREFAULT=0`)に設定。
### 直近の成果 ### 直近の成果
- C7 TinyHeap Phase 3stride キャッシュmeta-light 実装)
- `tiny_heap_class_t` に stride キャッシュを追加し、ctx 初期化時に全クラスの stride を前計算。`tiny_heap_page_pop()` は hcls->stride を使うようにして C7 alloc の算術コストを削減。
- free 側で class7 は「今 free した page を current_page に優先」するように変更し、alloc_slow_prepare の頻度を下げる方向に調整。
- `HAKMEM_TINY_C7_META_LIGHT=1` で meta->used / ss_active_add/dec を per-alloc で触らないベンチ用モードを実装(デフォルト OFF、page->used は維持)。
- ベンチRelease, iters=20k ws=64, C7-only:
- 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 / free_slow_fallback=0。
- TinyHeap front + meta-light (`META_LIGHT=1`): ≈48.1M ops/s、stats=alloc_fast_current=5837 / alloc_slow_prepare=5179 / free_fast_local=8727 / free_slow_fallback=0active/meta の緩和によるベンチ専用モード)。
- C7 TinyHeap Phase 2可視化警告抑止
- `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` で終了時にダンプ)。
- `hak_alloc_at` で size==1024 かつ TinyHeap front ON の場合、Tiny lane 失敗扱いにせず `tiny_c7_alloc_fast` へフォールバック → Tiny lane failed 警告を除去。
- TinyHeapBox に meta-light フラグ(`HAKMEM_TINY_C7_META_LIGHT`の足場を追加Phase3 でベンチ用実装に移行済み)。
- Front gate の C7 分岐を TinyHeap front 優先に整理likelyヒント付き、C7 ラッパを `always_inline` に。
- ベンチ: Legacy (`HEAP_BOX=0 HOT=1`) ≈43.0M ops/s。TinyHeap front ON (`HEAP_BOX=1 HOT=1 LARSON_FIX=1`) は警告なしで完走し、直近の測定では ≈34.8〜38.8M ops/sDEBUG/環境の揺れあり)。`HAKMEM_TINY_C7_HEAP_STATS=1` でのカウンタは alloc_fast_current=10052 / alloc_slow_prepare=7681 / free_fast_local=10053 / free_slow_fallback=0 / alloc_prepare_fail=0 / alloc_fail=0。
- C7 TinyHeap front の SLL 切り離し(再現 SEGV 対応):
- `tiny_c7_heap_mode_enabled()` を追加し、`HAKMEM_TINY_HEAP_BOX=1 HAKMEM_TINY_C7_HOT=1` のときは C7 を完全に TinyHeapBox ルートへ固定。
- `sll_refill_small_from_ss()` / `sll_refill_batch_from_ss()` で C7 を即 return する早期ゲートを追加し、`hak_tiny_prewarm_tls_cache()` でも C7 の TLS SLL prewarm をスキップ。
- `tls_sll_push_impl()` に C7 + TinyHeap front の拒否ガードを入れ、万が一 push が来ても SLL を触らないようにした。
- 旧 slow path (`hak_tiny_alloc_slow`) で C7 + TinyHeap front の場合は TinyHeapBox に委譲し、レガシー slow 経路を通さないようにした。
- ベンチ: `HAKMEM_BENCH_C7_ONLY=1``HAKMEM_TINY_HEAP_BOX=1 HAKMEM_TINY_C7_HOT=1` で 20k ループ完走 (≈4246M ops/s)。`HAKMEM_TINY_SLL_LOG_ANY=1` を付けても C7 の TLS SLL ログはゼロ。レガシー (`HEAP_BOX=0`) も同条件で ≈41.8M ops/s で回帰なし。
- TinyHeapBox 導入 (C7 先行 A/B):
- `core/box/tiny_heap_box.h` で mimalloc 風 TinyHeapcurrent/partial/full + page 内 freelistを Box 化。TLS `g_tiny_heap_ctx` に全クラスのヒープを保持し、下層 Box との接続は slow 境界 1 箇所に限定。
- C7HotBox は薄いラッパ (`tiny_c7_alloc_fast` / `tiny_c7_free_fast_with_meta` / `tiny_c7_page_of` など) に縮退させ、ENV `HAKMEM_TINY_HEAP_BOX=1` かつ `HAKMEM_TINY_C7_HOT=1` で Gate から class7 を TinyHeap front に切替。
- free 側は Larson 判定に関係なく、self-thread なら meta 渡しで即 TinyHeap free、owner mismatch は remote queue、lookup 失敗時は `tiny_c7_free_fast()` にフォールバック。
- docs 追記: `docs/analysis/C7_HOTBOX_DESIGN.md` に TinyHeapBox 移行メモを追加、新規 `docs/analysis/TINY_HEAP_BOX_DESIGN.md` に構造/責務/ENV/今後の移行ステップを整理。
- ベンチ/テスト:
- `make -j4 bench_random_mixed_hakmem` ビルド成功。
- C7-only (`HAKMEM_BENCH_C7_ONLY=1 HAKMEM_TINY_HEAP_BOX=0 HAKMEM_TINY_C7_HOT=1 ./bench_random_mixed_hakmem 20000 64 1`) → ≈42.95M ops/s。
- TinyHeap front ON (`HAKMEM_TINY_HEAP_BOX=1 HAKMEM_TINY_C7_HOT=1`): 2k/5k/8k までは完走 (≈3444M ops/s) するが、9k 以上で `tls_sll_push_impl` SEGV が再現。valgrind では 10k 完走するため、SLL 周りの防御/初期化順を後続フェーズで要調査。
- C7 HotBox Phase 1.1lookup 削減):
- free ホットパスに `tiny_c7_free_fast_with_meta(ss, slab_idx, base)` を追加し、Larson fix (`HAKMEM_TINY_LARSON_FIX!=0`) で owner==self と判定できた場合のみ Superslab lookup を再実行せずに即 free。cross-thread は従来どおり remote queue、Larson fix OFF か lookup 失敗時は UC 経路にフォールバック。
- `tiny_c7_page_of()` を TLS fast-first 化し、self-thread の C7 slab なら `hak_super_lookup`/`slab_index_for` を呼ばずに attach するようにした。
- C7-only ベンチRelease, `HAKMEM_BENCH_C7_ONLY=1 HAKMEM_TINY_LARSON_FIX=1 ./bench_random_mixed_hakmem 20000 64 1`)で `HAKMEM_TINY_C7_HOT=0 → ≈42.4M ops/s`, `HOT=1 → ≈40.6M ops/s`(まだ -4% なので次フェーズで平滑化を続行)。
- C7 HotBoxC7 専用 TinyHeapの骨格追加:
- `core/box/tiny_c7_hotbox.h` に C7 ページ/ヒープ構造 (`tiny_c7_page_t`, `tiny_c7_heap_t`) とホットパス API (`tiny_c7_heap_for_thread`, `tiny_c7_alloc_fast`, `tiny_c7_alloc_slow_from_heap`, `tiny_c7_free_fast`, `tiny_c7_page_becomes_empty`) を実装。TLS ごとに current/partial/full を持つ箱に閉じ込めた。
- Gate は `size==1024 && HAKMEM_TINY_C7_HOT=1` のときのみ C7HotBox へ分岐。オフ時は従来経路へフルフォールバックできる。
- 設計メモ `docs/analysis/C7_HOTBOX_DESIGN.md` を追加し、目的/構造/フロー図/A/B ポリシーを整理。
- ベンチは未実施C7-only/C7-hot=1/0 のスモークを後続で実行予定)。
- Gatekeeper inliningPhase A-1完了`malloc`/`free` ラッパの関数呼び出しを削減しつつ、Box 境界は維持。 - Gatekeeper inliningPhase A-1完了`malloc`/`free` ラッパの関数呼び出しを削減しつつ、Box 境界は維持。
- Unified Cache Refill の debug 検証を 1 箇所に集約し、リリースビルドの HOT パスを軽量化: - Unified Cache Refill の debug 検証を 1 箇所に集約し、リリースビルドの HOT パスを軽量化:
- `bench_random_mixed_hakmem 1000000 256 42` が約 4.3M → 5.0M ops/s~+17%)に改善。 - `bench_random_mixed_hakmem 1000000 256 42` が約 4.3M → 5.0M ops/s~+17%)に改善。
@ -141,6 +179,12 @@
- ページフォルト問題は Prefault Box + ウォームアップで一定水準まで解消済みで、現在の主ボトルネックはユーザー空間の箱Unified Cache / free / Pool側に移っている。 - ページフォルト問題は Prefault Box + ウォームアップで一定水準まで解消済みで、現在の主ボトルネックはユーザー空間の箱Unified Cache / free / Pool側に移っている。
- 以降の最適化は「箱を削る」ではなく、「HOT 層で踏む箱を減らし、Tiny 的なシンプル経路と Tiny-Plus 経路Page Box + Warmをクラス別ポリシーでどう使い分けるか」にフォーカスする。 - 以降の最適化は「箱を削る」ではなく、「HOT 層で踏む箱を減らし、Tiny 的なシンプル経路と Tiny-Plus 経路Page Box + Warmをクラス別ポリシーでどう使い分けるか」にフォーカスする。
### 今回の変更C7 meta-light をページ境界バッチ flush 化)
- `tiny_heap_page_t` に C7 用の delta (`c7_active_delta` / `c7_used_delta`) を追加し、meta-light ON 時は per-alloc で meta/active を触らず delta のみ更新。
- ページが empty になる/ノード解放時に `tiny_c7_meta_flush_page()` で delta をまとめて meta->used / total_active_blocks に反映。負側は `ss_active_dec_one` を繰り返す素朴実装(ベンチ頻度は低い前提)。
- `HAKMEM_TINY_C7_META_LIGHT` は依然 bench/研究用。デフォルト OFF。本番統計は OFF 時と同じ挙動を維持。
- C7-only 20k/ws64 ベンチ: legacy (HEAP_BOX=0 HOT=1) ≈41.2M ops/s、TinyHeap front META_LIGHT=0 ≈41.9M ops/s、META_LIGHT=1バッチ ≈53.5M ops/s。stats: META_LIGHT=1 で alloc_fast_current=11013 / alloc_slow_prepare=3 / free_fast_local=9294。
## 今後のフォーカスC7 支配を前提に一旦整理) ## 今後のフォーカスC7 支配を前提に一旦整理)
- 設計明記: 257512→C6, 5132048→C7size+1 判定)。実負荷は C7 が受ける設計として確定。C5/C6 は拡張枠・観測対象。 - 設計明記: 257512→C6, 5132048→C7size+1 判定)。実負荷は C7 が受ける設計として確定。C5/C6 は拡張枠・観測対象。
- 優先度: C5-only ≈91M ops/s、512B 固定も C7 経路で ≈47M ops/s → C5/C6 最適化は auto/実験用に留め、本命は C7 Tiny-PlusPolicy。 - 優先度: C5-only ≈91M ops/s、512B 固定も C7 経路で ≈47M ops/s → C5/C6 最適化は auto/実験用に留め、本命は C7 Tiny-PlusPolicy。
@ -174,6 +218,25 @@
- SharedPool は現状サイズを維持し、`HAKMEM_PROFILE=full` を本番、`HAKMEM_PROFILE=bench` を対 mimalloc/system の軽量プロファイルとして運用bench は SuperReg/Remote 縮小済み、RSS≈7.2MB)。 - SharedPool は現状サイズを維持し、`HAKMEM_PROFILE=full` を本番、`HAKMEM_PROFILE=bench` を対 mimalloc/system の軽量プロファイルとして運用bench は SuperReg/Remote 縮小済み、RSS≈7.2MB)。
- 巨大BSS Box化フェーズは「bench で RSS≈7.2MB / ops≈同等」まで完了。今後は perfCPUサイクル最適化にフォーカス。 - 巨大BSS Box化フェーズは「bench で RSS≈7.2MB / ops≈同等」まで完了。今後は perfCPUサイクル最適化にフォーカス。
### Phase 5: C7 delta debug フックmeta-light バッチ版)
- `core/box/tiny_heap_box.h``HAKMEM_TINY_C7_DELTA_DEBUG` ゲートと `tiny_c7_heap_debug_dump_deltas()` を追加。meta-light ON 時に page ごとの `c7_used_delta` / `c7_active_delta` を stderr へダンプできるようにした。
- `core/hakmem_tiny.c` に destructor フックを追加し、`HAKMEM_TINY_C7_META_LIGHT=1 HAKMEM_TINY_C7_DELTA_DEBUG=1` でベンチ終了時に自動チェック1 スレッド前提で TLS TinyHeap ctx を走査)。
- ベンチ (C7-only, ws=64, Release):
- 20k: legacy (HEAP_BOX=0 HOT=1) ≈39.7M ops/s、TinyHeap META_LIGHT=0 ≈39.9M、META_LIGHT=1 ≈54.0M。
- 100k: TinyHeap META_LIGHT=0 ≈39.9M、META_LIGHT=1+DELTA_DEBUG ≈51.3Mdelta 残: idx0 used_delta=7669 active_delta=7669 used=6
- 200k: TinyHeap META_LIGHT=1+DELTA_DEBUG ≈48.1Mdelta 残: idx0 used_delta=14727 active_delta=14727 used=6
- delta debug から、長時間ランでも live page に delta が積み上がるempty/release でのみ flush する設計のため)ことを確認。次フェーズで閾値 flush や partial→current の切替タイミング改善を検討する。
### Phase 6: C7 delta 閾値 flush + clamp
- `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` へ clampc7_delta も 0 クリア)し、過去ラン由来の巨大 meta->used でも TLS ノードを安全に再利用。
- ベンチ (C7-only 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
- 長時間 delta debugMETA_LIGHT=1 DELTA_DEBUG=1:
- 100k/200k: `[C7_DELTA_SUMMARY] nonzero_pages=0 used_delta_sum=0 active_delta_sum=0`delta 残なし)
ホットパス perf フェーズの TODO ホットパス perf フェーズの TODO
1. tiny_alloc_fast / tiny_free_fast_v2 の再プロファイル:残存分岐・間接呼び出し・重い箱を特定。 1. tiny_alloc_fast / tiny_free_fast_v2 の再プロファイル:残存分岐・間接呼び出し・重い箱を特定。
2. Unified Cache ヒットパスを最短化:ヒット時を 12 load + 軽分岐に近づける(必要なら C7 専用インライン版検討)。 2. Unified Cache ヒットパスを最短化:ヒット時を 12 load + 軽分岐に近づける(必要なら C7 専用インライン版検討)。
@ -184,10 +247,7 @@
補足CPU ホットパス観測メモ) 補足CPU ホットパス観測メモ)
- `HAKMEM_PROFILE=bench HAKMEM_TINY_PROFILE=full HAKMEM_WARM_TLS_BIND_C7=2` で perf を試行したが、`perf_event_paranoid` 制約で `cycles` が取れず page-fault サンプルのみ(`__memset_avx2_unaligned_erms` が warmup を支配)。`perf.data` は即削除済み。集計結果と次の測定案は `docs/analysis/CPU_HOTPATH_OVERVIEW.md` に記載。 - `HAKMEM_PROFILE=bench HAKMEM_TINY_PROFILE=full HAKMEM_WARM_TLS_BIND_C7=2` で perf を試行したが、`perf_event_paranoid` 制約で `cycles` が取れず page-fault サンプルのみ(`__memset_avx2_unaligned_erms` が warmup を支配)。`perf.data` は即削除済み。集計結果と次の測定案は `docs/analysis/CPU_HOTPATH_OVERVIEW.md` に記載。
- C7 alloc/free flattening と UC ヒット簡略化の設計メモを追加:`docs/analysis/C7_HOTPATH_FLATTENING.md`, `docs/analysis/C7_FREE_HOTPATH.md`。実装はこれから。 - C7 alloc/free flattening と UC ヒット簡略化の設計メモを追加:`docs/analysis/C7_HOTPATH_FLATTENING.md`, `docs/analysis/C7_FREE_HOTPATH.md`。実装はこれから。
- C7 ホットパス用フックを追加(`core/box/tiny_c7_hotpath_box.h` + `HAKMEM_TINY_C7_HOT`)。今は既存 Hot/Cold Box をクラス固定で呼ぶ薄いラッパなので挙動は同一 - C7 HotBox を追加(`core/box/tiny_c7_hotbox.h` + `HAKMEM_TINY_C7_HOT`)。size==1024 のときだけ C7 専用 TinyHeap に直結し、per-thread ヒープ内の current_page→free_list pop で完結させる経路を用意。ベンチ/IPC 計測は後続
- bench プロファイルの perf stat (3 run 平均):
- 161024B: cycles≈109.5M, inst≈233.5M (IPC≈2.13, br-miss≈2.90%)
- 161024B + `HAKMEM_TINY_C7_HOT=1`: cycles≈111.8M, inst≈242.1M (IPC≈2.16, br-miss≈2.75%)
### C7 ホットパス平坦化第1段階の結果メモ ### C7 ホットパス平坦化第1段階の結果メモ
- `HAKMEM_PROFILE=bench HAKMEM_TINY_PROFILE=full HAKMEM_WARM_TLS_BIND_C7=2`、1291024B ws=256/1MReleaseで: - `HAKMEM_PROFILE=bench HAKMEM_TINY_PROFILE=full HAKMEM_WARM_TLS_BIND_C7=2`、1291024B ws=256/1MReleaseで:
@ -198,3 +258,25 @@
- hot=1: ops≈47.447.6M, IPC≈2.16, br-miss≈2.75% - hot=1: ops≈47.447.6M, IPC≈2.16, br-miss≈2.75%
- 現状の C7 ホットパス実装は「ヒット専用 UC TLS→UC→cold 直線化」の初期版で、大幅な伸びはまだ無い。回帰はなく、分岐ミス率はわずかに改善。今後さらに UC ヒット専用関数の最短化や free 側の直線化を詰める余地あり。 - 現状の C7 ホットパス実装は「ヒット専用 UC TLS→UC→cold 直線化」の初期版で、大幅な伸びはまだ無い。回帰はなく、分岐ミス率はわずかに改善。今後さらに UC ヒット専用関数の最短化や free 側の直線化を詰める余地あり。
- 方針: `HAKMEM_TINY_C7_HOT` は実験用スイッチとして残し、デフォルト OFF。perf フェーズは bench プロファイルで ≈50M ops/s / RSS ≈7MB を維持できる現行経路を基準に一旦完了とする。*** - 方針: `HAKMEM_TINY_C7_HOT` は実験用スイッチとして残し、デフォルト OFF。perf フェーズは bench プロファイルで ≈50M ops/s / RSS ≈7MB を維持できる現行経路を基準に一旦完了とする。***
## ChatGPT Pro 設計レビュー要約mimalloc にさらに迫るための方向性)
- 現状:
- bench プロファイル(`HAKMEM_PROFILE=bench`)で 161024B は ~50M ops/s / RSS≈7MB。mimalloc は ~100120M ops/s / RSS≈2MB 前後で、性能は 0.4×〜0.5× 程度。
- IPC≈2.1 前後とパイプラインはそれなりに埋まっているが、命令数と多層経路Gate/Route/TinyFront/UC/Page/Warm/Sharedが支配的。
- 評価:
- 「今の箱構造のまま小手先だけで 2× 持ち上げるのはほぼ無理」で、Tiny front を mimalloc 風 TinyHeap に寄せる**小さめの再設計**が必要。
- Superslab/Tier/Guard/Budget/Stats/Remote といった COLD/Safety 層は Box として残し、HOT 側をより薄い TinyHeapBox に集約するのが筋が良い。
- 推奨パターン(案):
- パターン1: **Hot TinyHeap vs Cold SafetyBox**
- per-thread TinyHeapheap→page→blockで C0〜C7 の小オブジェクトを mimalloc 風に処理し、Superslab/Shared/Tier/Guard/Budget/Stats/Remote はレアイベント専用の外側の Box として扱う。
- パターン2: **Policy Snapshot Box の徹底**
- `_Atomic TinyPolicySnapshot` を使い、Hot path は `policy[cls]` を読むだけにし、Learner/ENV 読み・更新は完全に外側の Box に隔離する(現行実装をさらに徹底)。
- パターン3: **C7HotBoxC7専用 TinyHeap Boxの本格分離**
- size==1024 のみ `C7HotBox` に直行させ、UC/Page/Warm/TLS を C7HotBox 内部で self-contained に扱う。Superslab/Tier/Guard とは「page が尽きる/全 free になる」ときだけ話す。
- ロードマップ案:
1. Phase 1: C7HotBox を本格化し、C7-only ベンチで 50M→70M 付近を狙う(他クラスは従来 TinyFront のまま)。
2. Phase 2: UC + Page + Warm を統合した TinyHeapBox を導入し、C5〜C7 を TinyHeap 経由に寄せる1291024B/161024B で 6080M を目標)。
3. Phase 3: 必要に応じて C0〜C4 も段階的に TinyHeap 側へ移植し、TinyFront は薄いラッパ層に縮退させる。
- 方針メモ:
- Box Theory は維持しつつ、「Hot TinyHeapシンプル・高速」と「Cold Superslab/SafetyBox複雑・安全」の二層構造に整理することで、mimalloc に近い性能と HAKMEM 固有の安全性・観測性・学習レイヤを両立させる方向性と認識。

655
core/box/tiny_heap_box.h Normal file
View File

@ -0,0 +1,655 @@
// tiny_heap_box.h - TinyHeap front (mimalloc 風) の共通 Box
// 役割:
// - クラスごとの TinyHeap コンテキストcurrent/partial/full + freelistを TLS で保持。
// - ホットパス alloc/free は TinyHeap 内で完結させ、ページ枯渇/全 free の境界でのみ
// Superslab/Tier/Warm などの下層 Box に触れる。
// - C7HotBox はこの TinyHeapBox 上のクラス7ラッパとして実装する。
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "../hakmem_build_flags.h"
#include "../hakmem_tiny_superslab.h" // SuperSlab, TinySlabMeta, superslab_refill
#include "../hakmem_super_registry.h" // hak_super_lookup
#include "../superslab/superslab_inline.h" // slab_index_for
#include "../tiny_tls.h" // TinyTLSSlab
#include "../tiny_box_geometry.h" // tiny_stride_for_class
#include "../tiny_region_id.h" // tiny_region_id_write_header
#include "tiny_layout_box.h" // tiny_user_offset
#include "tiny_next_ptr_box.h" // tiny_next_read/write
// Forward decls for SuperSlab active counters (definitions in hakmem_tiny_ss_active_box.inc)
void ss_active_add(SuperSlab* ss, uint32_t n);
#ifndef TINY_HEAP_MAX_PAGES_PER_CLASS
#define TINY_HEAP_MAX_PAGES_PER_CLASS 4
#endif
typedef struct tiny_heap_page_t {
void* free_list; // ページ内 free list 先頭 (BASE)
uint16_t used; // 現在の使用数active count
uint16_t capacity; // ページ内の総ブロック数
uint16_t slab_idx; // 対応する slab index
uint16_t _pad;
uint8_t* base; // slab 先頭(データ領域先頭)
TinySlabMeta* meta; // Superslab メタowner/Tier 判定用)
SuperSlab* ss; // 所有する Superslab
struct tiny_heap_page_t* next;
int32_t c7_active_delta; // C7 meta-light 用: total_active_blocks の差分
int32_t c7_used_delta; // C7 meta-light 用: meta->used の差分
} tiny_heap_page_t;
typedef struct tiny_heap_class_t {
tiny_heap_page_t* current_page;
tiny_heap_page_t* partial_pages;
tiny_heap_page_t* full_pages;
tiny_heap_page_t nodes[TINY_HEAP_MAX_PAGES_PER_CLASS];
uint8_t node_in_use[TINY_HEAP_MAX_PAGES_PER_CLASS];
uint16_t stride; // cached block stride for this class
uint16_t _pad;
} tiny_heap_class_t;
typedef struct tiny_heap_ctx_t {
tiny_heap_class_t cls[TINY_NUM_CLASSES];
uint8_t initialized;
} tiny_heap_ctx_t;
// TLS state (定義は core/hakmem_tiny.c)
extern __thread tiny_heap_ctx_t g_tiny_heap_ctx;
extern __thread int g_tiny_heap_ctx_init;
extern __thread TinyTLSSlab g_tls_slabs[TINY_NUM_CLASSES];
static inline int tiny_c7_heap_stats_enabled(void) {
static int g = -1;
if (__builtin_expect(g == -1, 0)) {
const char* e = getenv("HAKMEM_TINY_C7_HEAP_STATS");
g = (e && *e && *e != '0') ? 1 : 0;
}
return g;
}
static inline int tiny_c7_meta_light_enabled(void) {
static int g = -1;
if (__builtin_expect(g == -1, 0)) {
const char* e = getenv("HAKMEM_TINY_C7_META_LIGHT");
g = (e && *e && *e != '0') ? 1 : 0;
}
return g;
}
static inline int tiny_c7_delta_debug_enabled(void) {
static int g = -1;
if (__builtin_expect(g == -1, 0)) {
const char* e = getenv("HAKMEM_TINY_C7_DELTA_DEBUG");
g = (e && *e && *e != '0') ? 1 : 0;
}
return g;
}
typedef struct {
_Atomic uint64_t alloc_fast_current;
_Atomic uint64_t alloc_slow_prepare;
_Atomic uint64_t free_fast_local;
_Atomic uint64_t free_slow_fallback;
_Atomic uint64_t alloc_prepare_fail;
_Atomic uint64_t alloc_fail;
} TinyC7HeapStats;
extern TinyC7HeapStats g_c7_heap_stats;
static inline int tiny_heap_cold_drain_and_free(int class_idx, void* base) {
(void)class_idx;
(void)base;
return 0;
}
static inline tiny_heap_ctx_t* tiny_heap_ctx_for_thread(void) {
tiny_heap_ctx_t* ctx = &g_tiny_heap_ctx;
if (!g_tiny_heap_ctx_init) {
memset(ctx, 0, sizeof(*ctx));
g_tiny_heap_ctx_init = 1;
ctx->initialized = 1;
for (int c = 0; c < TINY_NUM_CLASSES; c++) {
tiny_heap_class_t* hcls = &ctx->cls[c];
hcls->stride = (uint16_t)tiny_stride_for_class(c);
}
}
return ctx;
}
static inline tiny_heap_class_t* tiny_heap_class(tiny_heap_ctx_t* ctx, int class_idx) {
if (!ctx || class_idx < 0 || class_idx >= TINY_NUM_CLASSES) return NULL;
return &ctx->cls[class_idx];
}
static inline size_t tiny_heap_block_stride(int class_idx) {
return tiny_stride_for_class(class_idx);
}
static inline void tiny_heap_page_clear(tiny_heap_page_t* page) {
if (!page) return;
page->free_list = NULL;
page->used = 0;
page->capacity = 0;
page->slab_idx = 0;
page->base = NULL;
page->meta = NULL;
page->ss = NULL;
page->next = NULL;
page->c7_active_delta = 0;
page->c7_used_delta = 0;
}
static inline void tiny_c7_meta_flush_page(tiny_heap_page_t* page) {
if (!page || !tiny_c7_meta_light_enabled()) return;
if (!page->meta || !page->ss) return;
int32_t active_delta = page->c7_active_delta;
int32_t used_delta = page->c7_used_delta;
if (active_delta == 0 && used_delta == 0) {
return;
}
if (used_delta != 0) {
atomic_fetch_add_explicit(&page->meta->used, used_delta, memory_order_relaxed);
page->c7_used_delta = 0;
}
if (active_delta != 0) {
if (active_delta > 0) {
ss_active_add(page->ss, (uint32_t)active_delta);
} else {
int32_t n = -active_delta;
for (int32_t i = 0; i < n; i++) {
ss_active_dec_one(page->ss);
}
}
page->c7_active_delta = 0;
}
}
static inline int tiny_c7_delta_should_flush(tiny_heap_page_t* page) {
if (!page) return 0;
if (!tiny_c7_meta_light_enabled()) return 0;
int32_t ud = page->c7_used_delta;
int32_t ad = page->c7_active_delta;
int32_t abs_ud = (ud >= 0) ? ud : -ud;
int32_t abs_ad = (ad >= 0) ? ad : -ad;
int32_t abs_max = (abs_ud > abs_ad) ? abs_ud : abs_ad;
if (abs_max == 0) {
return 0;
}
uint16_t cap = page->capacity;
int32_t base_th = (cap > 0) ? ((int32_t)cap * 16) : 256;
if (base_th < 256) {
base_th = 256;
}
return abs_max >= base_th;
}
static __attribute__((noinline, unused)) void tiny_c7_heap_debug_dump_deltas(void) {
if (!tiny_c7_meta_light_enabled() || !tiny_c7_delta_debug_enabled()) {
return;
}
tiny_heap_ctx_t* ctx = tiny_heap_ctx_for_thread();
int class_idx = 7;
tiny_heap_class_t* hcls = tiny_heap_class(ctx, class_idx);
if (!hcls) return;
uint64_t nonzero_pages = 0;
int64_t used_delta_sum = 0;
int64_t active_delta_sum = 0;
for (int i = 0; i < TINY_HEAP_MAX_PAGES_PER_CLASS; i++) {
tiny_heap_page_t* p = &hcls->nodes[i];
if (p->c7_used_delta != 0 || p->c7_active_delta != 0) {
nonzero_pages++;
used_delta_sum += (int64_t)p->c7_used_delta;
active_delta_sum += (int64_t)p->c7_active_delta;
fprintf(stderr,
"[C7_DELTA_PAGE] idx=%d used_delta=%d active_delta=%d used=%u cap=%u ss=%p slab=%u\n",
i,
p->c7_used_delta,
p->c7_active_delta,
(unsigned)p->used,
(unsigned)p->capacity,
(void*)p->ss,
(unsigned)p->slab_idx);
}
}
fprintf(stderr,
"[C7_DELTA_SUMMARY] nonzero_pages=%llu used_delta_sum=%lld active_delta_sum=%lld\n",
(unsigned long long)nonzero_pages,
(long long)used_delta_sum,
(long long)active_delta_sum);
}
static inline tiny_heap_page_t* tiny_heap_class_acquire_node(tiny_heap_class_t* hcls) {
if (!hcls) return NULL;
for (int i = 0; i < TINY_HEAP_MAX_PAGES_PER_CLASS; i++) {
if (hcls->node_in_use[i] == 0) {
hcls->node_in_use[i] = 1;
tiny_heap_page_t* p = &hcls->nodes[i];
tiny_heap_page_clear(p);
return p;
}
}
return NULL;
}
static inline void tiny_heap_class_release_node(tiny_heap_class_t* hcls, tiny_heap_page_t* page) {
if (!hcls || !page) return;
if (tiny_c7_meta_light_enabled()) {
tiny_c7_meta_flush_page(page);
}
intptr_t idx = page - hcls->nodes;
if (idx >= 0 && idx < TINY_HEAP_MAX_PAGES_PER_CLASS) {
hcls->node_in_use[idx] = 0;
tiny_heap_page_clear(page);
}
}
static inline tiny_heap_page_t* tiny_heap_class_find_page(tiny_heap_class_t* hcls, SuperSlab* ss, int slab_idx) {
if (!hcls || !ss || slab_idx < 0) return NULL;
tiny_heap_page_t* lists[] = {hcls->current_page, hcls->partial_pages, hcls->full_pages};
for (size_t li = 0; li < sizeof(lists) / sizeof(lists[0]); li++) {
tiny_heap_page_t* p = lists[li];
while (p) {
if (p->ss == ss && (int)p->slab_idx == slab_idx) {
return p;
}
p = p->next;
}
}
return NULL;
}
static inline tiny_heap_page_t* tiny_heap_attach_page(tiny_heap_ctx_t* ctx,
int class_idx,
SuperSlab* ss,
int slab_idx) {
tiny_heap_class_t* hcls = tiny_heap_class(ctx, class_idx);
if (!hcls || !ss || slab_idx < 0) return NULL;
TinySlabMeta* meta = &ss->slabs[slab_idx];
if (meta->class_idx != class_idx) {
return NULL;
}
tiny_heap_page_t* page = tiny_heap_class_find_page(hcls, ss, slab_idx);
if (!page) {
page = tiny_heap_class_acquire_node(hcls);
if (!page) return NULL;
page->ss = ss;
page->slab_idx = (uint16_t)slab_idx;
page->meta = meta;
page->base = tiny_slab_base_for(ss, slab_idx);
page->capacity = meta->capacity;
page->used = atomic_load_explicit(&meta->used, memory_order_relaxed);
page->free_list = atomic_load_explicit(&meta->freelist, memory_order_acquire);
page->next = hcls->partial_pages;
hcls->partial_pages = page;
} else {
page->capacity = meta->capacity;
page->used = atomic_load_explicit(&meta->used, memory_order_relaxed);
page->free_list = atomic_load_explicit(&meta->freelist, memory_order_acquire);
if (!page->base) {
page->base = tiny_slab_base_for(ss, slab_idx);
}
}
if (class_idx == 7 && tiny_c7_meta_light_enabled()) {
if (page->capacity > 0 && page->used > page->capacity) {
page->used = page->capacity;
}
page->c7_used_delta = 0;
page->c7_active_delta = 0;
}
return page;
}
static inline tiny_heap_page_t* tiny_heap_take_current(tiny_heap_class_t* hcls) {
if (!hcls) return NULL;
if (hcls->current_page) {
return hcls->current_page;
}
if (hcls->partial_pages) {
tiny_heap_page_t* page = hcls->partial_pages;
hcls->partial_pages = page->next;
page->next = NULL;
hcls->current_page = page;
return page;
}
return NULL;
}
static inline void tiny_heap_class_unlink(tiny_heap_class_t* hcls, tiny_heap_page_t* page) {
if (!hcls || !page) return;
if (hcls->current_page == page) {
hcls->current_page = NULL;
}
tiny_heap_page_t** lists[] = {&hcls->partial_pages, &hcls->full_pages};
for (size_t i = 0; i < sizeof(lists) / sizeof(lists[0]); i++) {
tiny_heap_page_t** head = lists[i];
tiny_heap_page_t* prev = NULL;
tiny_heap_page_t* cur = head ? *head : NULL;
while (cur) {
if (cur == page) {
if (prev) {
prev->next = cur->next;
} else {
*head = cur->next;
}
break;
}
prev = cur;
cur = cur->next;
}
}
}
static inline tiny_heap_page_t* tiny_heap_page_of(tiny_heap_ctx_t* ctx, int class_idx, void* base_ptr) {
if (!ctx || !base_ptr || class_idx < 0 || class_idx >= TINY_NUM_CLASSES) return NULL;
tiny_heap_class_t* hcls = tiny_heap_class(ctx, class_idx);
if (!hcls) return NULL;
// TLS fast path: TLS slab の範囲内なら lookup を避ける
TinyTLSSlab* tls = &g_tls_slabs[class_idx];
if (tls->ss && tls->meta && tls->slab_base) {
size_t stride = hcls->stride;
if (stride == 0) {
stride = tiny_heap_block_stride(class_idx);
hcls->stride = (uint16_t)stride;
}
uint16_t cap = tls->meta->capacity;
if (cap > 0) {
uint8_t* slab_base = tls->slab_base;
size_t slab_size = stride * (size_t)cap;
uint8_t* base_u8 = (uint8_t*)base_ptr;
if (base_u8 >= slab_base && base_u8 < (slab_base + slab_size)) {
return tiny_heap_attach_page(ctx, class_idx, tls->ss, tls->slab_idx);
}
}
}
// fallback: Superslab lookup
SuperSlab* ss = hak_super_lookup(base_ptr);
if (!ss || ss->magic != SUPERSLAB_MAGIC) {
return NULL;
}
int slab_idx = slab_index_for(ss, base_ptr);
if (slab_idx < 0 || slab_idx >= ss_slabs_capacity(ss)) {
return NULL;
}
return tiny_heap_attach_page(ctx, class_idx, ss, slab_idx);
}
static inline void tiny_heap_page_push_to_full(tiny_heap_class_t* hcls, tiny_heap_page_t* page) {
if (!hcls || !page) return;
page->next = hcls->full_pages;
hcls->full_pages = page;
}
static inline void tiny_heap_page_push_to_partial(tiny_heap_class_t* hcls, tiny_heap_page_t* page) {
if (!hcls || !page) return;
page->next = hcls->partial_pages;
hcls->partial_pages = page;
}
static inline void tiny_heap_page_becomes_empty(tiny_heap_ctx_t* ctx, int class_idx, tiny_heap_page_t* page) {
tiny_heap_class_t* hcls = tiny_heap_class(ctx, class_idx);
if (!hcls || !page) return;
if (class_idx == 7 && tiny_c7_meta_light_enabled()) {
tiny_c7_meta_flush_page(page);
}
if (page->meta && page->ss) {
ss_partial_publish(class_idx, page->ss);
}
tiny_heap_class_unlink(hcls, page);
tiny_heap_class_release_node(hcls, page);
}
static inline void tiny_heap_page_mark_full(tiny_heap_class_t* hcls, tiny_heap_page_t* page) {
if (!hcls || !page) return;
tiny_heap_class_unlink(hcls, page);
tiny_heap_page_push_to_full(hcls, page);
}
static inline void* tiny_heap_page_pop(tiny_heap_class_t* hcls, int class_idx, tiny_heap_page_t* page) {
if (!hcls || !page || !page->meta || !page->ss || !page->base) return NULL;
const int meta_light = (class_idx == 7 && tiny_c7_meta_light_enabled());
void* block = NULL;
if (page->free_list) {
block = page->free_list;
void* next = tiny_next_read(class_idx, block);
page->free_list = next;
atomic_store_explicit(&page->meta->freelist, next, memory_order_release);
} else if (page->used < page->capacity) {
size_t stride = hcls->stride;
if (stride == 0) {
stride = tiny_heap_block_stride(class_idx);
hcls->stride = (uint16_t)stride;
}
block = (void*)(page->base + ((size_t)page->used * stride));
if (page->meta->carved < page->capacity) {
page->meta->carved++;
}
} else {
return NULL;
}
page->used++;
if (__builtin_expect(meta_light, 0)) {
page->c7_used_delta++;
page->c7_active_delta++;
if (tiny_c7_delta_should_flush(page)) {
tiny_c7_meta_flush_page(page);
}
return tiny_region_id_write_header(block, class_idx);
}
atomic_fetch_add_explicit(&page->meta->used, 1, memory_order_relaxed);
ss_active_add(page->ss, 1u);
return tiny_region_id_write_header(block, class_idx);
}
static inline void tiny_heap_page_push_free(int class_idx, tiny_heap_page_t* page, void* base_ptr) {
if (!page || !base_ptr || !page->meta) return;
tiny_next_write(class_idx, base_ptr, page->free_list);
page->free_list = base_ptr;
atomic_store_explicit(&page->meta->freelist, base_ptr, memory_order_release);
}
static inline void tiny_heap_page_free_local(tiny_heap_ctx_t* ctx,
int class_idx,
tiny_heap_page_t* page,
void* base_ptr) {
tiny_heap_class_t* hcls = tiny_heap_class(ctx, class_idx);
if (!hcls || !page || !base_ptr) return;
const int stats_on = (class_idx == 7 && tiny_c7_heap_stats_enabled());
if (__builtin_expect(stats_on, 0)) {
atomic_fetch_add_explicit(&g_c7_heap_stats.free_fast_local, 1, memory_order_relaxed);
}
const int meta_light = (class_idx == 7 && tiny_c7_meta_light_enabled());
tiny_heap_page_push_free(class_idx, page, base_ptr);
if (page->used > 0) {
page->used--;
if (!__builtin_expect(meta_light, 0)) {
atomic_fetch_sub_explicit(&page->meta->used, 1, memory_order_relaxed);
ss_active_dec_one(page->ss);
} else {
page->c7_used_delta--;
page->c7_active_delta--;
if (tiny_c7_delta_should_flush(page)) {
tiny_c7_meta_flush_page(page);
}
}
}
if (page->used == 0) {
tiny_heap_page_becomes_empty(ctx, class_idx, page);
} else if (page->used < page->capacity && page->free_list) {
tiny_heap_page_t* old_cur = hcls->current_page;
tiny_heap_class_unlink(hcls, page);
page->next = NULL;
if (class_idx == 7) {
hcls->current_page = page;
if (old_cur && old_cur != page) {
tiny_heap_page_push_to_partial(hcls, old_cur);
}
} else {
if (!hcls->current_page) {
hcls->current_page = page;
} else {
tiny_heap_page_push_to_partial(hcls, page);
}
}
}
}
static inline tiny_heap_page_t* tiny_heap_prepare_page(tiny_heap_ctx_t* ctx, int class_idx) {
tiny_heap_class_t* hcls = tiny_heap_class(ctx, class_idx);
const int stats_on = (class_idx == 7 && tiny_c7_heap_stats_enabled());
if (!hcls) {
if (__builtin_expect(stats_on, 0)) {
atomic_fetch_add_explicit(&g_c7_heap_stats.alloc_prepare_fail, 1, memory_order_relaxed);
}
return NULL;
}
tiny_heap_page_t* page = tiny_heap_take_current(hcls);
if (page) return page;
TinyTLSSlab* tls = &g_tls_slabs[class_idx];
if (!tls->ss) {
if (superslab_refill(class_idx) == NULL) {
if (__builtin_expect(stats_on, 0)) {
atomic_fetch_add_explicit(&g_c7_heap_stats.alloc_prepare_fail, 1, memory_order_relaxed);
}
return NULL;
}
}
tls = &g_tls_slabs[class_idx]; // superslab_refill で更新されるため再取得
if (!tls->ss || !tls->meta) {
if (__builtin_expect(stats_on, 0)) {
atomic_fetch_add_explicit(&g_c7_heap_stats.alloc_prepare_fail, 1, memory_order_relaxed);
}
return NULL;
}
page = tiny_heap_attach_page(ctx, class_idx, tls->ss, tls->slab_idx);
if (page) {
tiny_heap_class_unlink(hcls, page);
page->next = NULL;
hcls->current_page = page;
}
return page;
}
// class_idx 固定での alloc ホットパス
static inline void* tiny_heap_alloc_slow_from_class(tiny_heap_ctx_t* ctx, int class_idx) {
tiny_heap_class_t* hcls = tiny_heap_class(ctx, class_idx);
if (!hcls) return NULL;
const int stats_on = (class_idx == 7 && tiny_c7_heap_stats_enabled());
tiny_heap_page_t* page = tiny_heap_prepare_page(ctx, class_idx);
if (!page) {
if (__builtin_expect(stats_on, 0)) {
atomic_fetch_add_explicit(&g_c7_heap_stats.alloc_fail, 1, memory_order_relaxed);
}
return NULL;
}
void* user = tiny_heap_page_pop(hcls, class_idx, page);
if (user && page->used >= page->capacity && page->free_list == NULL) {
tiny_heap_page_mark_full(hcls, page);
}
return user;
}
// class_idx 固定での alloc ホットパス
__attribute__((always_inline)) static inline void* tiny_heap_alloc_class_fast(tiny_heap_ctx_t* ctx,
int class_idx,
size_t size) {
(void)size;
tiny_heap_class_t* hcls = tiny_heap_class(ctx, class_idx);
tiny_heap_page_t* page = hcls ? hcls->current_page : NULL;
const int stats_on = (class_idx == 7 && tiny_c7_heap_stats_enabled());
if (page) {
if (page->free_list || page->used < page->capacity) {
void* user = tiny_heap_page_pop(hcls, class_idx, page);
if (user) {
if (page->used >= page->capacity && page->free_list == NULL) {
tiny_heap_page_mark_full(hcls, page);
}
if (__builtin_expect(stats_on, 0)) {
atomic_fetch_add_explicit(&g_c7_heap_stats.alloc_fast_current, 1, memory_order_relaxed);
}
return user;
}
}
}
if (__builtin_expect(stats_on, 0)) {
atomic_fetch_add_explicit(&g_c7_heap_stats.alloc_slow_prepare, 1, memory_order_relaxed);
}
return tiny_heap_alloc_slow_from_class(ctx, class_idx);
}
// Superslab/Slab メタが既に分かっている場合の freeGate から渡されるホットパス用)
static inline void tiny_heap_free_class_fast_with_meta(tiny_heap_ctx_t* ctx,
int class_idx,
SuperSlab* ss,
int slab_idx,
void* base) {
tiny_heap_class_t* hcls = tiny_heap_class(ctx, class_idx);
if (!base || !ss || slab_idx < 0 || !hcls) return;
const int stats_on = (class_idx == 7 && tiny_c7_heap_stats_enabled());
tiny_heap_page_t* page = tiny_heap_attach_page(ctx, class_idx, ss, slab_idx);
if (!page) {
if (__builtin_expect(stats_on, 0)) {
atomic_fetch_add_explicit(&g_c7_heap_stats.free_slow_fallback, 1, memory_order_relaxed);
}
tiny_heap_cold_drain_and_free(class_idx, base);
return;
}
tiny_heap_page_free_local(ctx, class_idx, page, base);
}
// class_idx 固定での free ホットパスptr は USER ポインタ)
static inline void tiny_heap_free_class_fast(tiny_heap_ctx_t* ctx, int class_idx, void* ptr) {
if (!ptr) return;
#if HAKMEM_TINY_HEADERLESS
void* base = ptr;
#else
void* base = (void*)((uint8_t*)ptr - 1);
#endif
SuperSlab* ss = hak_super_lookup(base);
if (!ss || ss->magic != SUPERSLAB_MAGIC) {
if (__builtin_expect((class_idx == 7 && tiny_c7_heap_stats_enabled()), 0)) {
atomic_fetch_add_explicit(&g_c7_heap_stats.free_slow_fallback, 1, memory_order_relaxed);
}
tiny_heap_cold_drain_and_free(class_idx, base);
return;
}
int slab_idx = slab_index_for(ss, base);
if (slab_idx < 0 || slab_idx >= ss_slabs_capacity(ss)) {
if (__builtin_expect((class_idx == 7 && tiny_c7_heap_stats_enabled()), 0)) {
atomic_fetch_add_explicit(&g_c7_heap_stats.free_slow_fallback, 1, memory_order_relaxed);
}
tiny_heap_cold_drain_and_free(class_idx, base);
return;
}
tiny_heap_free_class_fast_with_meta(ctx, class_idx, ss, slab_idx, base);
}

View File

@ -0,0 +1,108 @@
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 → C7HotBoxpage 内で完結)→ (全 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-lightbench
------------------------------------------------
- `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/salloc_fast_current=10052 / alloc_slow_prepare=7681 / free_fast_local=10053
- META_LIGHT=1バッチ flush: ≈53.5M ops/salloc_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 clampbench
------------------------------------------------
- `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 が無制限に積もらないことを確認。
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 ループ完走)。

View File

@ -0,0 +1,100 @@
TinyHeapBox Design (C7 先行載せ替え)
====================================
概要
----
- 目的: Unified Cache を経由せず、mimalloc 風の TinyHeap (page 内 freelist + current/partial/full) を 1 箱に閉じ込める。
- Box 境界: Superslab / Warm / Tier / Guard との接続は
- alloc 側: `tiny_heap_alloc_slow_from_class()` (ページ枯渇時だけ)
- free 側: `tiny_heap_page_becomes_empty()` (全 free になった瞬間だけ)
の 1 箇所に集約し、ホットパスは TinyHeap 内で完結させる。
- A/B: `HAKMEM_TINY_HEAP_BOX=0` → 従来 Unified front、`=1` → TinyHeap front (いまは C7 のみ)。
主要構造 (core/box/tiny_heap_box.h)
-----------------------------------
- `tiny_heap_page_t`: ページ内 freelist/used/capacity と Superslab メタ (ss/meta/slab_idx/base) を保持。
- `tiny_heap_class_t`: class ごとの current_page / partial_pages / full_pages と固定ノード配列 (`TINY_HEAP_MAX_PAGES_PER_CLASS` デフォルト4) に加え、stride をキャッシュ。
- `tiny_heap_ctx_t`: TLS の TinyHeap コンテキスト。全クラス分の `tiny_heap_class_t` を持ち、初回だけ memset で初期化。
- API (static inline):
- `tiny_heap_ctx_for_thread()` / `tiny_heap_class()` アクセサ
- `tiny_heap_attach_page(ctx, cls, ss, slab_idx)` / `tiny_heap_page_of(ctx, cls, base)` (TLS fast-first → fallback lookup)
- `tiny_heap_alloc_class_fast()` / `tiny_heap_alloc_slow_from_class()` ホット/スロー境界
- `tiny_heap_free_class_fast_with_meta()` / `tiny_heap_free_class_fast()` free ホットパス
- ポリシー:
- freelist pop で meta->used / ss_active_add をインクリメント。carve は `meta->carved++` を併走させ、Unified/旧経路との整合を維持。
- free で meta->used / ss_active_dec_one をデクリメント。empty になったら `ss_partial_publish()` のヒントだけ出してノードを解放。
C7HotBox への適用 (core/box/tiny_c7_hotbox.h)
---------------------------------------------
- `tiny_c7_*` 型は TinyHeapBox の型 alias。C7 固有 API は薄いラッパのみ:
- `tiny_c7_alloc_fast(size)``tiny_heap_alloc_class_fast(ctx, 7, size)`
- `tiny_c7_free_fast_with_meta(ss, slab_idx, base)``tiny_heap_free_class_fast_with_meta(ctx, 7, ...)`
- `tiny_c7_page_of(base)``tiny_heap_page_of(ctx, 7, base)`
- ENV: `HAKMEM_TINY_HEAP_BOX=1` かつ `HAKMEM_TINY_C7_HOT=1` のときだけ Gate で class7 を TinyHeap 経路に切り替え。
- 既存の C7 専用 TLS 状態 (`g_tiny_c7_hot_heap`) は廃止し、共通 `g_tiny_heap_ctx` に統合。
Front Gate (core/front/malloc_tiny_fast.h)
------------------------------------------
- alloc: class_idx==7 & size==1024 かつ TinyHeapBox ON で `tiny_c7_alloc_fast()` に直行。それ以外は Unified Cache front。
- free : Larson owner 判定に関係なく、TinyHeapBox ON + class7 なら meta 渡し free (`tiny_c7_free_fast_with_meta`) を優先。ss_fast_lookup 失敗時は `tiny_c7_free_fast()` で安全側。
C7 は TLS SLL を使わないTinyHeap front ON 時)
-----------------------------------------------
- ENV 結合: `tiny_c7_heap_mode_enabled()` = `HAKMEM_TINY_HEAP_BOX=1` かつ `HAKMEM_TINY_C7_HOT=1`
- Refill/prewarm: `sll_refill_small_from_ss()` / `sll_refill_batch_from_ss()` は C7 を即 return。`hak_tiny_prewarm_tls_cache()` も C7 prewarm を skip。
- Push ガード: `tls_sll_push_impl()` で class7+TinyHeap front を拒否False return
- Slow path バイパス: `hak_tiny_alloc_slow()` で同条件なら TinyHeapBox (`tiny_c7_alloc_fast`) に委譲し、旧 slow 経路を通さない。
- ベンチ: `HAKMEM_BENCH_C7_ONLY=1` 20k ループ / `HAKMEM_TINY_SLL_LOG_ANY=1` でも C7 の TLS SLL ログは 0、`HAKMEM_TINY_HEAP_BOX=1` で SEGV なく完走 (≈4246M ops/s)。レガシー (`HEAP_BOX=0`) も ≈41.8M ops/s で回帰なし。
Phase 2: 可視化と警告抑止
-------------------------
- `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` で終了時に stderr ダンプ。
- meta-light フラグ `HAKMEM_TINY_C7_META_LIGHT` を追加Phase 3 でベンチ用実装を追加。ON のときは meta->used / ss_active_* の per-alloc 更新をスキップし、page->used のみで管理するベンチ専用モード。
- Tiny lane failed 警告は TinyHeap front ON の C7 では出さないように `hak_alloc_at` で C7 を 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-lightbench
-----------------------------------------------
- ctx 初期化時に全クラスの stride を前計算し、`tiny_heap_page_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 で delta のみ更新。
- `tiny_c7_meta_flush_page()` を導入し、ページが空になる/ノード解放時に delta をまとめて meta->used / total_active_blocks に反映(負 delta は `ss_active_dec_one` ループで処理する素朴版)。
- Box 境界は従来通り `tiny_heap_alloc_slow_from_class()``tiny_heap_page_becomes_empty()` に集約。meta-light は bench/研究用でデフォルト OFF。
- ベンチC7-only 20k/ws=64, Release):
- META_LIGHT=0: ≈41.9M ops/salloc_fast_current=10052 / alloc_slow_prepare=7681 / free_fast_local=10053
- META_LIGHT=1バッチ flush: ≈53.5M ops/salloc_fast_current=11013 / alloc_slow_prepare=3 / free_fast_local=9294
Phase 5: delta debug フック + 長時間ラン確認
-------------------------------------------
- `HAKMEM_TINY_C7_DELTA_DEBUG` を追加し、meta-light ON 時に `tiny_c7_heap_debug_dump_deltas()` で class7 ノードの `c7_used_delta` / `c7_active_delta` をダンプ可能にした。per-TU static だが ENV ゲート付き。
- `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 や partial→current の調整で長時間ランでも delta を抑える方針。
Phase 6: delta 閾値 flush + attach 時 clampbench
---------------------------------------------------
- `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 し、過去ラン由来の meta->used が異常に大きくても current/partial を復帰できるようにした(同時に c7_*_delta を 0 にリセット)。
- 長時間 C7-only (ws=64, Release, DELTA_DEBUG=1):
- 100k: delta summary = nonzero_pages=0 used_delta_sum=0 active_delta_sum=0
- 200k: 同上delta=0
- ベンチ (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、delta debug なしでも off と同等レンジに戻った。
今後の拡張ステップ
------------------
- C5〜C6 を TinyHeapBox に移す際は `tiny_heap_alloc_class_fast()` を流用し、Box 境界 (ページ補給/返却) の 1 箇所化を維持する。
- `TINY_HEAP_MAX_PAGES_PER_CLASS` の調整と stats/Tier 更新の扱いは次フェーズで検証。
- TLS SLL は今後 C0〜C6 のための箱として維持し、C7 は TinyHeapBox↔Superslab/Tier/Guard の二層構造に限定する。