C7 v2: add lease helpers and v2 page reset
This commit is contained in:
206
CURRENT_TASK.md
206
CURRENT_TASK.md
@ -1,5 +1,116 @@
|
||||
## HAKMEM 状況メモ (2025-12-05 更新 / C7 Warm/TLS Bind 反映)
|
||||
|
||||
### Phase 32: C7 HotHeap v2 で current_page を自前管理(ページ供給だけ 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 を巻き取り、pop/push は v1 の `tiny_heap_page_pop/free_local` を直接叩く形に変更(meta/ss_active の整合は v1 に委譲)。
|
||||
- Free も current_page(lease_page)が一致する場合は v2 側で処理し、範囲外/不一致のみ従来 C7 free へフォールバック。Superslab/Remote/Stats は依然 v1 に任せる(lease は返却せず 1 枚だけ保持)。
|
||||
- 目的: C7 v2 で current_page/freelist を握れる状態を作り、今後の v2 専用 slow 境界や multi-page 対応を進めやすくする。
|
||||
|
||||
### Phase 31: C7-only HotHeap v2 A/B 配線(v1 ラッパ)
|
||||
- ENV: `HAKMEM_TINY_HOTHEAP_V2` + `HAKMEM_TINY_HOTHEAP_CLASSES` (bit7) で C7 を v2 経路に差し替え可能に。
|
||||
- Front: `malloc_tiny_fast` / `free_tiny_fast` の C7 直線パスで v2→v1→legacy slow の順に試行(デフォルトは v1)。
|
||||
- 実体: v2 alloc/free は現時点で v1 の薄ラッパ(挙動不変、性能も A/B で同等の想定)。他クラスは未接続のまま。
|
||||
- 目的: 次フェーズで C7-only で v1/v2 を切り替えられるようにする前段階。
|
||||
- A/B(Release, HEAP_STATS=ON)
|
||||
- C7-only (ws=64, iters=20k): v2 OFF **43.28M**, v2 ON **43.28M**(fast=11015 / slow=1 で一致)
|
||||
- Mixed 16–1024B (ws=256, iters=20k): LEGACY **42.18M** / C7_SAFE v2 OFF **41.15M** / C7_SAFE v2 ON **40.74M**(cls7 fast=5691 / slow=1 で一致)
|
||||
|
||||
### Phase 28: v1 の締め(標準プロファイルと次世代入口)
|
||||
- 標準プロファイルを固定:
|
||||
- LEGACY … TinyHeap 無効。
|
||||
- C7_SAFE … `HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_STATS_BOX=1 HAKMEM_TINY_STATS_BATCH=0`(C7 SAFE + Stats Box 即時)。C6 は OFF。
|
||||
- Bench/実験専用 … C7_ULTRA_BENCH、C6 TinyHeap(mask=0x40/0xC0)、STATS_BATCH=1。
|
||||
- mimalloc 対決用フラグ(v1 基準点):
|
||||
- C7-only: `HAKMEM_BENCH_C7_ONLY=1 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_STATS_BOX=1 HAKMEM_TINY_STATS_BATCH=0 HAKMEM_TINY_LARSON_FIX=1`(ULTRA は bench 用)。
|
||||
- Mixed 16–1024B: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_LARSON_FIX=1` で PROFILE=LEGACY と PROFILE=C7_SAFE を並べて比較。
|
||||
- C6 の扱いを凍結: C6 TinyHeap/Hot は v1 では bench 専用に留め、v2 で C5–C7 をまとめて再設計する前提に移行。
|
||||
|
||||
### Phase 27: STATS_BOX / STATS_BATCH A/B(C7 SAFE)
|
||||
- C7-only(20k/ws=64, PROFILE=C7_SAFE, HOT=1, LARSON_FIX=1, HEAP_STATS=ON)
|
||||
- STATS_BOX=0: **43.31M ops/s**(cls7 fast=11015 / slow=1)
|
||||
- STATS_BOX=1, BATCH=0: **43.06M ops/s**(fast/slow 同一)
|
||||
- STATS_BOX=1, BATCH=1: **35.10M ops/s**(fast/slow 同一、性能大幅低下)
|
||||
- STATS_BOX=1, BATCH=1, META_MODE=2(ULTRA bench): **48.55M ops/s**(bench 専用)
|
||||
- Mixed 16–1024B(20k/ws=256, HEAP_STATS=ON)
|
||||
- LEGACY: **40.92M ops/s**
|
||||
- C7_SAFE + STATS_BOX=1, BATCH=0: **42.72M ops/s**
|
||||
- C7_SAFE + STATS_BOX=1, BATCH=1: **35.27M ops/s**
|
||||
- 結論: STATS_BOX 自体は安全で BATCH=0 なら性能も同等〜わずかプラス。BATCH=1 は C7-only/Mixed とも大きく劣化するため bench 専用に留め、標準は STATS_BOX=1 & BATCH=0(または STATS_BOX=0)のままとする。
|
||||
|
||||
### Phase 26: Cold Stats Box をバッチ対応アグリゲータに拡張(C7 SAFE)
|
||||
- `core/box/tiny_stats_box.h` に pending(used/active)と ENV `HAKMEM_TINY_STATS_BATCH` を追加。`tiny_stats_flush_for_page()` は delta を受け取り、バッチ ON なら page pending へ貯め、threshold(capacity×16 相当)超え or empty で `tiny_stats_maybe_flush_for_page()` が meta/ss_active_* にまとめて反映。バッチ OFF なら従来通り即時更新。
|
||||
- `tiny_heap_page_t` に pending フィールドを追加し、`tiny_heap_meta_flush_page()` は C7 SAFE の delta を Stats Box に渡すだけに変更(deltas は heap 側で zero)。C7 以外の挙動は不変。
|
||||
- ドキュメント: `docs/analysis/COLD_TINY_STATS_BOX_DESIGN.md` に遅延許容条件とバッチフロー、ENV (`HAKMEM_TINY_STATS_BOX`, `HAKMEM_TINY_STATS_BATCH`) を追記。
|
||||
- A/B は Phase27 で実施済み(C7-only/Mixed いずれも BATCH=1 は大幅マイナス)。挙動変更は C7 SAFE + Stats Box 有効時のみ。
|
||||
|
||||
### Phase 25: Cold Stats Box(C7 SAFE flush の箱分離)
|
||||
- 新規ドキュメント: `docs/analysis/COLD_TINY_STATS_BOX_DESIGN.md` を追加し、meta/active 更新を Cold Stats Box に押し出す設計メモを作成(Hot 側は page->used だけ、統計は Box 経由で更新する方針)。
|
||||
- コード: `core/box/tiny_stats_box.h` を追加(`HAKMEM_TINY_STATS_BOX` で A/B)。C7 SAFE (class7 meta_mode=1) の delta flush は `tiny_stats_flush_for_page()` 経由に分離し、現状は従来と同じ meta->used / ss_active_* 更新を行うだけ(挙動不変)。
|
||||
- ビルド: `make -j4 bench_random_mixed_hakmem` OK。
|
||||
- ベンチ (C7-only 20k/ws=64, PROFILE=C7_SAFE, HOT=1, HEAP_STATS=ON):
|
||||
- STATS_BOX=0: **42.99M ops/s**(cls7 fast=11015 / slow=1)。
|
||||
- STATS_BOX=1: **42.92M ops/s**(cls7 fast=11015 / slow=1)。挙動・カウンタ一致 → A/B で差分なし。
|
||||
|
||||
### Phase 24: C6 SAFE 性能チェック(bench 専用の結論固め)
|
||||
- 条件: Release, ws=256, iters=20k, `HAKMEM_TINY_LARSON_FIX=1`, すべて debug ENV OFF。`HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1` で測定。
|
||||
- C6-heavy (min=257/max=768):
|
||||
- LEGACY (TinyHeap OFF): **41.74M ops/s**(HEAP_STATS 0)。
|
||||
- C6 TinyHeap mode0 (`HEAP_BOX=1 HEAP_CLASSES=0x40 C6_HOT=1 C6_META_MODE=0`): **36.07M ops/s**(cls6 fast=5381 / slow_prepare=1)。
|
||||
- C6 TinyHeap mode1 (`HEAP_BOX=1 HEAP_CLASSES=0x40 C6_HOT=1 C6_META_MODE=1`): **28.86M ops/s**(cls6 fast=2692 / slow_prepare=2690)。
|
||||
- Mixed 16–1024B:
|
||||
- LEGACY: **40.90M ops/s**。
|
||||
- C7_SAFE (C6 OFF, `PROFILE=C7_SAFE`): **40.96M ops/s**(cls7 fast=5691 / slow=1)。
|
||||
- C6+C7 SAFE (`HEAP_CLASSES=0xC0` / C6+7 HOT / meta_mode=1): **27.21M ops/s**(cls6 fast=1388 / slow=1366、cls7 fast=5664 / slow=19)。
|
||||
- 結論: C6 TinyHeap は mode0/1 いずれも C6-heavy/Mixed で大幅マイナス。C6 meta_mode=1 は slow_prepare が増え性能も悪化。C6 は引き続き bench/実験専用マスク(0x40/0xC0)とし、通常は LEGACY または C7_SAFE プロファイルを推奨。
|
||||
|
||||
### Phase 20: C6 Hot front の箱追加(C7 対称の直線パス)
|
||||
- 新規ドキュメント: `docs/analysis/C6_HOTBOX_DESIGN.md` を追加し、C6 を TinyHeap でホット化する箱の目的と境界を定義(SAFE のみ、ULTRA なし)。C6 TinyHeap は当面 bench/実験扱いと明記。
|
||||
- ENV/Route:
|
||||
- `HAKMEM_TINY_C6_HOT` を追加。1 のとき class6 だけ Gate→Heap の直線パスを有効化。
|
||||
- Route snapshot は `tiny_heap_class_route_enabled(6)` が `HAKMEM_TINY_C6_HOT && class_mask` を満たすときだけ HEAP に設定。
|
||||
- `tiny_c6_front_uses_heap()` を追加し、C7 と対称の front 判定を用意。
|
||||
- Front:
|
||||
- alloc: size が class6 範囲((256, 512])かつ `tiny_c6_front_uses_heap()` のとき、LUT/route を飛ばして `tiny_heap_alloc_class_fast(6)` に直行。miss は静かに `tiny_cold_refill_and_alloc(6)` へ。
|
||||
- free: class_idx==6 かつ `tiny_c6_front_uses_heap()` なら Larson self-thread 判定後に TinyHeap free へ直行(route LUT は 1 回だけ参照)。
|
||||
- ベンチ(Release, ws=256, iters=20k, LARSON_FIX=1):
|
||||
- C6-heavy (min=257/max=768):
|
||||
- LEGACY (PROFILE=LEGACY): ≈44.0M ops/s。
|
||||
- C6 TinyHeap + Hot (`HEAP_BOX=1 HEAP_CLASSES=0x40 C6_HOT=1 META_MODE=1`): ≈38.3M ops/s(HEAP_STATS cls6: fast=5381 slow_prepare=1)。
|
||||
- Mixed 16–1024B:
|
||||
- LEGACY: ≈42.0M ops/s。
|
||||
- C7_SAFE (C6 OFF): ≈42.3M ops/s。
|
||||
- C6+C7 TinyHeap + Hot (`HEAP_CLASSES=0xC0 C6_HOT=1 C7_HOT=1 META_MODE C6=1 C7=1`): ≈37.3M ops/s(HEAP_STATS cls6: fast=2753 slow=1 / cls7: fast=5682 slow=1)。
|
||||
- 所感: フロントを薄くしても C6 TinyHeap は依然マイナスが大きい。C7 SAFE は Mixed でもほぼ誤差~わずかプラス。C6 は bench/実験専用マスク(0x40/0xC0)の位置づけを維持。
|
||||
|
||||
### Phase 19: プロファイル固定と次の箱候補
|
||||
- プロファイルまとめ:
|
||||
- LEGACY: TinyHeap 全無効(基準)。Mixed 16–1024B は ≈44M ops/s 台。
|
||||
- C7_SAFE: class7 だけ TinyHeap + meta_mode=1。C7-only 20k/ws64 ≈46.6M、Mixed 16–1024B は LEGACY 比 ±1M 以内(軽いマイナス〜誤差)。C7-heavy 向け推奨プロファイル。
|
||||
- C7_ULTRA_BENCH: class7 + meta_mode=2(bench 専用、Superslab/Tier 整合は緩む)。C7-only 20k/ws64 ≈52M。
|
||||
- C6 TinyHeap: `HAKMEM_TINY_HEAP_CLASSES=0x40/0xC0` は bench/実験専用。C6-heavy/Mixed では明確にマイナス(例: LEGACY≈44.3M → C6 TinyHeap≈38.6M)。
|
||||
- 当面の運用:
|
||||
- 普段は PROFILE=LEGACY か PROFILE=C7_SAFE を手で選択。C6 TinyHeap は明示しない限り OFF。
|
||||
- C7-only 比較: `HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_LARSON_FIX=1`(ULTRA は研究用途のみ)。
|
||||
- Mixed 16–1024B: PROFILE=LEGACY と PROFILE=C7_SAFE を並べて比較。C6 を触るときは HEAP_CLASSES を明示し、HEAP_STATS で fast/slow を記録。
|
||||
- 次に伸ばす箱候補(検討メモのみ):
|
||||
1. C6 TinyHeap を C7 SAFE 流に本気で攻める(current 固定 + delta/flush の安全版)。Superslab/Tier の整合を再チェックしつつ命令削減。
|
||||
2. Tiny front をさらに薄くする(class6/7 用の直線 front を拡張し、Gate/UC/TLS SLL 経路の命令を減らす)。上記1と実質同じ箱の別側面。
|
||||
|
||||
### Phase 18: C6 SAFE 計測・メタモード拡張(環境ゲートのみ実装、挙動は整合優先)
|
||||
- ENV: `HAKMEM_TINY_C6_META_MODE` を追加(0=OFF, 1=SAFE)。現状は整合性優先で C6 は meta/active を per-alloc 更新のまま(behavior mode=0扱い、delta/flush 未使用)。TinyHeap へ載せるかは `HAKMEM_TINY_HEAP_CLASSES` で指定(デフォルト 0x80=C7 のみ)。
|
||||
- C6 偏重 (min=257/max=768, ws=256, iters=20k, LARSON_FIX=1):
|
||||
- LEGACY (TinyHeap OFF): ≈44.28M ops/s(HEAP_STATS=0)。
|
||||
- TinyHeap C6 only mask=0x40, META_MODE=0: ≈38.81M ops/s(cls6 fast=5372 / slow_prepare=1)。
|
||||
- TinyHeap C6 only mask=0x40, META_MODE=1: ≈38.59M ops/s(同上:slow_prepare≒1 → 回帰は prepare 頻度由来ではない)。
|
||||
- TinyHeap C6+C7 mask=0xC0, C6 META=1 / C7 META=1: ≈39.94M ops/s(cls6 fast=5372/slow=1, cls7 fast=5691/slow=1)。
|
||||
- Mixed 16–1024B (ws=256, iters=20k, LARSON_FIX=1):
|
||||
- LEGACY: ≈44.27M ops/s。
|
||||
- PROFILE=C7_SAFE (mask=0x80, C7 META=1): ≈43.64M ops/s。
|
||||
- TinyHeap C6 only mask=0x40, META_MODE=0: ≈38.48M ops/s(cls6 fast=2744/slow=1)。
|
||||
- TinyHeap C6 only mask=0x40, META_MODE=1: ≈38.66M ops/s(cls6 fast=2744/slow=1)。
|
||||
- TinyHeap C6+C7 mask=0xC0, C6 META=1 / C7 META=1: ≈39.49M ops/s(cls6 fast=2744/slow=1, cls7 fast=5691/slow=1)。
|
||||
- 所感: C6 は slow_prepare がほぼ 0 でも回帰しており、meta/route 側コストが支配的。C6 SAFE はまだ「挙動は mode 0 と同等(安全寄せ)」で、meta-light は未適用。次は C6 専用の軽量化を安全に再導入するか、Front/Gate/Route 側の命令削減を優先するかを検討。
|
||||
|
||||
### 現在の状態(Tiny / Superslab / Warm Pool)
|
||||
- Tiny Front / Superslab / Shared Pool は Box Theory 準拠で 3 層構造に整理済み(HOT/WARM/COLD)。
|
||||
- Tiny Gatekeeper Box(alloc/free)と Tiny Route Box により、USER→BASE 変換と Tiny vs Pool のルーティングを入口 1 箇所に集約。
|
||||
@ -256,6 +367,101 @@
|
||||
- Mixed 16–1024B: OFF≈47.6M / C7-only TinyHeap≈36.9M / C6+C7 TinyHeap≈30.3M(警告なし)。
|
||||
- ドキュメント更新: Tiny lane 判定と TinyHeap 整合のメモを `docs/analysis/TINY_HEAP_BOX_DESIGN.md` / `docs/analysis/C7_HOTBOX_DESIGN.md` に追記。
|
||||
|
||||
### Phase ULTRA: C7 meta モードを 0/1/2 の 3 段階に
|
||||
- 新 ENV `HAKMEM_TINY_C7_META_MODE` を導入(0:OFF, 1:SAFE meta-light=従来の delta+閾値 flush/clamp, 2:ULTRA=bench 専用で meta/active を per-alloc では触らない)。`HAKMEM_TINY_C7_META_LIGHT` は未指定時の後方互換ゲートとして残し、mode 未指定なら SAFE=1 相当。
|
||||
- ULTRA(mode=2) は per-alloc で meta->used / ss_active_* を更新せず、delta/flush もスキップ。Box 境界は維持するが Superslab/Tier 統計は崩れる前提で C7-only bench 専用。
|
||||
- SAFE(mode=1) は従来のページ境界 flush + 閾値 flush + attach clamp を維持。本番は mode=0/1 のみを推奨。
|
||||
- ベンチ (C7-only 20k/ws=64, Release, HEAP_BOX=1 HEAP_CLASSES=0x80 HOT=1 LARSON_FIX=1):
|
||||
- mode=0: ≈35.0M ops/s
|
||||
- mode=1: ≈37.1M ops/s
|
||||
- mode=2 (ULTRA): ≈41.4M ops/s
|
||||
- ドキュメント更新: meta モードの三段化と ULTRA は bench 専用である旨を `docs/analysis/TINY_HEAP_BOX_DESIGN.md` / `docs/analysis/C7_HOTBOX_DESIGN.md` に追記。
|
||||
|
||||
### Phase 10: C7 ULTRA の軽量化&fast/slow 計測(20k/ws=64, Release)
|
||||
- 変更: ULTRA(mode=2) の pop/push で meta->freelist/carved への atomic store をスキップ(per-alloc の余分な write を削減、Box 境界は維持)。
|
||||
- C7-only stats(HEAP_BOX=1 HEAP_CLASSES=0x80 HOT=1 LARSON_FIX=1 C7_HEAP_STATS=1):
|
||||
- mode=0: ops≈38.7M / alloc_fast_current=10052 / alloc_slow_prepare=7681 / free_fast_local=10053
|
||||
- mode=1: ops≈34.1M / alloc_fast_current=5837 / alloc_slow_prepare=5179 / free_fast_local=8727
|
||||
- mode=2(ULTRA): ops≈41.6M / alloc_fast_current=5948 / alloc_slow_prepare=5068 / free_fast_local=7190
|
||||
- 所感: slow_prepare 割合が依然高く、ULTRA でも legacy(≈42.5M) をわずかに下回る。次ステップは current_page の持続や prepare 回数削減に集中する。
|
||||
|
||||
### Phase 11: C7 current_page の可視化と ULTRA 固定化トライ(20k/ws=64, Release)
|
||||
- 追加カウンタ(C7_HEAP_STATS=1 連動): `g_c7_page_stats` を導入し、prepare_calls / prepare_with_current_null / prepare_from_partial / current_set_from_free / current_dropped_to_partial を記録。destructor で `[C7_PAGE_STATS]` をダンプ。
|
||||
- C7 ULTRA の free パスを強化: free で used>0 のページは必ず current_page に据え直し、meta 触らず早期 return。page stats もこの経路でカウント。
|
||||
- ベンチ (mode=2 ULTRA, HEAP_BOX=1 HEAP_CLASSES=0x80 HOT=1 LARSON_FIX=1 C7_HEAP_STATS=1 stats dump ON):
|
||||
- ops≈40.9M
|
||||
- C7_HEAP_STATS: alloc_fast_current=5948 / alloc_slow_prepare=5068 / free_fast_local=7190
|
||||
- C7_PAGE_STATS: prepare_calls=5068 / prepare_with_current_null=5068 / prepare_from_partial=0 / current_set_from_free=0 / current_dropped_to_partial=0
|
||||
→ prepare のたびに current_page が NULL になっており、free 側で current を維持できていないことが判明。次は current_page ポリシー/attach パスの軽量化を追加で検討。
|
||||
- ULTRA の current_page 固定化(unlink/empty を抑止、prepare で current を優先)を追加。
|
||||
- C7-only 20k/ws=64, mode=2: ops≈52.0M、C7_HEAP_STATS: fast=11015 / slow_prepare=1 / free_fast_local=7137、C7_PAGE_STATS: prepare_calls=1 (current null=1)。
|
||||
- 現状 C7 ULTRA は legacy (~42.5M) を上回り、slow_prepare をほぼ 0 に抑制できている。SAFE への逆輸入余地は今後検討。
|
||||
|
||||
### Phase 12: SAFE (META_MODE=1) に current_page ポリシーを逆輸入
|
||||
- C7 SAFE で current_page を極力保持するように変更(empty 時も delta flush のみで detach せず current 維持、mark_full で current を追い出さない、prepare は current に空きがあれば即 return)。
|
||||
- ベンチ (HEAP_BOX=1 HEAP_CLASSES=0x80 HOT=1 LARSON_FIX=1, ws=64):
|
||||
- SAFE mode=1, 20k: ops≈46.6M(C7_HEAP_STATS fast=11015 / slow_prepare=1 / free_fast_local=8726、C7_PAGE_STATS: prepare_calls=1)。
|
||||
- SAFE 長時間: 100k≈46.7M / 200k≈44.99M。`HAKMEM_TINY_C7_DELTA_DEBUG=1` でも `[C7_DELTA_SUMMARY] nonzero_pages=0 used_delta_sum=0 active_delta_sum=0`。
|
||||
- ULTRA (mode=2) は bench 専用のまま。本番寄りは mode=0/1 を使用する方針。
|
||||
|
||||
### Phase 13: TinyHeap stats 汎用化と C6/C7 混在ベンチ
|
||||
- 変更: `TinyC7HeapStats` を `TinyHeapClassStats g_tiny_heap_stats[TINY_NUM_CLASSES]` に拡張し、`HAKMEM_TINY_HEAP_STATS` / `_DUMP`(従来の `_C7_` も互換)で全クラスの fast/slow/fallback を取得可能にした。C7 page stats は従来通り。
|
||||
- Mixed 16–1024B (iters=20k, ws=256, LARSON_FIX=1):
|
||||
- TinyHeap OFF: ≈43.7M ops/s。
|
||||
- C7 SAFE のみ TinyHeap (`HEAP_BOX=1 HEAP_CLASSES=0x80 META_MODE=1 HOT=1`): ≈44.9M ops/s(`HEAP_STATS[7] fast=5691 slow_prepare=1`)。
|
||||
- C6+C7 TinyHeap (`HEAP_CLASSES=0xC0` 同条件): ≈39.3M ops/s(`HEAP_STATS[6] fast=2744 slow=1`, `HEAP_STATS[7] fast=5691 slow=1`)。
|
||||
- C6 偏重 (min=257 max=768, iters=20k, ws=256):
|
||||
- TinyHeap OFF: ≈43.8M ops/s。
|
||||
- C6 TinyHeap のみ (`HEAP_CLASSES=0x40`, C7 legacy): ≈38.5M ops/s(`HEAP_STATS[6] fast=5372 slow=1`)。
|
||||
- C6+C7 TinyHeap (`HEAP_CLASSES=0xC0`, C7 SAFE): ≈40.6M ops/s(`HEAP_STATS[6] fast=5372 slow=1`, `HEAP_STATS[7] fast=5691 slow=1`)。
|
||||
- 方針メモ: C7 SAFE は mixed でも悪化せずプラスが見えるのでデフォルト TinyHeap 候補。C6 は slow_prepare は少ないが経路オーバーヘッドで throughput 低下が大きいので、当面は bench/実験用 (HEAP_CLASSES=0x40/0xC0) に留める。推奨例: 本番寄り C7 は `HEAP_BOX=1 HEAP_CLASSES=0x80 META_MODE=1`、C7-only bench は `META_MODE=2`。
|
||||
|
||||
### Phase 21–22 (C6 meta_mode=1 クラッシュ切り分け: 実験専用)
|
||||
- C6 meta_mode=1 は bench/実験専用として明記(通常ベンチは meta_mode=0 or C6 TinyHeap OFF 推奨)。
|
||||
- C6 delta/flush トレース: `HAKMEM_TINY_C6_DELTA_TRACE` で last_delta_site を記録、`HAKMEM_TINY_C6_DELTA_DEBUG` と合わせて class6 の delta を dump 可能。
|
||||
- C6 pop Fail-Fast: `HAKMEM_TINY_C6_DEBUG_POP=1` で `tiny_heap_page_pop` が page 範囲/容量/空き無し/ss mismatch/クラス不整合/容量 0/フリーリスト OOB を検知すると `[C6_POP_FAIL]` を吐いて abort。pop/free のデバッグログもこの ENV でのみ出力(上限512行)。
|
||||
- 防御強化:
|
||||
- attach 時に meta->freelist を範囲チェックし、OOB は `meta->freelist=NULL` に潰す(debug 時のみ 1 行ログ)。
|
||||
- empty→release 時に C6 SAFE は meta->freelist を NULL にし、debug 時は page->free_list を poison して再利用時の壊れを検知。
|
||||
- pop で freelist OOB を Fail-Fast 追加。
|
||||
- delta site にタグ付け(ALLOC/FREE/ATTACH/EMPTY/THRESHOLD)、flush 前に記録して壊れたページの直前イベントを把握できるようにした。
|
||||
- 再現状況(C6-heavy min=257/max=768, ws=256, HOT=1, meta_mode=1, DEBUG_POP/DELTA_TRACE/DELTA_DEBUG=1):
|
||||
- iters=1000/1500/2000: すべて完走、`C6_DELTA_SUMMARY` は 0/0/0、Fail-Fast ログなし。
|
||||
- iters=20000 でも完走(同じく delta_sum=0)。ネイティブでの以前の SIGSEGV は再現せず。
|
||||
- ログ末尾は同一ページ内で free_list が範囲内に収まり、last_delta_site は ATTACH/ALLOC を往復。
|
||||
→ クラッシュ原因は meta->freelist の OOB 読み込みが濃厚。Fail-Fast/Poison で暫定的に封じ込め。
|
||||
|
||||
### Phase 14: TinyHeap Profile Box 追加とプロファイル別 A/B
|
||||
- ENV を整理: `HAKMEM_TINY_HEAP_PROFILE` を追加(LEGACY/C7_SAFE/C7_ULTRA_BENCH/CUSTOM)。ENV 未指定時のデフォルト mask/meta_mode をプロファイルで決定、`HAKMEM_TINY_HEAP_BOX` も LEGACY 以外なら自動 ON。`HAKMEM_TINY_HEAP_CLASSES` / `HAKMEM_TINY_C7_META_MODE` があればそちらを最優先。
|
||||
- C7_SAFE → class mask=0x80, C7 meta_mode=1(SAFE)、C7_HOT は別途 1。
|
||||
- C7_ULTRA_BENCH → class mask=0x80, C7 meta_mode=2(bench 専用)。
|
||||
- LEGACY → TinyHeap 無効。
|
||||
- ベンチ(20k/ws=64, Release, LARSON_FIX=1):
|
||||
- C7-only: LEGACY (HEAP_BOX=0, PROFILE=LEGACY, HOT=0) ≈39.4M / PROFILE=C7_SAFE+HOT=1 ≈42.1M / PROFILE=C7_ULTRA_BENCH+HOT=1 ≈48.8M。
|
||||
- Mixed 16–1024B: PROFILE=LEGACY ≈44.2M / PROFILE=C7_SAFE+HOT=1 ≈42.8M。
|
||||
- 推奨プロファイル例(現状案):
|
||||
- 本番寄せ C7: `HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_LARSON_FIX=1`。
|
||||
- C7-only bench/mimalloc 比較: `HAKMEM_TINY_HEAP_PROFILE=C7_ULTRA_BENCH HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_LARSON_FIX=1`。
|
||||
- 次フェーズの判断メモ: C7_SAFE プリセットは固まったので、次は (A) C6 TinyHeap を SAFE 流に軽量化するか、(B) front/gate の命令数削減を perf で詰めるかの二択で進める。
|
||||
|
||||
### Phase 16: Front/Gate Flatten(Route Snapshot + Front class stats)
|
||||
- Route Snapshot Box (`core/box/tiny_route_env_box.h`) を追加し、起動時にクラスごとの経路(TinyHeap/Legacy)を LUT に固定。`tiny_route_for_class(ci)` で hot path の分岐を 1 回に縮約(`tiny_route_snapshot_init()` は init 時+未初期化時に lazy 呼び出し)。
|
||||
- Front class 分布カウンタを追加(`HAKMEM_TINY_FRONT_CLASS_STATS[_DUMP]=1`)。Mixed 16–1024B/LEGACY では cls2=147/147, cls3=341/341, cls4=720/720, cls5=1420/1420, cls6=2745/2745, cls7 alloc=5692 free=0。C7_SAFE では同配分で cls7 free=4573。
|
||||
- Gate 再配線: `malloc_tiny_fast` は「size→class→route」を 1 回だけ評価し、route=HEAP は TinyHeap 直行、NULL 時のみ Legacy slow へ静かにフォールバック。`free_tiny_fast` も route LUT ベースで TinyHeap/Legacy を振り分け(Larson fix + TinyHeap free with meta)。
|
||||
- ベンチ (Release, LARSON_FIX=1, iters=20k):
|
||||
- C7-only ws=64: LEGACY ≈39.7M / C7_SAFE profile+HOT=1 ≈41.1M / C7_ULTRA_BENCH+HOT=1 ≈46.1M。
|
||||
- Mixed 16–1024B ws=256: LEGACY ≈42.1M / C7_SAFE profile+HOT=1 ≈39.8M(差を ~-5% まで圧縮)。
|
||||
- 次フェーズ候補メモ: gate/UC の命令削減を続けるか、C6 TinyHeap を SAFE 流(current 固定+軽量化)に寄せるかを選ぶ段階。
|
||||
|
||||
### Phase 17: C7 フロント超直線化 (size==1024 専用パイプ)
|
||||
- Route Snapshot の上に C7 判定ヘルパ `tiny_c7_front_uses_heap()` を追加し、Gate から class7 の経路を 1 LUT で取得。
|
||||
- `malloc_tiny_fast` 冒頭に C7 専用パス: `size==1024 && tiny_c7_front_uses_heap()` のとき class/LUT/route を飛ばして `tiny_c7_alloc_fast` に直行、miss 時のみ `tiny_cold_refill_and_alloc(7)` へ静かにフォールバック。他クラスは従来の route LUT 経由。
|
||||
- `free_tiny_fast` も class7 が Heap route なら先に判定し、Larson owner 一致後に `tiny_c7_free_fast_with_meta` へ直行(route はスナップショットから 1 回だけ読む)。
|
||||
- ベンチ (Release, iters=20k, LARSON_FIX=1, HOT=1):
|
||||
- C7-only ws=64: PROFILE=LEGACY ≈37.1M / C7_SAFE ≈38.2M / C7_ULTRA_BENCH ≈45.3M ops/s。
|
||||
- Mixed 16–1024B ws=256: PROFILE=LEGACY ≈40.3M / C7_SAFE ≈40.7M ops/s(回帰を ~-1M まで圧縮)。
|
||||
- 次ステップ案: (A) C6 TinyHeap を C7 SAFE 流(current 固定+meta-light SAFE)に寄せて再評価するか、(B) Tiny front/gate/UC の命令数削減を perf で詰めるかを選ぶフェーズ。
|
||||
|
||||
ホットパス perf フェーズの TODO(案)
|
||||
1. tiny_alloc_fast / tiny_free_fast_v2 の再プロファイル:残存分岐・間接呼び出し・重い箱を特定。
|
||||
2. Unified Cache ヒットパスを最短化:ヒット時を 1–2 load + 軽分岐に近づける(必要なら C7 専用インライン版検討)。
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
#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
|
||||
#include "tiny_heap_env_box.h" // profile gates
|
||||
|
||||
// Forward decls for SuperSlab active counters (definitions in hakmem_tiny_ss_active_box.inc)
|
||||
void ss_active_add(SuperSlab* ss, uint32_t n);
|
||||
@ -40,8 +41,11 @@ typedef struct tiny_heap_page_t {
|
||||
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 の差分
|
||||
int32_t active_delta; // meta-light 用: total_active_blocks の差分(主に C6/C7)
|
||||
int32_t used_delta; // meta-light 用: meta->used の差分(主に C6/C7)
|
||||
int32_t stats_active_pending; // Cold Stats Box 用 pending(C7 SAFE)
|
||||
int32_t stats_used_pending; // Cold Stats Box 用 pending(C7 SAFE)
|
||||
int32_t last_delta_site; // debug: 直近で delta を触ったサイト(C6/C7 meta-light 用)
|
||||
} tiny_heap_page_t;
|
||||
|
||||
typedef struct tiny_heap_class_t {
|
||||
@ -59,27 +63,104 @@ typedef struct tiny_heap_ctx_t {
|
||||
uint8_t initialized;
|
||||
} tiny_heap_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
TinySlabMeta* meta;
|
||||
SuperSlab* ss;
|
||||
uint8_t* base;
|
||||
void* freelist;
|
||||
uint16_t capacity;
|
||||
uint16_t slab_idx;
|
||||
tiny_heap_page_t* page; // v1 TinyHeap の page 構造体(lease 元)
|
||||
} TinyHeapPageLease;
|
||||
|
||||
// Stats Box(Cold 側への集計フック)
|
||||
#include "tiny_stats_box.h"
|
||||
|
||||
// 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 inline int tiny_heap_stats_enabled(void) {
|
||||
static int g = -1;
|
||||
if (__builtin_expect(g == -1, 0)) {
|
||||
const char* eh = getenv("HAKMEM_TINY_HEAP_STATS");
|
||||
const char* e = getenv("HAKMEM_TINY_C7_HEAP_STATS");
|
||||
g = (e && *e && *e != '0') ? 1 : 0;
|
||||
g = ((eh && *eh && *eh != '0') || (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;
|
||||
static inline int tiny_c7_heap_stats_enabled(void) {
|
||||
return tiny_heap_stats_enabled();
|
||||
}
|
||||
return g;
|
||||
|
||||
// meta mode: 0=OFF, 1=SAFE meta-light (delta + flush), 2=ULTRA (bench only, meta/active をほぼ触らない)
|
||||
// meta mode: 0=OFF, 1=SAFE meta-light (delta + flush), 2=ULTRA (bench only, meta/active をほぼ触らない)
|
||||
static inline int tiny_c7_meta_mode(void);
|
||||
static inline int tiny_heap_meta_mode_for_class(int class_idx) {
|
||||
if (class_idx == 7) {
|
||||
return tiny_c7_meta_mode();
|
||||
}
|
||||
if (class_idx == 6) {
|
||||
static int g_c6_mode = -1;
|
||||
if (__builtin_expect(g_c6_mode == -1, 0)) {
|
||||
const char* e = getenv("HAKMEM_TINY_C6_META_MODE");
|
||||
if (e && *e && *e != '0') {
|
||||
g_c6_mode = 1;
|
||||
} else {
|
||||
g_c6_mode = 0;
|
||||
}
|
||||
}
|
||||
// NOTE: mode 1 for class 6 is experimental (bench-only, may crash). See CURRENT_TASK for guidance.
|
||||
return g_c6_mode;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int tiny_c7_meta_mode(void) {
|
||||
static int g_mode = -1;
|
||||
if (__builtin_expect(g_mode == -1, 0)) {
|
||||
const char* e = getenv("HAKMEM_TINY_C7_META_MODE");
|
||||
if (!e || !*e) {
|
||||
const char* l = getenv("HAKMEM_TINY_C7_META_LIGHT");
|
||||
if (l && *l && *l != '0') {
|
||||
g_mode = 1;
|
||||
} else {
|
||||
g_mode = tiny_heap_profile_default_c7_meta_mode(tiny_heap_profile_mode());
|
||||
}
|
||||
} else {
|
||||
int v = atoi(e);
|
||||
if (v < 0) v = 0;
|
||||
if (v > 2) v = 2;
|
||||
g_mode = v;
|
||||
}
|
||||
}
|
||||
return g_mode;
|
||||
}
|
||||
|
||||
static inline int tiny_heap_behavior_mode_for_class(int class_idx) {
|
||||
return tiny_heap_meta_mode_for_class(class_idx);
|
||||
}
|
||||
|
||||
static inline int tiny_heap_meta_light_enabled_for_class(int class_idx) {
|
||||
return tiny_heap_behavior_mode_for_class(class_idx) >= 1;
|
||||
}
|
||||
|
||||
static inline int tiny_heap_meta_ultra_enabled_for_class(int class_idx) {
|
||||
return (class_idx == 7) && tiny_heap_meta_mode_for_class(class_idx) == 2;
|
||||
}
|
||||
|
||||
static inline int tiny_heap_meta_mode_effective(int class_idx) {
|
||||
return tiny_heap_meta_mode_for_class(class_idx);
|
||||
}
|
||||
|
||||
static inline int tiny_c7_meta_light_enabled(void) {
|
||||
return tiny_heap_meta_light_enabled_for_class(7);
|
||||
}
|
||||
|
||||
static inline int tiny_c7_meta_ultra_enabled(void) {
|
||||
return tiny_heap_meta_ultra_enabled_for_class(7);
|
||||
}
|
||||
|
||||
static inline int tiny_c7_delta_debug_enabled(void) {
|
||||
@ -91,6 +172,33 @@ static inline int tiny_c7_delta_debug_enabled(void) {
|
||||
return g;
|
||||
}
|
||||
|
||||
static inline int tiny_c6_delta_debug_enabled(void) {
|
||||
static int g = -1;
|
||||
if (__builtin_expect(g == -1, 0)) {
|
||||
const char* e = getenv("HAKMEM_TINY_C6_DELTA_DEBUG");
|
||||
g = (e && *e && *e != '0') ? 1 : 0;
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
static inline int tiny_c6_delta_trace_enabled(void) {
|
||||
static int g = -1;
|
||||
if (__builtin_expect(g == -1, 0)) {
|
||||
const char* e = getenv("HAKMEM_TINY_C6_DELTA_TRACE");
|
||||
g = (e && *e && *e != '0') ? 1 : 0;
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
static inline int tiny_c6_debug_pop_enabled(void) {
|
||||
static int g = -1;
|
||||
if (__builtin_expect(g == -1, 0)) {
|
||||
const char* e = getenv("HAKMEM_TINY_C6_DEBUG_POP");
|
||||
g = (e && *e && *e != '0') ? 1 : 0;
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
_Atomic uint64_t alloc_fast_current;
|
||||
_Atomic uint64_t alloc_slow_prepare;
|
||||
@ -98,9 +206,38 @@ typedef struct {
|
||||
_Atomic uint64_t free_slow_fallback;
|
||||
_Atomic uint64_t alloc_prepare_fail;
|
||||
_Atomic uint64_t alloc_fail;
|
||||
} TinyC7HeapStats;
|
||||
} TinyHeapClassStats;
|
||||
|
||||
extern TinyC7HeapStats g_c7_heap_stats;
|
||||
extern TinyHeapClassStats g_tiny_heap_stats[TINY_NUM_CLASSES];
|
||||
|
||||
typedef struct {
|
||||
_Atomic uint64_t prepare_calls;
|
||||
_Atomic uint64_t prepare_with_current_null;
|
||||
_Atomic uint64_t prepare_from_partial;
|
||||
_Atomic uint64_t current_set_from_free;
|
||||
_Atomic uint64_t current_dropped_to_partial;
|
||||
} TinyC7PageStats;
|
||||
|
||||
extern TinyC7PageStats g_c7_page_stats;
|
||||
|
||||
typedef enum {
|
||||
C6_DELTA_NONE = 0,
|
||||
C6_DELTA_ALLOC = 1,
|
||||
C6_DELTA_FREE = 2,
|
||||
C6_DELTA_EMPTY = 3,
|
||||
C6_DELTA_ATTACH = 4,
|
||||
C6_DELTA_THRESHOLD = 5,
|
||||
} tiny_c6_delta_site_t;
|
||||
|
||||
static inline int tiny_c7_page_stats_enabled(void) {
|
||||
return tiny_heap_stats_enabled();
|
||||
}
|
||||
|
||||
static inline TinyHeapClassStats* tiny_heap_stats_for_class(int class_idx) {
|
||||
if (!tiny_heap_stats_enabled()) return NULL;
|
||||
if (class_idx < 0 || class_idx >= TINY_NUM_CLASSES) return NULL;
|
||||
return &g_tiny_heap_stats[class_idx];
|
||||
}
|
||||
|
||||
static inline int tiny_heap_cold_drain_and_free(int class_idx, void* base) {
|
||||
(void)class_idx;
|
||||
@ -127,6 +264,28 @@ static inline tiny_heap_class_t* tiny_heap_class(tiny_heap_ctx_t* ctx, int class
|
||||
return &ctx->cls[class_idx];
|
||||
}
|
||||
|
||||
static inline int tiny_heap_page_is_valid(tiny_heap_class_t* hcls, tiny_heap_page_t* page) {
|
||||
if (!hcls || !page) return 0;
|
||||
return (page >= hcls->nodes) && (page < (hcls->nodes + TINY_HEAP_MAX_PAGES_PER_CLASS));
|
||||
}
|
||||
|
||||
static inline int tiny_heap_ptr_in_page_range(tiny_heap_page_t* page, void* ptr) {
|
||||
if (!page || !ptr || page->capacity == 0 || !page->base) return 0;
|
||||
uintptr_t p = (uintptr_t)ptr;
|
||||
uintptr_t low = (uintptr_t)page->base;
|
||||
size_t stride = 0;
|
||||
// stride は caller が hcls->stride を更新済みの前提。0 の場合は range 判定を諦める。
|
||||
// (debug 用なので false positive でも安全側に倒す)
|
||||
if (page->meta) {
|
||||
stride = tiny_stride_for_class((int)page->meta->class_idx);
|
||||
}
|
||||
if (stride == 0) {
|
||||
return 0;
|
||||
}
|
||||
uintptr_t high = low + (uintptr_t)(stride * (size_t)page->capacity);
|
||||
return (p >= low) && (p < high);
|
||||
}
|
||||
|
||||
static inline size_t tiny_heap_block_stride(int class_idx) {
|
||||
return tiny_stride_for_class(class_idx);
|
||||
}
|
||||
@ -141,23 +300,45 @@ static inline void tiny_heap_page_clear(tiny_heap_page_t* page) {
|
||||
page->meta = NULL;
|
||||
page->ss = NULL;
|
||||
page->next = NULL;
|
||||
page->c7_active_delta = 0;
|
||||
page->c7_used_delta = 0;
|
||||
page->active_delta = 0;
|
||||
page->used_delta = 0;
|
||||
page->stats_active_pending = 0;
|
||||
page->stats_used_pending = 0;
|
||||
page->last_delta_site = 0;
|
||||
}
|
||||
|
||||
static inline void tiny_c7_meta_flush_page(tiny_heap_page_t* page) {
|
||||
if (!page || !tiny_c7_meta_light_enabled()) return;
|
||||
static inline void tiny_c6_mark_delta_site(tiny_heap_page_t* page, tiny_c6_delta_site_t site) {
|
||||
if (!page) return;
|
||||
if (!tiny_c6_delta_trace_enabled()) return;
|
||||
page->last_delta_site = (int32_t)site;
|
||||
}
|
||||
|
||||
static inline void tiny_heap_meta_flush_page(int class_idx, tiny_heap_page_t* page) {
|
||||
if (tiny_heap_meta_ultra_enabled_for_class(class_idx)) {
|
||||
return;
|
||||
}
|
||||
if (!page || !tiny_heap_meta_light_enabled_for_class(class_idx)) return;
|
||||
if (!page->meta || !page->ss) return;
|
||||
|
||||
int32_t active_delta = page->c7_active_delta;
|
||||
int32_t used_delta = page->c7_used_delta;
|
||||
int32_t active_delta = page->active_delta;
|
||||
int32_t used_delta = page->used_delta;
|
||||
if (active_delta == 0 && used_delta == 0) {
|
||||
if (class_idx == 7 && tiny_stats_box_enabled() && tiny_stats_batch_enabled()) {
|
||||
tiny_stats_maybe_flush_for_page(class_idx, page, page->used == 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
page->active_delta = 0;
|
||||
page->used_delta = 0;
|
||||
|
||||
if (class_idx == 7 && tiny_stats_box_enabled()) {
|
||||
tiny_stats_flush_for_page(class_idx, page, used_delta, active_delta);
|
||||
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) {
|
||||
@ -168,16 +349,16 @@ static inline void tiny_c7_meta_flush_page(tiny_heap_page_t* page) {
|
||||
ss_active_dec_one(page->ss);
|
||||
}
|
||||
}
|
||||
page->c7_active_delta = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int tiny_c7_delta_should_flush(tiny_heap_page_t* page) {
|
||||
static inline int tiny_heap_delta_should_flush(int class_idx, tiny_heap_page_t* page) {
|
||||
if (!page) return 0;
|
||||
if (!tiny_c7_meta_light_enabled()) return 0;
|
||||
if (!tiny_heap_meta_light_enabled_for_class(class_idx)) return 0;
|
||||
if (tiny_heap_meta_ultra_enabled_for_class(class_idx)) return 0;
|
||||
|
||||
int32_t ud = page->c7_used_delta;
|
||||
int32_t ad = page->c7_active_delta;
|
||||
int32_t ud = page->used_delta;
|
||||
int32_t ad = page->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;
|
||||
@ -193,13 +374,13 @@ static inline int tiny_c7_delta_should_flush(tiny_heap_page_t* page) {
|
||||
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;
|
||||
}
|
||||
static __attribute__((noinline, unused)) void tiny_heap_debug_dump_deltas_for_class(int class_idx, const char* tag) {
|
||||
if (class_idx < 0 || class_idx >= TINY_NUM_CLASSES) return;
|
||||
if (!tiny_heap_meta_light_enabled_for_class(class_idx)) return;
|
||||
if (class_idx == 7 && !tiny_c7_delta_debug_enabled()) return;
|
||||
if (class_idx == 6 && !tiny_c6_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;
|
||||
|
||||
@ -209,15 +390,16 @@ static __attribute__((noinline, unused)) void tiny_c7_heap_debug_dump_deltas(voi
|
||||
|
||||
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) {
|
||||
if (p->used_delta != 0 || p->active_delta != 0) {
|
||||
nonzero_pages++;
|
||||
used_delta_sum += (int64_t)p->c7_used_delta;
|
||||
active_delta_sum += (int64_t)p->c7_active_delta;
|
||||
used_delta_sum += (int64_t)p->used_delta;
|
||||
active_delta_sum += (int64_t)p->active_delta;
|
||||
fprintf(stderr,
|
||||
"[C7_DELTA_PAGE] idx=%d used_delta=%d active_delta=%d used=%u cap=%u ss=%p slab=%u\n",
|
||||
"[%s_DELTA_PAGE] idx=%d used_delta=%d active_delta=%d used=%u cap=%u ss=%p slab=%u\n",
|
||||
tag ? tag : "C?_DELTA",
|
||||
i,
|
||||
p->c7_used_delta,
|
||||
p->c7_active_delta,
|
||||
p->used_delta,
|
||||
p->active_delta,
|
||||
(unsigned)p->used,
|
||||
(unsigned)p->capacity,
|
||||
(void*)p->ss,
|
||||
@ -226,12 +408,27 @@ static __attribute__((noinline, unused)) void tiny_c7_heap_debug_dump_deltas(voi
|
||||
}
|
||||
|
||||
fprintf(stderr,
|
||||
"[C7_DELTA_SUMMARY] nonzero_pages=%llu used_delta_sum=%lld active_delta_sum=%lld\n",
|
||||
"[%s_DELTA_SUMMARY] nonzero_pages=%llu used_delta_sum=%lld active_delta_sum=%lld\n",
|
||||
tag ? tag : "C?_DELTA",
|
||||
(unsigned long long)nonzero_pages,
|
||||
(long long)used_delta_sum,
|
||||
(long long)active_delta_sum);
|
||||
}
|
||||
|
||||
static __attribute__((noinline, unused)) void tiny_c7_heap_debug_dump_deltas(void) {
|
||||
if (!tiny_c7_meta_light_enabled()) {
|
||||
return;
|
||||
}
|
||||
tiny_heap_debug_dump_deltas_for_class(7, "C7");
|
||||
}
|
||||
|
||||
static __attribute__((noinline, unused)) void tiny_c6_heap_debug_dump_deltas(void) {
|
||||
if (!tiny_heap_meta_light_enabled_for_class(6)) {
|
||||
return;
|
||||
}
|
||||
tiny_heap_debug_dump_deltas_for_class(6, "C6");
|
||||
}
|
||||
|
||||
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++) {
|
||||
@ -247,8 +444,15 @@ static inline tiny_heap_page_t* tiny_heap_class_acquire_node(tiny_heap_class_t*
|
||||
|
||||
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);
|
||||
int class_idx = (page->meta) ? page->meta->class_idx : -1;
|
||||
if (tiny_heap_meta_ultra_enabled_for_class(class_idx)) {
|
||||
page->active_delta = 0;
|
||||
page->used_delta = 0;
|
||||
} else if (tiny_heap_meta_light_enabled_for_class(class_idx)) {
|
||||
if (class_idx == 6) {
|
||||
tiny_c6_mark_delta_site(page, C6_DELTA_EMPTY);
|
||||
}
|
||||
tiny_heap_meta_flush_page(class_idx, page);
|
||||
}
|
||||
intptr_t idx = page - hcls->nodes;
|
||||
if (idx >= 0 && idx < TINY_HEAP_MAX_PAGES_PER_CLASS) {
|
||||
@ -304,12 +508,33 @@ static inline tiny_heap_page_t* tiny_heap_attach_page(tiny_heap_ctx_t* ctx,
|
||||
page->base = tiny_slab_base_for(ss, slab_idx);
|
||||
}
|
||||
}
|
||||
if (class_idx == 7 && tiny_c7_meta_light_enabled()) {
|
||||
if (class_idx == 6 && tiny_heap_meta_mode_effective(class_idx) == 1) {
|
||||
// Fail-Fast: meta->freelist が範囲外なら破棄しておく(古い slab の残骸を拾わない)
|
||||
if (page->free_list && !tiny_heap_ptr_in_page_range(page, page->free_list)) {
|
||||
if (tiny_c6_debug_pop_enabled()) {
|
||||
fprintf(stderr,
|
||||
"[C6_ATTACH_OOB] freelist=%p base=%p cap=%u ss=%p slab=%d\n",
|
||||
page->free_list,
|
||||
(void*)page->base,
|
||||
(unsigned)page->capacity,
|
||||
(void*)page->ss,
|
||||
slab_idx);
|
||||
}
|
||||
page->free_list = NULL;
|
||||
atomic_store_explicit(&meta->freelist, NULL, memory_order_release);
|
||||
}
|
||||
}
|
||||
if (tiny_heap_meta_light_enabled_for_class(class_idx)) {
|
||||
if (!tiny_heap_meta_ultra_enabled_for_class(class_idx)) {
|
||||
if (page->capacity > 0 && page->used > page->capacity) {
|
||||
page->used = page->capacity;
|
||||
}
|
||||
page->c7_used_delta = 0;
|
||||
page->c7_active_delta = 0;
|
||||
}
|
||||
page->used_delta = 0;
|
||||
page->active_delta = 0;
|
||||
if (class_idx == 6) {
|
||||
tiny_c6_mark_delta_site(page, C6_DELTA_ATTACH);
|
||||
}
|
||||
}
|
||||
return page;
|
||||
}
|
||||
@ -406,8 +631,33 @@ static inline void tiny_heap_page_becomes_empty(tiny_heap_ctx_t* ctx, int class_
|
||||
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 (tiny_heap_meta_ultra_enabled_for_class(class_idx)) {
|
||||
// ULTRA: C7 は 1 ページ前提で保持し続ける。publish/unlink/release を避ける。
|
||||
hcls->current_page = page;
|
||||
return;
|
||||
}
|
||||
if (tiny_heap_meta_light_enabled_for_class(class_idx)) {
|
||||
// SAFE: delta を反映
|
||||
if (class_idx == 6) {
|
||||
tiny_c6_mark_delta_site(page, C6_DELTA_EMPTY);
|
||||
}
|
||||
tiny_heap_meta_flush_page(class_idx, page);
|
||||
if (class_idx == 7) {
|
||||
// C7 SAFE: current を保持し、頻繁な detach を避ける
|
||||
tiny_heap_class_unlink(hcls, page);
|
||||
hcls->current_page = page;
|
||||
page->next = NULL;
|
||||
return;
|
||||
}
|
||||
if (class_idx == 6) {
|
||||
// C6 SAFE: freelist の残骸を次回 attach へ渡さない
|
||||
atomic_store_explicit(&page->meta->freelist, NULL, memory_order_release);
|
||||
if (tiny_c6_debug_pop_enabled()) {
|
||||
page->free_list = (void*)0xDEAD6EED;
|
||||
} else {
|
||||
page->free_list = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (page->meta && page->ss) {
|
||||
ss_partial_publish(class_idx, page->ss);
|
||||
@ -418,19 +668,91 @@ static inline void tiny_heap_page_becomes_empty(tiny_heap_ctx_t* ctx, int class_
|
||||
|
||||
static inline void tiny_heap_page_mark_full(tiny_heap_class_t* hcls, tiny_heap_page_t* page) {
|
||||
if (!hcls || !page) return;
|
||||
int class_idx = -1;
|
||||
if (page->meta) {
|
||||
class_idx = page->meta->class_idx;
|
||||
}
|
||||
if (tiny_heap_meta_ultra_enabled_for_class(class_idx)) {
|
||||
// ULTRA: full 判定で current を追い出さない
|
||||
return;
|
||||
}
|
||||
if (tiny_heap_meta_light_enabled_for_class(class_idx) && hcls->current_page == page) {
|
||||
// SAFE: current page は可能な限り保持し、頻繁な unlink を避ける(C6/C7)
|
||||
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());
|
||||
const int mode = tiny_heap_meta_mode_effective(class_idx);
|
||||
const int c6_pop_dbg = (class_idx == 6) && tiny_c6_debug_pop_enabled();
|
||||
if (c6_pop_dbg) {
|
||||
static _Atomic uint32_t g_pop_dbg = 0;
|
||||
uint32_t pop_n = atomic_fetch_add_explicit(&g_pop_dbg, 1, memory_order_relaxed);
|
||||
if (pop_n < 8) {
|
||||
fprintf(stderr, "[POP_ENTRY] cls=%d page=%p\n", class_idx, (void*)page);
|
||||
}
|
||||
}
|
||||
if (!tiny_heap_page_is_valid(hcls, page)) return NULL;
|
||||
if (!page->meta || !page->ss || !page->base) return NULL;
|
||||
if (class_idx == 6 && mode == 1) {
|
||||
int fail = 0;
|
||||
const char* reason = NULL;
|
||||
SuperSlab* ss_chk = hak_super_lookup(page->base);
|
||||
if (!page->meta || page->meta->class_idx != class_idx) {
|
||||
fail = 1; reason = "meta_cls";
|
||||
} else if (page->capacity == 0) {
|
||||
fail = 1; reason = "cap0";
|
||||
} else if (!page->free_list && page->used >= page->capacity) {
|
||||
fail = 1; reason = "exhausted";
|
||||
} else if (!ss_chk) {
|
||||
fail = 1; reason = "ss_lookup_null";
|
||||
} else if (page->ss && ss_chk != page->ss) {
|
||||
fail = 1; reason = "ss_mismatch";
|
||||
} else if (page->free_list && !tiny_heap_ptr_in_page_range(page, page->free_list)) {
|
||||
fail = 1; reason = "freelist_oob";
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"[C6_POP_FAIL] reason=%s page=%p base=%p freelist=%p used=%u cap=%u ss=%p ss_chk=%p meta=%p last_site=%d\n",
|
||||
reason ? reason : "unknown",
|
||||
(void*)page,
|
||||
(void*)page->base,
|
||||
page->free_list,
|
||||
(unsigned)page->used,
|
||||
(unsigned)page->capacity,
|
||||
(void*)page->ss,
|
||||
(void*)ss_chk,
|
||||
(void*)page->meta,
|
||||
page->last_delta_site);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
void* block = NULL;
|
||||
if (class_idx == 6 && __builtin_expect(mode == 1, 0) && c6_pop_dbg) {
|
||||
static _Atomic uint32_t g_c6_pop_dbg = 0;
|
||||
uint32_t n = atomic_fetch_add_explicit(&g_c6_pop_dbg, 1, memory_order_relaxed);
|
||||
if (n < 512) {
|
||||
fprintf(stderr,
|
||||
"[C6_POP_DEBUG] page=%p used=%u cap=%u free_list=%p meta=%p ss=%p base=%p last_site=%d\n",
|
||||
(void*)page,
|
||||
(unsigned)page->used,
|
||||
(unsigned)page->capacity,
|
||||
page->free_list,
|
||||
(void*)page->meta,
|
||||
(void*)page->ss,
|
||||
(void*)page->base,
|
||||
page->last_delta_site);
|
||||
}
|
||||
}
|
||||
if (page->free_list) {
|
||||
block = page->free_list;
|
||||
void* next = tiny_next_read(class_idx, block);
|
||||
page->free_list = next;
|
||||
if (__builtin_expect(mode != 2, 1)) {
|
||||
atomic_store_explicit(&page->meta->freelist, next, memory_order_release);
|
||||
}
|
||||
} else if (page->used < page->capacity) {
|
||||
size_t stride = hcls->stride;
|
||||
if (stride == 0) {
|
||||
@ -438,7 +760,7 @@ static inline void* tiny_heap_page_pop(tiny_heap_class_t* hcls, int class_idx, t
|
||||
hcls->stride = (uint16_t)stride;
|
||||
}
|
||||
block = (void*)(page->base + ((size_t)page->used * stride));
|
||||
if (page->meta->carved < page->capacity) {
|
||||
if (__builtin_expect(mode != 2, 1) && page->meta->carved < page->capacity) {
|
||||
page->meta->carved++;
|
||||
}
|
||||
} else {
|
||||
@ -446,11 +768,20 @@ static inline void* tiny_heap_page_pop(tiny_heap_class_t* hcls, int class_idx, t
|
||||
}
|
||||
|
||||
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);
|
||||
if (__builtin_expect(mode == 2, 0)) {
|
||||
return tiny_region_id_write_header(block, class_idx);
|
||||
}
|
||||
if (__builtin_expect(mode == 1, 0)) {
|
||||
if (class_idx == 6) {
|
||||
tiny_c6_mark_delta_site(page, C6_DELTA_ALLOC);
|
||||
}
|
||||
page->used_delta++;
|
||||
page->active_delta++;
|
||||
if (tiny_heap_delta_should_flush(class_idx, page)) {
|
||||
if (class_idx == 6) {
|
||||
tiny_c6_mark_delta_site(page, C6_DELTA_THRESHOLD);
|
||||
}
|
||||
tiny_heap_meta_flush_page(class_idx, page);
|
||||
}
|
||||
return tiny_region_id_write_header(block, class_idx);
|
||||
}
|
||||
@ -462,10 +793,13 @@ static inline void* tiny_heap_page_pop(tiny_heap_class_t* hcls, int class_idx, t
|
||||
|
||||
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;
|
||||
const int ultra = tiny_heap_meta_ultra_enabled_for_class(class_idx);
|
||||
tiny_next_write(class_idx, base_ptr, page->free_list);
|
||||
page->free_list = base_ptr;
|
||||
if (!__builtin_expect(ultra, 0)) {
|
||||
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,
|
||||
@ -473,25 +807,131 @@ static inline void tiny_heap_page_free_local(tiny_heap_ctx_t* ctx,
|
||||
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);
|
||||
if (!tiny_heap_page_is_valid(hcls, page)) return;
|
||||
TinyHeapClassStats* stats = tiny_heap_stats_for_class(class_idx);
|
||||
if (__builtin_expect(stats != NULL, 0)) {
|
||||
atomic_fetch_add_explicit(&stats->free_fast_local, 1, memory_order_relaxed);
|
||||
}
|
||||
const int behavior_mode = tiny_heap_behavior_mode_for_class(class_idx);
|
||||
const int mode = tiny_heap_meta_mode_effective(class_idx);
|
||||
const int page_stats_on = (class_idx == 7 && tiny_c7_page_stats_enabled());
|
||||
if (class_idx == 6 && __builtin_expect(mode == 1, 0) && tiny_c6_debug_pop_enabled()) {
|
||||
SuperSlab* ss_chk = hak_super_lookup(base_ptr);
|
||||
if (!ss_chk || ss_chk != page->ss || !page->meta || page->meta->class_idx != class_idx) {
|
||||
fprintf(stderr,
|
||||
"[C6_FREE_FAIL] ss_chk=%p page_ss=%p meta=%p meta_cls=%d base=%p page=%p used=%u cap=%u last_site=%d\n",
|
||||
(void*)ss_chk,
|
||||
(void*)page->ss,
|
||||
(void*)page->meta,
|
||||
page->meta ? page->meta->class_idx : -1,
|
||||
base_ptr,
|
||||
(void*)page,
|
||||
(unsigned)page->used,
|
||||
(unsigned)page->capacity,
|
||||
page->last_delta_site);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
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)) {
|
||||
if (__builtin_expect(mode == 2, 0)) {
|
||||
// ULTRA: meta/active は触らない
|
||||
} else if (!__builtin_expect(mode == 1, 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 (class_idx == 6) {
|
||||
tiny_c6_mark_delta_site(page, C6_DELTA_FREE);
|
||||
}
|
||||
page->used_delta--;
|
||||
page->active_delta--;
|
||||
if (tiny_heap_delta_should_flush(class_idx, page)) {
|
||||
if (class_idx == 6) {
|
||||
tiny_c6_mark_delta_site(page, C6_DELTA_THRESHOLD);
|
||||
}
|
||||
tiny_heap_meta_flush_page(class_idx, page);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (class_idx == 6 && __builtin_expect(mode == 1, 0) && tiny_c6_debug_pop_enabled()) {
|
||||
static _Atomic uint32_t g_c6_free_dbg = 0;
|
||||
uint32_t n = atomic_fetch_add_explicit(&g_c6_free_dbg, 1, memory_order_relaxed);
|
||||
if (n < 512) {
|
||||
fprintf(stderr,
|
||||
"[C6_FREE_DEBUG] page=%p used=%u cap=%u free_list=%p meta=%p ss=%p base=%p used_delta=%d active_delta=%d\n",
|
||||
(void*)page,
|
||||
(unsigned)page->used,
|
||||
(unsigned)page->capacity,
|
||||
page->free_list,
|
||||
(void*)page->meta,
|
||||
(void*)page->ss,
|
||||
(void*)page->base,
|
||||
page->used_delta,
|
||||
page->active_delta);
|
||||
}
|
||||
}
|
||||
|
||||
if (class_idx == 7 && __builtin_expect(behavior_mode == 2, 0)) {
|
||||
tiny_heap_page_t* old_cur = hcls->current_page;
|
||||
if (__builtin_expect(page_stats_on, 0)) {
|
||||
if (!old_cur) {
|
||||
atomic_fetch_add_explicit(&g_c7_page_stats.current_set_from_free, 1, memory_order_relaxed);
|
||||
} else if (old_cur != page) {
|
||||
atomic_fetch_add_explicit(&g_c7_page_stats.current_dropped_to_partial, 1, memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
// ULTRA: 1ページで回す前提。current を強制保持し、partial/full には追い出さない。
|
||||
hcls->current_page = page;
|
||||
if (old_cur && old_cur != page) {
|
||||
tiny_heap_page_push_to_partial(hcls, old_cur);
|
||||
}
|
||||
// partial list に page が重複して残っている場合を避ける
|
||||
tiny_heap_page_t** pp = &hcls->partial_pages;
|
||||
while (*pp) {
|
||||
if (*pp == page) {
|
||||
*pp = (*pp)->next;
|
||||
break;
|
||||
}
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (__builtin_expect(behavior_mode == 1, 0)) {
|
||||
if (page->used == 0) {
|
||||
tiny_heap_page_becomes_empty(ctx, class_idx, page);
|
||||
return;
|
||||
}
|
||||
if (page->used < page->capacity && page->free_list) {
|
||||
tiny_heap_page_t* old_cur = hcls->current_page;
|
||||
if (class_idx == 7 && __builtin_expect(page_stats_on, 0)) {
|
||||
if (!old_cur) {
|
||||
atomic_fetch_add_explicit(&g_c7_page_stats.current_set_from_free, 1, memory_order_relaxed);
|
||||
} else if (old_cur != page) {
|
||||
atomic_fetch_add_explicit(&g_c7_page_stats.current_dropped_to_partial, 1, memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
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 (class_idx == 6) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (page->used == 0) {
|
||||
tiny_heap_page_becomes_empty(ctx, class_idx, page);
|
||||
@ -500,6 +940,13 @@ static inline void tiny_heap_page_free_local(tiny_heap_ctx_t* ctx,
|
||||
tiny_heap_class_unlink(hcls, page);
|
||||
page->next = NULL;
|
||||
if (class_idx == 7) {
|
||||
if (__builtin_expect(page_stats_on, 0)) {
|
||||
if (!old_cur) {
|
||||
atomic_fetch_add_explicit(&g_c7_page_stats.current_set_from_free, 1, memory_order_relaxed);
|
||||
} else if (old_cur != page) {
|
||||
atomic_fetch_add_explicit(&g_c7_page_stats.current_dropped_to_partial, 1, memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
hcls->current_page = page;
|
||||
if (old_cur && old_cur != page) {
|
||||
tiny_heap_page_push_to_partial(hcls, old_cur);
|
||||
@ -516,30 +963,53 @@ static inline void tiny_heap_page_free_local(tiny_heap_ctx_t* ctx,
|
||||
|
||||
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());
|
||||
TinyHeapClassStats* stats = tiny_heap_stats_for_class(class_idx);
|
||||
const int page_stats_on = (class_idx == 7 && tiny_c7_page_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);
|
||||
if (__builtin_expect(stats != NULL, 0)) {
|
||||
atomic_fetch_add_explicit(&stats->alloc_prepare_fail, 1, memory_order_relaxed);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (__builtin_expect(page_stats_on, 0)) {
|
||||
atomic_fetch_add_explicit(&g_c7_page_stats.prepare_calls, 1, memory_order_relaxed);
|
||||
if (!hcls->current_page) {
|
||||
atomic_fetch_add_explicit(&g_c7_page_stats.prepare_with_current_null, 1, memory_order_relaxed);
|
||||
if (hcls->partial_pages) {
|
||||
atomic_fetch_add_explicit(&g_c7_page_stats.prepare_from_partial, 1, memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tiny_heap_meta_ultra_enabled_for_class(class_idx)) {
|
||||
if (hcls->current_page) {
|
||||
return hcls->current_page;
|
||||
}
|
||||
}
|
||||
if (tiny_heap_meta_light_enabled_for_class(class_idx)) {
|
||||
if (hcls->current_page &&
|
||||
(hcls->current_page->free_list || hcls->current_page->used < hcls->current_page->capacity)) {
|
||||
return hcls->current_page;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (__builtin_expect(stats != NULL, 0)) {
|
||||
atomic_fetch_add_explicit(&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);
|
||||
if (__builtin_expect(stats != NULL, 0)) {
|
||||
atomic_fetch_add_explicit(&stats->alloc_prepare_fail, 1, memory_order_relaxed);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@ -552,16 +1022,46 @@ static inline tiny_heap_page_t* tiny_heap_prepare_page(tiny_heap_ctx_t* ctx, int
|
||||
return page;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// C7 HotHeap v2 page lease helpers (Phase 32)
|
||||
// =============================================================================
|
||||
static inline TinyHeapPageLease tiny_heap_page_lease_nil(void) {
|
||||
TinyHeapPageLease lease;
|
||||
memset(&lease, 0, sizeof(lease));
|
||||
return lease;
|
||||
}
|
||||
|
||||
static inline TinyHeapPageLease tiny_heap_c7_lease_page_for_v2(void) {
|
||||
TinyHeapPageLease lease = tiny_heap_page_lease_nil();
|
||||
tiny_heap_ctx_t* ctx = tiny_heap_ctx_for_thread();
|
||||
if (!ctx) return lease;
|
||||
tiny_heap_page_t* page = tiny_heap_prepare_page(ctx, 7);
|
||||
if (!page) return lease;
|
||||
lease.page = page;
|
||||
lease.meta = page->meta;
|
||||
lease.ss = page->ss;
|
||||
lease.base = page->base;
|
||||
lease.capacity = page->capacity;
|
||||
lease.slab_idx = page->slab_idx;
|
||||
lease.freelist = page->free_list;
|
||||
return lease;
|
||||
}
|
||||
|
||||
static inline void tiny_heap_c7_return_page_from_v2(TinyHeapPageLease* lease) {
|
||||
(void)lease;
|
||||
// Phase32: C7 v2 は 1 枚使い切り前提。返却処理はまだ持たない。
|
||||
}
|
||||
|
||||
// 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());
|
||||
TinyHeapClassStats* stats = tiny_heap_stats_for_class(class_idx);
|
||||
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);
|
||||
if (__builtin_expect(stats != NULL, 0)) {
|
||||
atomic_fetch_add_explicit(&stats->alloc_fail, 1, memory_order_relaxed);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@ -580,7 +1080,7 @@ __attribute__((always_inline)) static inline void* tiny_heap_alloc_class_fast(ti
|
||||
(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());
|
||||
TinyHeapClassStats* stats = tiny_heap_stats_for_class(class_idx);
|
||||
|
||||
if (page) {
|
||||
if (page->free_list || page->used < page->capacity) {
|
||||
@ -589,16 +1089,16 @@ __attribute__((always_inline)) static inline void* tiny_heap_alloc_class_fast(ti
|
||||
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);
|
||||
if (__builtin_expect(stats != NULL, 0)) {
|
||||
atomic_fetch_add_explicit(&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);
|
||||
if (__builtin_expect(stats != NULL, 0)) {
|
||||
atomic_fetch_add_explicit(&stats->alloc_slow_prepare, 1, memory_order_relaxed);
|
||||
}
|
||||
return tiny_heap_alloc_slow_from_class(ctx, class_idx);
|
||||
}
|
||||
@ -611,12 +1111,12 @@ static inline void tiny_heap_free_class_fast_with_meta(tiny_heap_ctx_t* ctx,
|
||||
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());
|
||||
TinyHeapClassStats* stats = tiny_heap_stats_for_class(class_idx);
|
||||
|
||||
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);
|
||||
if (__builtin_expect(stats != NULL, 0)) {
|
||||
atomic_fetch_add_explicit(&stats->free_slow_fallback, 1, memory_order_relaxed);
|
||||
}
|
||||
tiny_heap_cold_drain_and_free(class_idx, base);
|
||||
return;
|
||||
@ -636,16 +1136,18 @@ static inline void tiny_heap_free_class_fast(tiny_heap_ctx_t* ctx, int class_idx
|
||||
#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);
|
||||
TinyHeapClassStats* stats = tiny_heap_stats_for_class(class_idx);
|
||||
if (__builtin_expect(stats != NULL, 0)) {
|
||||
atomic_fetch_add_explicit(&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);
|
||||
TinyHeapClassStats* stats = tiny_heap_stats_for_class(class_idx);
|
||||
if (__builtin_expect(stats != NULL, 0)) {
|
||||
atomic_fetch_add_explicit(&stats->free_slow_fallback, 1, memory_order_relaxed);
|
||||
}
|
||||
tiny_heap_cold_drain_and_free(class_idx, base);
|
||||
return;
|
||||
|
||||
63
core/box/tiny_hotheap_v2_box.h
Normal file
63
core/box/tiny_hotheap_v2_box.h
Normal file
@ -0,0 +1,63 @@
|
||||
// tiny_hotheap_v2_box.h - TinyHotHeap v2 skeleton (Step1: types & stubs only)
|
||||
// 役割:
|
||||
// - TinyHotHeap v2 の型と API を先行定義するが、現行経路には配線しない。
|
||||
// - HAKMEM_TINY_HOTHEAP_V2 / HAKMEM_TINY_HOTHEAP_CLASSES で将来の A/B に備える。
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef TINY_HOTHEAP_MAX_CLASSES
|
||||
#define TINY_HOTHEAP_MAX_CLASSES 8 // とりあえず全クラス分確保(後で mask で限定)
|
||||
#endif
|
||||
|
||||
struct TinySlabMeta;
|
||||
struct SuperSlab;
|
||||
struct tiny_heap_page_t; // from tiny_heap_box.h (v1 page型)
|
||||
|
||||
typedef struct tiny_hotheap_page_v2 {
|
||||
void* freelist;
|
||||
uint16_t used;
|
||||
uint16_t capacity;
|
||||
uint16_t slab_idx;
|
||||
uint8_t _pad;
|
||||
void* base;
|
||||
struct TinySlabMeta* meta; // Superslab slab meta
|
||||
struct SuperSlab* ss; // Owning Superslab (meta/ss_active の整合は v1 が保持)
|
||||
struct tiny_heap_page_t* lease_page; // v1 側の page 構造体(freelist/used の正は常に lease_page)
|
||||
struct tiny_hotheap_page_v2* next;
|
||||
} tiny_hotheap_page_v2;
|
||||
|
||||
typedef struct tiny_hotheap_class_v2 {
|
||||
tiny_hotheap_page_v2* current_page;
|
||||
tiny_hotheap_page_v2* partial_pages;
|
||||
tiny_hotheap_page_v2* full_pages;
|
||||
uint16_t stride;
|
||||
uint16_t _pad;
|
||||
tiny_hotheap_page_v2 storage_page; // C7 専用の 1 枚だけをまず保持
|
||||
} tiny_hotheap_class_v2;
|
||||
|
||||
typedef struct tiny_hotheap_ctx_v2 {
|
||||
tiny_hotheap_class_v2 cls[TINY_HOTHEAP_MAX_CLASSES];
|
||||
} tiny_hotheap_ctx_v2;
|
||||
|
||||
// TLS state (定義は core/hakmem_tiny.c)
|
||||
extern __thread tiny_hotheap_ctx_v2* g_tiny_hotheap_ctx_v2;
|
||||
|
||||
// API (Step1: まだ中身はダミー)
|
||||
tiny_hotheap_ctx_v2* tiny_hotheap_v2_tls_get(void);
|
||||
void* tiny_hotheap_v2_alloc(uint8_t class_idx);
|
||||
void tiny_hotheap_v2_free(uint8_t class_idx, void* p, void* meta);
|
||||
|
||||
static inline void tiny_hotheap_v2_page_reset(tiny_hotheap_page_v2* page) {
|
||||
if (!page) return;
|
||||
page->freelist = NULL;
|
||||
page->used = 0;
|
||||
page->capacity = 0;
|
||||
page->slab_idx = 0;
|
||||
page->base = NULL;
|
||||
page->meta = NULL;
|
||||
page->ss = NULL;
|
||||
page->lease_page = NULL;
|
||||
page->next = NULL;
|
||||
}
|
||||
@ -23,11 +23,14 @@
|
||||
#include "tiny_debug_ring.h"
|
||||
#include "tiny_route.h"
|
||||
#include "front/tiny_heap_v2.h"
|
||||
#include "box/tiny_front_stats_box.h"
|
||||
#include "tiny_tls_guard.h"
|
||||
#include "tiny_ready.h"
|
||||
#include "box/c7_meta_used_counter_box.h"
|
||||
#include "box/tiny_c7_hotbox.h"
|
||||
#include "box/tiny_heap_box.h"
|
||||
#include "box/tiny_hotheap_v2_box.h"
|
||||
#include "box/tiny_route_env_box.h"
|
||||
#include "box/super_reg_box.h"
|
||||
#include "hakmem_tiny_tls_list.h"
|
||||
#include "hakmem_tiny_remote_target.h" // Phase 2C-1: Remote target queue
|
||||
@ -49,47 +52,240 @@ extern uint64_t g_bytes_allocated; // from hakmem_tiny_superslab.c
|
||||
__thread hak_base_ptr_t s_tls_sll_last_push[TINY_NUM_CLASSES] = {0};
|
||||
__thread tiny_heap_ctx_t g_tiny_heap_ctx;
|
||||
__thread int g_tiny_heap_ctx_init = 0;
|
||||
TinyC7HeapStats g_c7_heap_stats = {0};
|
||||
__thread tiny_hotheap_ctx_v2* g_tiny_hotheap_ctx_v2 = NULL;
|
||||
TinyHeapClassStats g_tiny_heap_stats[TINY_NUM_CLASSES] = {0};
|
||||
TinyC7PageStats g_c7_page_stats = {0};
|
||||
tiny_route_kind_t g_tiny_route_class[TINY_NUM_CLASSES] = {0};
|
||||
int g_tiny_route_snapshot_done = 0;
|
||||
_Atomic uint64_t g_tiny_front_alloc_class[TINY_NUM_CLASSES] = {0};
|
||||
_Atomic uint64_t g_tiny_front_free_class[TINY_NUM_CLASSES] = {0};
|
||||
|
||||
static int tiny_c7_heap_stats_dump_enabled(void) {
|
||||
static int tiny_heap_stats_dump_enabled(void) {
|
||||
static int g = -1;
|
||||
if (__builtin_expect(g == -1, 0)) {
|
||||
const char* eh = getenv("HAKMEM_TINY_HEAP_STATS_DUMP");
|
||||
const char* e = getenv("HAKMEM_TINY_C7_HEAP_STATS_DUMP");
|
||||
g = (e && *e && *e != '0') ? 1 : 0;
|
||||
g = ((eh && *eh && *eh != '0') || (e && *e && *e != '0')) ? 1 : 0;
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
__attribute__((destructor))
|
||||
static void tiny_c7_heap_stats_dump(void) {
|
||||
if (!tiny_c7_heap_stats_enabled() || !tiny_c7_heap_stats_dump_enabled()) {
|
||||
static void tiny_heap_stats_dump(void) {
|
||||
if (!tiny_heap_stats_enabled() || !tiny_heap_stats_dump_enabled()) {
|
||||
return;
|
||||
}
|
||||
TinyC7HeapStats snap = {
|
||||
.alloc_fast_current = atomic_load_explicit(&g_c7_heap_stats.alloc_fast_current, memory_order_relaxed),
|
||||
.alloc_slow_prepare = atomic_load_explicit(&g_c7_heap_stats.alloc_slow_prepare, memory_order_relaxed),
|
||||
.free_fast_local = atomic_load_explicit(&g_c7_heap_stats.free_fast_local, memory_order_relaxed),
|
||||
.free_slow_fallback = atomic_load_explicit(&g_c7_heap_stats.free_slow_fallback, memory_order_relaxed),
|
||||
.alloc_prepare_fail = atomic_load_explicit(&g_c7_heap_stats.alloc_prepare_fail, memory_order_relaxed),
|
||||
.alloc_fail = atomic_load_explicit(&g_c7_heap_stats.alloc_fail, memory_order_relaxed),
|
||||
for (int cls = 0; cls < TINY_NUM_CLASSES; cls++) {
|
||||
TinyHeapClassStats snap = {
|
||||
.alloc_fast_current = atomic_load_explicit(&g_tiny_heap_stats[cls].alloc_fast_current, memory_order_relaxed),
|
||||
.alloc_slow_prepare = atomic_load_explicit(&g_tiny_heap_stats[cls].alloc_slow_prepare, memory_order_relaxed),
|
||||
.free_fast_local = atomic_load_explicit(&g_tiny_heap_stats[cls].free_fast_local, memory_order_relaxed),
|
||||
.free_slow_fallback = atomic_load_explicit(&g_tiny_heap_stats[cls].free_slow_fallback, memory_order_relaxed),
|
||||
.alloc_prepare_fail = atomic_load_explicit(&g_tiny_heap_stats[cls].alloc_prepare_fail, memory_order_relaxed),
|
||||
.alloc_fail = atomic_load_explicit(&g_tiny_heap_stats[cls].alloc_fail, memory_order_relaxed),
|
||||
};
|
||||
if (snap.alloc_fast_current == 0 && snap.alloc_slow_prepare == 0 &&
|
||||
snap.free_fast_local == 0 && snap.free_slow_fallback == 0 &&
|
||||
snap.alloc_prepare_fail == 0 && snap.alloc_fail == 0) {
|
||||
continue;
|
||||
}
|
||||
fprintf(stderr,
|
||||
"[C7_HEAP_STATS] alloc_fast_current=%llu alloc_slow_prepare=%llu free_fast_local=%llu free_slow_fallback=%llu alloc_prepare_fail=%llu alloc_fail=%llu\n",
|
||||
"[HEAP_STATS cls=%d] alloc_fast_current=%llu alloc_slow_prepare=%llu free_fast_local=%llu free_slow_fallback=%llu alloc_prepare_fail=%llu alloc_fail=%llu\n",
|
||||
cls,
|
||||
(unsigned long long)snap.alloc_fast_current,
|
||||
(unsigned long long)snap.alloc_slow_prepare,
|
||||
(unsigned long long)snap.free_fast_local,
|
||||
(unsigned long long)snap.free_slow_fallback,
|
||||
(unsigned long long)snap.alloc_prepare_fail,
|
||||
(unsigned long long)snap.alloc_fail);
|
||||
}
|
||||
TinyC7PageStats ps = {
|
||||
.prepare_calls = atomic_load_explicit(&g_c7_page_stats.prepare_calls, memory_order_relaxed),
|
||||
.prepare_with_current_null = atomic_load_explicit(&g_c7_page_stats.prepare_with_current_null, memory_order_relaxed),
|
||||
.prepare_from_partial = atomic_load_explicit(&g_c7_page_stats.prepare_from_partial, memory_order_relaxed),
|
||||
.current_set_from_free = atomic_load_explicit(&g_c7_page_stats.current_set_from_free, memory_order_relaxed),
|
||||
.current_dropped_to_partial = atomic_load_explicit(&g_c7_page_stats.current_dropped_to_partial, memory_order_relaxed),
|
||||
};
|
||||
if (ps.prepare_calls || ps.prepare_with_current_null || ps.prepare_from_partial ||
|
||||
ps.current_set_from_free || ps.current_dropped_to_partial) {
|
||||
fprintf(stderr,
|
||||
"[C7_PAGE_STATS] prepare_calls=%llu prepare_with_current_null=%llu prepare_from_partial=%llu current_set_from_free=%llu current_dropped_to_partial=%llu\n",
|
||||
(unsigned long long)ps.prepare_calls,
|
||||
(unsigned long long)ps.prepare_with_current_null,
|
||||
(unsigned long long)ps.prepare_from_partial,
|
||||
(unsigned long long)ps.current_set_from_free,
|
||||
(unsigned long long)ps.current_dropped_to_partial);
|
||||
fflush(stderr);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((destructor))
|
||||
static void tiny_front_class_stats_dump(void) {
|
||||
if (!tiny_front_class_stats_dump_enabled()) {
|
||||
return;
|
||||
}
|
||||
for (int cls = 0; cls < TINY_NUM_CLASSES; cls++) {
|
||||
uint64_t a = atomic_load_explicit(&g_tiny_front_alloc_class[cls], memory_order_relaxed);
|
||||
uint64_t f = atomic_load_explicit(&g_tiny_front_free_class[cls], memory_order_relaxed);
|
||||
if (a == 0 && f == 0) {
|
||||
continue;
|
||||
}
|
||||
fprintf(stderr, "[FRONT_CLASS cls=%d] alloc=%llu free=%llu\n",
|
||||
cls, (unsigned long long)a, (unsigned long long)f);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((destructor))
|
||||
static void tiny_c7_delta_debug_destructor(void) {
|
||||
if (!tiny_c7_meta_light_enabled() || !tiny_c7_delta_debug_enabled()) {
|
||||
if (tiny_c7_meta_light_enabled() && tiny_c7_delta_debug_enabled()) {
|
||||
tiny_c7_heap_debug_dump_deltas();
|
||||
}
|
||||
if (tiny_heap_meta_light_enabled_for_class(6) && tiny_c6_delta_debug_enabled()) {
|
||||
tiny_c6_heap_debug_dump_deltas();
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TinyHotHeap v2 (Phase30/31 wiring). Currently C7-only thin wrapper.
|
||||
// =============================================================================
|
||||
static _Atomic uint64_t g_tiny_hotheap_v2_c7_alloc = 0;
|
||||
static _Atomic uint64_t g_tiny_hotheap_v2_c7_free = 0;
|
||||
static _Atomic uint64_t g_tiny_hotheap_v2_c7_alloc_fallback = 0;
|
||||
static _Atomic uint64_t g_tiny_hotheap_v2_c7_free_fallback = 0;
|
||||
tiny_hotheap_ctx_v2* tiny_hotheap_v2_tls_get(void) {
|
||||
tiny_hotheap_ctx_v2* ctx = g_tiny_hotheap_ctx_v2;
|
||||
if (__builtin_expect(ctx == NULL, 0)) {
|
||||
ctx = (tiny_hotheap_ctx_v2*)calloc(1, sizeof(tiny_hotheap_ctx_v2));
|
||||
if (__builtin_expect(ctx == NULL, 0)) {
|
||||
fprintf(stderr, "[TinyHotHeapV2] TLS alloc failed (OOM)\n");
|
||||
abort();
|
||||
}
|
||||
g_tiny_hotheap_ctx_v2 = ctx;
|
||||
// C7 用 stride を最初にだけ設定(他クラスは未使用のまま)
|
||||
ctx->cls[7].stride = (uint16_t)tiny_stride_for_class(7);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void* tiny_hotheap_v2_alloc(uint8_t class_idx) {
|
||||
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc, 1, memory_order_relaxed);
|
||||
if (__builtin_expect(class_idx != 7, 0)) {
|
||||
return NULL; // いまは C7 専用
|
||||
}
|
||||
|
||||
tiny_hotheap_ctx_v2* v2ctx = tiny_hotheap_v2_tls_get();
|
||||
tiny_hotheap_class_v2* vhcls = &v2ctx->cls[7];
|
||||
tiny_hotheap_page_v2* v2page = vhcls ? vhcls->current_page : NULL;
|
||||
if (!v2page && vhcls) {
|
||||
v2page = &vhcls->storage_page;
|
||||
tiny_hotheap_v2_page_reset(v2page);
|
||||
vhcls->current_page = v2page;
|
||||
}
|
||||
|
||||
tiny_heap_ctx_t* v1ctx = tiny_heap_ctx_for_thread();
|
||||
tiny_heap_class_t* v1hcls = tiny_heap_class(v1ctx, 7);
|
||||
TinyHeapClassStats* stats = tiny_heap_stats_for_class(7);
|
||||
|
||||
// Hot path: current_page の lease をそのまま使う
|
||||
if (v2page && v2page->lease_page && v1hcls) {
|
||||
tiny_heap_page_t* ipage = v2page->lease_page;
|
||||
if (ipage->free_list || ipage->used < ipage->capacity) {
|
||||
void* user = tiny_heap_page_pop(v1hcls, 7, ipage);
|
||||
if (user) {
|
||||
if (ipage->used >= ipage->capacity && ipage->free_list == NULL) {
|
||||
tiny_heap_page_mark_full(v1hcls, ipage);
|
||||
}
|
||||
if (__builtin_expect(stats != NULL, 0)) {
|
||||
atomic_fetch_add_explicit(&stats->alloc_fast_current, 1, memory_order_relaxed);
|
||||
}
|
||||
v2page->freelist = ipage->free_list;
|
||||
v2page->used = ipage->used;
|
||||
return user;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (__builtin_expect(stats != NULL, 0)) {
|
||||
atomic_fetch_add_explicit(&stats->alloc_slow_prepare, 1, memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Lease a page from v1 (C7 SAFE) and wrap it
|
||||
TinyHeapPageLease lease = tiny_heap_c7_lease_page_for_v2();
|
||||
if (!lease.page || !vhcls || !v1hcls) {
|
||||
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_fallback, 1, memory_order_relaxed);
|
||||
size_t size = vhcls ? (vhcls->stride ? vhcls->stride : tiny_stride_for_class(7)) : tiny_stride_for_class(7);
|
||||
return tiny_c7_alloc_fast(size); // safety fallback to v1
|
||||
}
|
||||
|
||||
if (!v2page) {
|
||||
v2page = &vhcls->storage_page;
|
||||
tiny_hotheap_v2_page_reset(v2page);
|
||||
vhcls->current_page = v2page;
|
||||
}
|
||||
|
||||
v2page->lease_page = lease.page;
|
||||
v2page->meta = lease.meta;
|
||||
v2page->ss = lease.ss;
|
||||
v2page->base = lease.base;
|
||||
v2page->capacity = lease.capacity;
|
||||
v2page->slab_idx = lease.slab_idx;
|
||||
v2page->freelist = lease.freelist;
|
||||
v2page->used = lease.page->used;
|
||||
|
||||
if (lease.page->free_list || lease.page->used < lease.page->capacity) {
|
||||
void* user = tiny_heap_page_pop(v1hcls, 7, lease.page);
|
||||
if (user) {
|
||||
if (lease.page->used >= lease.page->capacity && lease.page->free_list == NULL) {
|
||||
tiny_heap_page_mark_full(v1hcls, lease.page);
|
||||
}
|
||||
if (__builtin_expect(stats != NULL, 0)) {
|
||||
atomic_fetch_add_explicit(&stats->alloc_fast_current, 1, memory_order_relaxed);
|
||||
}
|
||||
v2page->freelist = lease.page->free_list;
|
||||
v2page->used = lease.page->used;
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
// Lease 取得後でも pop できなければ v1 に委譲
|
||||
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_fallback, 1, memory_order_relaxed);
|
||||
size_t size = vhcls ? (vhcls->stride ? vhcls->stride : tiny_stride_for_class(7)) : tiny_stride_for_class(7);
|
||||
return tiny_c7_alloc_fast(size);
|
||||
}
|
||||
|
||||
void tiny_hotheap_v2_free(uint8_t class_idx, void* p, void* meta) {
|
||||
if (__builtin_expect(class_idx != 7, 0)) {
|
||||
return;
|
||||
}
|
||||
tiny_c7_heap_debug_dump_deltas();
|
||||
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_free, 1, memory_order_relaxed);
|
||||
tiny_hotheap_ctx_v2* v2ctx = tiny_hotheap_v2_tls_get();
|
||||
tiny_hotheap_class_v2* vhcls = v2ctx ? &v2ctx->cls[7] : NULL;
|
||||
tiny_hotheap_page_v2* v2page = vhcls ? vhcls->current_page : NULL;
|
||||
TinySlabMeta* meta_ptr = (TinySlabMeta*)meta;
|
||||
|
||||
tiny_heap_ctx_t* v1ctx = tiny_heap_ctx_for_thread();
|
||||
tiny_heap_class_t* v1hcls = tiny_heap_class(v1ctx, 7);
|
||||
|
||||
if (v2page && v2page->lease_page && meta_ptr && v1hcls &&
|
||||
v2page->meta == meta_ptr && tiny_heap_ptr_in_page_range(v2page->lease_page, p)) {
|
||||
tiny_heap_page_free_local(v1ctx, 7, v2page->lease_page, p);
|
||||
v2page->freelist = v2page->lease_page->free_list;
|
||||
v2page->used = v2page->lease_page->used;
|
||||
vhcls->current_page = v2page; // keep pinned
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: mimic v1 free path
|
||||
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_free_fallback, 1, memory_order_relaxed);
|
||||
SuperSlab* ss = hak_super_lookup(p);
|
||||
if (ss && ss->magic == SUPERSLAB_MAGIC) {
|
||||
int slab_idx = slab_index_for(ss, p);
|
||||
if (slab_idx >= 0 && slab_idx < ss_slabs_capacity(ss)) {
|
||||
tiny_c7_free_fast_with_meta(ss, slab_idx, p);
|
||||
return;
|
||||
}
|
||||
}
|
||||
tiny_c7_free_fast(p);
|
||||
}
|
||||
|
||||
#if !HAKMEM_BUILD_RELEASE
|
||||
|
||||
95
docs/analysis/TINY_HEAP_V2_DESIGN.md
Normal file
95
docs/analysis/TINY_HEAP_V2_DESIGN.md
Normal file
@ -0,0 +1,95 @@
|
||||
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 と同等を維持するのが目的。
|
||||
|
||||
ゴール
|
||||
-----
|
||||
- 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 では C5–C7 を最初から設計し直す前提で扱う。
|
||||
|
||||
方針候補(箇条書き)
|
||||
-------------------
|
||||
- C5–C7 を 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 スコープの固定)
|
||||
-------------------------------------
|
||||
- ゴール:
|
||||
- C5–C7 を 1 つの TinyHotHeap(HotHeap v2)に統合し、heap→page→block のみに集中するホット層を構築する。
|
||||
- C7-only / Mixed で mimalloc に 1.5〜2× 近づける “理論上” の構造(フロントと Cold 更新の命令数を半減させる)。
|
||||
- Hot 層は current 固定+delta/Stats Box バッチを前提とし、学習層は Snapshot Box に閉じ込めてホットパスから排除する。
|
||||
- 非ゴール:
|
||||
- v2 では C0–C4 や巨大サイズ帯は触らない(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=C5–C7 へ)。
|
||||
- A/B 方針:
|
||||
- v1 TinyHeap(C7 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 型と API(hot_alloc/free/refill)を追加し、コンパイルパスだけ通す(まだ未使用)。
|
||||
2. Step2: C7-only を v2 に載せる A/B(HOTHEAP_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 API(alloc/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/B(Release, HEAP_STATS=ON): C7-only 43.28M(v2 ON/OFF 差なし)、Mixed 16–1024B は LEGACY 42.18M / C7_SAFE v2 OFF 41.15M / v2 ON 40.74M(cls7 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_page+freelist の多ページ対応・ページ返却まで拡張し、Superslab への触れ方を v2 専用に寄せる(v1 はページ供給だけの Box にする)。
|
||||
- B) 当面 v2 は C7 限定のラッパ+単一ページ管理のまま据え置き、mid サイズや他クラスの箱に時間を回す。
|
||||
Reference in New Issue
Block a user