From 8f18963ad5ac01192076cb333e30e73b52c063cf Mon Sep 17 00:00:00 2001 From: "Moe Charm (CI)" Date: Mon, 8 Dec 2025 21:30:21 +0900 Subject: [PATCH] Phase 36-37: TinyHotHeap v2 HotBox redesign and C7 current_page policy fixes - Redefine TinyHotHeap v2 as per-thread Hot Box with clear boundaries - Add comprehensive OS statistics tracking for SS allocations - Implement route-based free handling for TinyHeap v2 - Add C6/C7 debugging and statistics improvements - Update documentation with implementation guidelines and analysis - Add new box headers for stats, routing, and front-end management --- CURRENT_TASK.md | 105 ++++ core/box/pool_api.inc.h | 456 +++++++++++++++- core/box/ss_allocation_box.c | 2 + core/box/ss_cache_box.c | 1 + core/box/ss_os_acquire_box.c | 86 ++- core/box/ss_os_acquire_box.h | 86 +++ core/box/tiny_front_stats_box.h | 42 ++ core/box/tiny_heap_box.h | 108 +++- core/box/tiny_heap_env_box.h | 123 ++++- core/box/tiny_hotheap_v2_box.h | 46 +- core/box/tiny_route_env_box.h | 56 ++ core/box/tiny_stats_box.h | 134 +++++ core/front/malloc_tiny_fast.h | 90 +++- core/hakmem_tiny.c | 498 +++++++++++++++--- core/hakmem_tiny_init.inc | 3 + core/superslab_cache.c | 8 + core/superslab_stats.c | 34 ++ core/tiny_region_id.h | 78 ++- docs/analysis/C6_HOTBOX_DESIGN.md | 60 +++ docs/analysis/C7_HOTBOX_DESIGN.md | 61 +++ docs/analysis/COLD_TINY_STATS_BOX_DESIGN.md | 74 +++ docs/analysis/FINAL_PERF_STATUS_2025XX.md | 31 ++ .../FIRST_TOUCH_PAGEFAULT_REDUCTION_PLAN.md | 51 ++ .../MID_LARGE_CPU_HOTPATH_ANALYSIS.md | 46 ++ .../PERF_ANALYSIS_MID_LARGE_PHASE53.md | 29 + ...ERF_ANALYSIS_TINY_FRONT_FLATTEN_PHASE42.md | 205 +++++++ docs/analysis/TINY_C6_HOTPATH_ANALYSIS.md | 18 + .../TINY_CPU_HOTPATH_USERLAND_ANALYSIS.md | 34 ++ docs/analysis/TINY_HEAP_BOX_DESIGN.md | 97 ++++ docs/analysis/TINY_HEAP_V2_DESIGN.md | 240 +++++++++ docs/analysis/TINY_NEXT_STEPS.md | 46 ++ .../TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md | 398 ++++++++++++++ hakmem.d | 26 +- perf.data.c6heavy | Bin 0 -> 52544 bytes perf.data.mid_u | Bin 0 -> 48792 bytes perf.data.mixed_u | Bin 0 -> 27148 bytes perf.data.pf | Bin 0 -> 35872 bytes 37 files changed, 3205 insertions(+), 167 deletions(-) create mode 100644 core/box/tiny_front_stats_box.h create mode 100644 core/box/tiny_route_env_box.h create mode 100644 core/box/tiny_stats_box.h create mode 100644 docs/analysis/C6_HOTBOX_DESIGN.md create mode 100644 docs/analysis/COLD_TINY_STATS_BOX_DESIGN.md create mode 100644 docs/analysis/FINAL_PERF_STATUS_2025XX.md create mode 100644 docs/analysis/FIRST_TOUCH_PAGEFAULT_REDUCTION_PLAN.md create mode 100644 docs/analysis/MID_LARGE_CPU_HOTPATH_ANALYSIS.md create mode 100644 docs/analysis/PERF_ANALYSIS_MID_LARGE_PHASE53.md create mode 100644 docs/analysis/PERF_ANALYSIS_TINY_FRONT_FLATTEN_PHASE42.md create mode 100644 docs/analysis/TINY_C6_HOTPATH_ANALYSIS.md create mode 100644 docs/analysis/TINY_CPU_HOTPATH_USERLAND_ANALYSIS.md create mode 100644 docs/analysis/TINY_NEXT_STEPS.md create mode 100644 docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md create mode 100644 perf.data.c6heavy create mode 100644 perf.data.mid_u create mode 100644 perf.data.mixed_u create mode 100644 perf.data.pf diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 1dc0aece..7df92813 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -1,5 +1,42 @@ ## HAKMEM 状況メモ (2025-12-05 更新 / C7 Warm/TLS Bind 反映) +### Phase 36: TinyHotHeap v2 を「Hot Box」として再定義(設計ドキュメント整備) +- 状況: HotHeap v2 は Phase35 まで「v1 TinyHeap/C7 SAFE の上に乗るラッパ」で、Mixed では構造的に勝てない状態だったため、**いったん棚上げ** の扱いになっていた。 +- 方針転換: `docs/analysis/TINY_HEAP_V2_DESIGN.md` に Phase36 セクションを追加し、TinyHeap v2 自体を per-thread Hot Box(TinyHotHeapBox v2)として再定義。Superslab/Tier/Remote/Stats/Learning はすべて外側の Cold Box に落とし、境界を + - alloc 側: `tiny_heap_refill_slow(th, ci)` + - free 側: `tiny_heap_page_retire_slow(th, page)` + の 1 箇所に集約する設計に切り替えた。 +- 設計内容: `TinyHeapCtx` / `TinyClassHeap` / `TinyPageMeta` による per-thread TinyHotHeap(C5〜C7)を Hot Box とし、C7-only → C6/C5 へ段階的に拡張する A 案を第一候補として整理。C7 超ホットレーン(B 案)、mimalloc 風 Segment+Page+Block へのフル寄せ(C 案)は将来の選択肢として文書化。 +- ENV/A/B: `HAKMEM_TINY_HOTHEAP_V2` / `HAKMEM_TINY_HOTHEAP_CLASSES` で v2 ON/OFF と対象クラスを切り替える方針を維持(デフォルトは依然 v2 OFF, v1 C7_SAFE)。Route Snapshot (`g_tiny_route_class[ci]`) で v1/v2/legacy を 1 LUT + 1 分岐で選択するイメージを明示。 +- 実装ステータス: 現時点では **設計とドキュメントのみ整備**。まだコードに TinyHotHeap v2 の新しい Hot Box 構造は反映していない(既存 v2 ラッパ実装もそのまま)。 +- 次のアクション窓口: + - 実装ガイド: `docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md`(本フェーズで骨子を追加、実装担当 AI/開発者向けの指示書)。 + - 詳細設計: `docs/analysis/TINY_HEAP_V2_DESIGN.md`(Phase36+ セクションに A/B/C 案と Box 構造を集約)。 + +### Phase 37: TinyHotHeap v2 C7 current_page ポリシー修正(スローパス多発の是正) +- ベンチ結果(Release, PROFILE=C7_SAFE): + - C7-only (ws=64, iters=20k): v2 OFF **40.09M ops/s** / v2 ON **25.57M ops/s**(`HEAP_STATS[7] fast=97 slow=32758` → ほぼ slow_prepare)。 + - Mixed 16–1024B (ws=256, iters=20k): v2 OFF **40.99M ops/s** / v2 ON **32.07M ops/s**(`fast=141 slow=16654`)。 +- 所感: v2 ON 時に `current_page` がほぼ活きず、C7-only/Mixed とも毎回 `slow_prepare` に落ちて大幅回帰している(current_page stats: `prepare_calls=slow_prepare` で current_null≒0)。現状では v2 を運用に使えないため、デフォルトは引き続き v2 OFF(C7_SAFE + HOTHEAP_V2=0)が安全。 +- 対応方針: `docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md` に Phase37 セクションを追加し、C7-only 向けに + - v2 専用 current_page デバッグ統計の追加(prepare_calls / prepare_with_current_null など) + - refill_slow で必ず `current_page` をセットするようにする + - free 側で current_page を維持・再利用するポリシーを導入 + - empty page の retire 条件を見直し(即返却せず partial として保持する実験を許容) + - v1 C7 SAFE/TinyHeapBox の current_page ポリシーを v2 に移植 + を実装タスクとして明示。 +- 判定基準: Phase37 完了の目安として、 + - C7-only で v2 OFF と v2 ON が ±5% 以内(できれば同等以上) + - `HEAP_STATS[7]` で `fast≈11015 slow≈1` に戻る + - v2 current_page stats で `prepare_with_current_null` が `prepare_calls` に対して ≪1% 程度 + を満たすことを目標とする。満たせない場合は引き続き v2 は研究用箱(デフォルト OFF)のままとする。 + +### Phase 33: C7 v2 HotHeap A/B(薄型化の足がかり) +- 条件: Release, HEAP_STATS=ON, C7 SAFE (`PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_LARSON_FIX=1`), v2 は C7 のみ (`HAKMEM_TINY_HOTHEAP_CLASSES=0x80`)。 +- C7-only (ws=64, iters=20k): v2 OFF **39.42M ops/s** / v2 ON **43.55M ops/s** (cls7 fast=11015 / slow=1 で一致、v2カウンタ増加)。→ v2 の current/freelist 自前化で C7-only はわずかにプラス。 +- Mixed 16–1024B (ws=256, iters=20k): v2 OFF **40.44M ops/s** / v2 ON **36.58M ops/s** (cls7 fast=5691 / slow=1 で一致、v2カウンタ増加)。→ Mixed では v2 のラップ/lease がまだオーバーヘッド。 +- 所感: C7-only では v2 を保ったまま次の薄型化に進めそう。Mixed での回帰は lease 判定や v1 呼び出し重複が疑わしいため、Phase34 で「余計な枝/ロード」の整理候補に入れる。 + ### 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 に委譲)。 @@ -505,3 +542,71 @@ 3. Phase 3: 必要に応じて C0〜C4 も段階的に TinyHeap 側へ移植し、TinyFront は薄いラッパ層に縮退させる。 - 方針メモ: - Box Theory は維持しつつ、「Hot TinyHeap(シンプル・高速)」と「Cold Superslab/SafetyBox(複雑・安全)」の二層構造に整理することで、mimalloc に近い性能と HAKMEM 固有の安全性・観測性・学習レイヤを両立させる方向性と認識。 + +### Phase33–34: C7 v2 A/B と運用方針固定 +- C7 v2 HotHeap(current/freelist を v2、自前 lease は v1)A/B: + - C7-only ws=64 iters=20k PROFILE=C7_SAFE HOT=1 LARSON_FIX=1 HEAP_STATS=ON: v2 OFF **39.42M** / v2 ON **43.55M**(cls7 fast=11015 slow=1)。 + - Mixed 16–1024B ws=256 iters=20k 同条件: v2 OFF **40.44M** / v2 ON **36.58M**(cls7 fast=5691 slow=1)。混在では回帰。 +- 運用方針: v2 は当面 **C7-only ベンチ専用**(Mixed では v2 OFF 推奨)。C7 SAFE v1 を標準とし、v2 は A/B 実験用。 +- v2 stats 追加(ENV `HAKMEM_TINY_HOTHEAP_V2_STATS=1`): + - `alloc_calls/alloc_fast/alloc_lease/alloc_fb_v1` + - `free_calls/free_fast/free_fb_v1` + - destructor で `[HOTHEAP_V2_C7_STATS] ...` を 1 行 dump。 +- Mixed 回帰の切り分け: 上記 stats で「v2 fast/fallback の比率」を取り、枝コストか fallback 多発かを次フェーズで判断する。 + +### Phase35: v2 を封印し、標準を v1+C7_SAFE に固定 +- 観測: v2 ON で C7-only/Mixed とも大幅劣化(alloc_fast がほぼ当たらず lease→v1 fallback が支配、HEAP_STATS slow_prepare も爆増)。 +- 方針: + - 標準設定は `HAKMEM_TINY_HOTHEAP_V2=0`。Mixed では必ず OFF。C7-only でも v2 は明示 ON の研究モード扱い。 + - コード上の v2 分岐は `__builtin_expect(..., 0)` に寄せ、コメントで「現状は負けている実験箱」だと明示。性能比較や mimalloc 対決では v1+C7_SAFE に集中する。 +- 次の焦点: v1+C7_SAFE のホットパス薄型化や mid-size 側の改善を優先し、v2 を触るのは「それでも足りない」ときの次善策に回す。 + +### Phase36: C7-only HotHeap v2 を Hot Box 化(lease は v1、Hot 部は自前) +- 変更: + - `tiny_hotheap_v2_tls_get()` で全クラスの storage_page を reset+stride 初期化し、TLS ctx を明示確保。v2 ページ node 取得ヘルパを追加(storage 再利用+不足時は calloc)。 + - C7 専用の Hot パスを実装: `tiny_hotheap_v2_alloc`/`free` で current_page→partial→refill の順に処理し、pop/push は lease した v1 page を更新して meta/ss を維持。used==0 は retire_slow 経由でリセット。 + - Cold 境界を明示: `tiny_hotheap_v2_refill_slow`(`tiny_heap_c7_lease_page_for_v2()` から 1 枚借りて wrap)と `tiny_hotheap_v2_page_retire_slow`(lease 情報を返却し reset)が Hot→Cold 唯一の接点。 + - Route/Front: route LUT をそのまま使い、C7 直線パスでも route==`TINY_ROUTE_HOTHEAP_V2` のときだけ v2 を試すよう整理(free 側も同様)。v2 を外したときの branch コストを最小化。 + - v2 stats は現状維持(alloc/free fast/lease/fallback を ENV `HAKMEM_TINY_HOTHEAP_V2_STATS` で計測)。 +- 状態: v2 は依然 C7-only 実験箱。デフォルト/推奨は `HOTHEAP_V2=0`(v1+C7_SAFE)。A/B 計測は未実施(make -j4 成功のみ)。 + +### Phase53: mid/smallmid シングルスレッド基線(v2 OFF, C7_SAFE) +- 条件: threads=1 / cycles=1,000,000 / ws=400 / reps=1。 +- スループット: HAKMEM **28.43M ops/s**(perf run 29.02M)、mimalloc **54.22M**、system **15.29M**。 +- perf stat (HAKMEM): cycles=211,394,722、instructions=513,342,673 (IPC≈2.43)、task-clock=57.48ms(user 33.29 / sys 25.22)、page-faults=7,374。 +- 所感: mid/smallmid では mimalloc が約1.9×。pf は ~7.3k で Tiny よりやや多めだが CPU 命令量と sys 比率が主因。次のターゲット選定用に数字を確定。 + +### Phase54: mid/smallmid CPU ホットパス分析(perf record, userland) +- 条件: `perf record -g -e cycles:u -o perf.data.mid_u ./bench_mid_large_mt_hakmem 1 1000000 400 1`(C7_SAFE, v2 OFF)。 +- ホットシンボル(self%): hak_pool_try_alloc.part.0=14.7%、worker_run=9.2%、free/hak_free_at≈9–10%、__memset_avx2_unaligned_erms≈9%、mid_desc_lookup=3.8%、hak_super_lookup=1.4%、hak_pool_free≈0.7%。 +- 所感: pool allocator と free/memset が支配的。mid_desc_lookup / hak_super_lookup も目立つ。Phase55 ターゲットは pool 系を筆頭候補に。 +- ドキュメント: docs/analysis/MID_LARGE_CPU_HOTPATH_ANALYSIS.md に詳細。 + +### Phase55: pool allocator ホットパス軽量化(着手予定) +- スコープ: core/hakmem_pool.c / core/hakmem_smallmid.c / box/pool_* の fast path。 +- 方針: hak_pool_try_alloc を「TLS/local freelist 即 return」直線化、debug/stat/slow を unlikely 側に寄せる。self-thread free では pool push を最優先にし、cross-thread/debug は後段に分離。mid_desc_lookup を入口で 1 回だけ決めて TLS にキャッシュする案を検討。 +- 成功ライン (bench_mid_large_mt_hakmem 1 1000000 400 1, Release, v2 OFF, C7_SAFE): ops/s を +5〜10%(28–29M→30–32M)改善し、perf self% で hak_pool_try_alloc+free/hak_free_at の合計が数ポイント低下していれば〆とする。 + +### Phase56: pool ホットパス実装の初期薄型化(結果) +- 変更: PoolBlock→user 変換を `hak_pool_block_to_user()` にまとめ、TLS fast path/pop と self-thread free の最優先 push を直線化。owner 判定を mid_desc 1 回の lookup に寄せ、同一スレッド free は ring/lo push で即 return。 +- ベンチ (C6-heavy: min=257/max=768, ws=256, iters=20k, C7_SAFE, v2 OFF): **25.93M ops/s**(従来 28–29M から悪化)。perf stat (1M, ws=400): cycles=225,766,496 / instructions=528,335,613 / task-clock=57.88ms、ops/s=25.71M。 +- 所感: fast path整理だけでは効果が出ず回帰。pool self%/memset などが依然重い可能性が高い。次の一手は pool fast pathのさらなる枝削減や memset/desc cache の見直しが必要。 + +### Phase57: pool v2 回帰トリアージ(実装) +- `HAKMEM_POOL_V2_ENABLED` で旧/新 pool を A/B できる gate を追加(デフォルト 0 = 旧挙動)。細分スイッチとして `HAKMEM_POOL_V2_BLOCK_TO_USER` / `HAKMEM_POOL_V2_TLS_FAST_PATH` を用意(v2時のみ有効)。 +- v1/v2 の両実装を同居させ、公開 API はラッパでルート切替。mid_lookup も同様に gate。 +- ベンチ (C6-heavy, 1M/400, Release): + - v2 OFF (v1): **27.40M ops/s** + - v2 ON: **24.73M ops/s** +- 所感: 回帰を避けるため標準は v1 を維持しつつ、どの変更が悪かったかをスイッチ単位でA/Bできるようにした。次は各スイッチON/OFFでの差分取り、必要なら v2 を研究箱のまま凍結。 + +### Phase Final: 現行デフォルトと研究箱の位置づけ +- 標準構成: `HAKMEM_TINY_HEAP_PROFILE=C7_SAFE`, `HAKMEM_TINY_HOTHEAP_V2=0`, `HAKMEM_TINY_STATS_BOX=1`, `HAKMEM_TINY_STATS_BATCH=0`, Pool は `HAKMEM_POOL_V2_ENABLED=0`。HugePage/ヘッダ light/off はすべて OFF。 +- 研究箱: + - TinyHotHeap v2(C7-only): 明示的に `HAKMEM_TINY_HOTHEAP_V2=1` のときだけ使用。Mixed では推奨 OFF。 + - Pool v2: `HAKMEM_POOL_V2_ENABLED=1` を立てたときのみ。標準は v1 で回帰なしを優先。 + - HugePage/ヘッダ light/off: first-touch/bench 専用。デフォルトはすべて OFF。 +- 次フェーズの方向性(v3 テーマの入口): + - TinyHeap v2 を C5–C7 統合 HotHeap として再設計(現行 v2 とは別ライン)。 + - first-touch/page-fault 系の本格対応(HugePage/ヘッダ light の昇格可否検証)。 + - mid/smallmid の pool/フロント最適化、または mid/large route のさらなるフラット化。 diff --git a/core/box/pool_api.inc.h b/core/box/pool_api.inc.h index 3dbf8e8d..c2ed511d 100644 --- a/core/box/pool_api.inc.h +++ b/core/box/pool_api.inc.h @@ -4,7 +4,422 @@ #include "pagefault_telemetry_box.h" // Box PageFaultTelemetry (PF_BUCKET_MID) -void* hak_pool_try_alloc(size_t size, uintptr_t site_id) { +// Pool v2 is experimental. Default OFF (use legacy v1 path). +static inline int hak_pool_v2_enabled(void) { + static int g = -1; + if (__builtin_expect(g == -1, 0)) { + const char* e = getenv("HAKMEM_POOL_V2_ENABLED"); + g = (e && *e && *e != '0') ? 1 : 0; + } + return g; +} + +// Fine-grained switches (only used when v2 is enabled). +static inline int hak_pool_v2_block_to_user_enabled(void) { + static int g = -1; + if (__builtin_expect(g == -1, 0)) { + const char* e = getenv("HAKMEM_POOL_V2_BLOCK_TO_USER"); + g = (e && *e && *e != '0') ? 1 : 0; + if (g == -1) g = 1; + } + return g; +} + +static inline int hak_pool_v2_tls_fast_enabled(void) { + static int g = -1; + if (__builtin_expect(g == -1, 0)) { + const char* e = getenv("HAKMEM_POOL_V2_TLS_FAST_PATH"); + g = (e && *e && *e != '0') ? 1 : 0; + if (g == -1) g = 1; + } + return g; +} + +// Thin helper to keep the hot path straight-line when converting a PoolBlock to +// a user pointer. All sampling/stat updates remain here so the callers stay +// small. +static inline void* hak_pool_block_to_user(PoolBlock* b, int class_idx, uintptr_t site_id) { + void* raw = (void*)b; + AllocHeader* hdr = (AllocHeader*)raw; + mid_set_header(hdr, g_class_sizes[class_idx], site_id); + void* user = (char*)raw + HEADER_SIZE; + mid_page_inuse_inc(raw); + t_pool_rng ^= t_pool_rng << 13; + t_pool_rng ^= t_pool_rng >> 17; + t_pool_rng ^= t_pool_rng << 5; + if ((t_pool_rng & ((1u << g_count_sample_exp) - 1u)) == 0u) { + g_pool.hits[class_idx]++; + } + pagefault_telemetry_touch(PF_BUCKET_MID, user); + return user; +} + +// Legacy inline conversion used when v2 helper is disabled. +static inline void* hak_pool_block_to_user_legacy(PoolBlock* b, int class_idx, uintptr_t site_id) { + void* raw = (void*)b; + AllocHeader* hdr = (AllocHeader*)raw; + mid_set_header(hdr, g_class_sizes[class_idx], site_id); + void* user = (char*)raw + HEADER_SIZE; + mid_page_inuse_inc(raw); + t_pool_rng ^= t_pool_rng << 13; + t_pool_rng ^= t_pool_rng >> 17; + t_pool_rng ^= t_pool_rng << 5; + if ((t_pool_rng & ((1u << g_count_sample_exp) - 1u)) == 0u) { + g_pool.hits[class_idx]++; + } + pagefault_telemetry_touch(PF_BUCKET_MID, user); + return user; +} + +static inline void* hak_pool_try_alloc_v2_impl(size_t size, uintptr_t site_id) { + // Debug: IMMEDIATE output to verify function is called + static int first_call = 1; + if (__builtin_expect(first_call, 0)) { + HAKMEM_LOG("[Pool] hak_pool_try_alloc FIRST CALL EVER!\n"); + first_call = 0; + } + + if (__builtin_expect(size == 40960, 0)) { + HAKMEM_LOG("[Pool] hak_pool_try_alloc called with 40KB (Bridge class 5)\n"); + } + + hak_pool_init(); // pthread_once() ensures thread-safe init (no data race!) + + // Debug for 33-41KB allocations + if (__builtin_expect(size >= 33000 && size <= 41000, 0)) { + HAKMEM_LOG("[Pool] hak_pool_try_alloc: size=%zu (after init)\n", size); + } + + // P1.7 guard: allow pool by default even when called from wrappers. + // Only block if explicitly disabled via env or during nested recursion. + extern int hak_in_wrapper(void); + extern __thread int g_hakmem_lock_depth; + int in_wrapper = hak_in_wrapper(); + if (in_wrapper && g_hakmem_lock_depth > 1) { + if (__builtin_expect(size >= 33000 && size <= 41000, 0)) { + HAKMEM_LOG("[Pool] REJECTED: nested wrapper depth=%d\n", g_hakmem_lock_depth); + } + return NULL; + } + if (in_wrapper && !g_wrap_l2_enabled) { + if (__builtin_expect(size >= 33000 && size <= 41000, 0)) { + HAKMEM_LOG("[Pool] REJECTED: in_wrapper=%d, wrap_l2=%d\n", in_wrapper, g_wrap_l2_enabled); + } + return NULL; + } + if (!hak_pool_is_poolable(size)) { + if (__builtin_expect(size >= 33000 && size <= 41000, 0)) { + HAKMEM_LOG("[Pool] REJECTED: not poolable (min=%d, max=%d)\n", POOL_MIN_SIZE, POOL_MAX_SIZE); + } + return NULL; + } + + // Get class and shard indices + int class_idx = hak_pool_get_class_index(size); + if (class_idx < 0) { + if (__builtin_expect(size >= 33000 && size <= 41000, 0)) { + HAKMEM_LOG("[Pool] REJECTED: class_idx=%d (size=%zu not mapped)\n", class_idx, size); + } + return NULL; + } + + if (__builtin_expect(size >= 33000 && size <= 41000, 0)) { + HAKMEM_LOG("[Pool] ACCEPTED: class_idx=%d, proceeding with allocation\n", class_idx); + } + + // MF2: Per-Page Sharding path + if (g_mf2_enabled) { + return mf2_alloc_fast(class_idx, size, site_id); + } + + // OLD PATH: TLS fast path (ring then local LIFO); drain TC only when needed + PoolTLSRing* ring = &g_tls_bin[class_idx].ring; + if (g_tc_enabled && ring->top < g_tc_drain_trigger && mid_tc_has_items(class_idx)) { + HKM_TIME_START(t_tc_drain); + if (mid_tc_drain_into_tls(class_idx, ring, &g_tls_bin[class_idx])) { + HKM_TIME_END(HKM_CAT_TC_DRAIN, t_tc_drain); + if (ring->top > 0) { + HKM_TIME_START(t_ring_pop0); + PoolBlock* tlsb = ring->items[--ring->top]; + HKM_TIME_END(HKM_CAT_POOL_TLS_RING_POP, t_ring_pop0); + return hak_pool_block_to_user(tlsb, class_idx, site_id); + } + } else { HKM_TIME_END(HKM_CAT_TC_DRAIN, t_tc_drain); } + } + if (g_tls_ring_enabled) { + if (ring->top == 0) { + atomic_fetch_add_explicit(&g_pool.ring_underflow, 1, memory_order_relaxed); + } + if (ring->top > 0) { + HKM_TIME_START(t_ring_pop1); + PoolBlock* tlsb = ring->items[--ring->top]; + HKM_TIME_END(HKM_CAT_POOL_TLS_RING_POP, t_ring_pop1); + return hak_pool_block_to_user(tlsb, class_idx, site_id); + } + } + if (g_tls_bin[class_idx].lo_head) { + HKM_TIME_START(t_lifo_pop0); + PoolBlock* b = g_tls_bin[class_idx].lo_head; + g_tls_bin[class_idx].lo_head = b->next; + if (g_tls_bin[class_idx].lo_count) g_tls_bin[class_idx].lo_count--; + HKM_TIME_END(HKM_CAT_POOL_TLS_LIFO_POP, t_lifo_pop0); + return hak_pool_block_to_user(b, class_idx, site_id); + } + + // Compute shard only when we need to access shared structures + int shard_idx = hak_pool_get_shard_index(site_id); + + // Try to batch-pop from a non-empty shard using trylock to fill TLS ring + if (g_tls_ring_enabled) { + int s0 = choose_nonempty_shard(class_idx, shard_idx); + for (int probe = 0; probe < g_trylock_probes; ++probe) { + int s = (s0 + probe) & (POOL_NUM_SHARDS - 1); + pthread_mutex_t* l = &g_pool.freelist_locks[class_idx][s].m; + atomic_fetch_add_explicit(&g_pool.trylock_attempts, 1, memory_order_relaxed); + if (pthread_mutex_trylock(l) == 0) { + atomic_fetch_add_explicit(&g_pool.trylock_success, 1, memory_order_relaxed); + // First, drain any remote frees into freelist + if (atomic_load_explicit(&g_pool.remote_count[class_idx][s], memory_order_relaxed) != 0) { + drain_remote_locked(class_idx, s); + } + PoolBlock* head = g_pool.freelist[class_idx][s]; + int to_ring = POOL_L2_RING_CAP - ring->top; if (to_ring < 0) to_ring = 0; + while (head && to_ring-- > 0) { PoolBlock* nxt = head->next; ring->items[ring->top++] = head; head = nxt; } + while (head) { PoolBlock* nxt = head->next; head->next = g_tls_bin[class_idx].lo_head; g_tls_bin[class_idx].lo_head = head; g_tls_bin[class_idx].lo_count++; head = nxt; } + g_pool.freelist[class_idx][s] = head; + if (!head) clear_nonempty_bit(class_idx, s); + pthread_mutex_unlock(l); + if (ring->top > 0) { + PoolBlock* tlsb = ring->items[--ring->top]; + return hak_pool_block_to_user(tlsb, class_idx, site_id); + } + } + } + } + + // Try TLS active pages (owner-only local bump-run, up to 3) + PoolTLSPage* ap = NULL; + if (g_tls_active_page_a[class_idx].page && g_tls_active_page_a[class_idx].count > 0 && g_tls_active_page_a[class_idx].bump < g_tls_active_page_a[class_idx].end) ap = &g_tls_active_page_a[class_idx]; + else if (g_tls_active_page_b[class_idx].page && g_tls_active_page_b[class_idx].count > 0 && g_tls_active_page_b[class_idx].bump < g_tls_active_page_b[class_idx].end) ap = &g_tls_active_page_b[class_idx]; + else if (g_tls_active_page_c[class_idx].page && g_tls_active_page_c[class_idx].count > 0 && g_tls_active_page_c[class_idx].bump < g_tls_active_page_c[class_idx].end) ap = &g_tls_active_page_c[class_idx]; + if (ap) { + if (g_tls_ring_enabled && ring->top < POOL_L2_RING_CAP) { + int need = POOL_L2_RING_CAP - ring->top; + (void)refill_tls_from_active_page(class_idx, ring, &g_tls_bin[class_idx], ap, need); + } + PoolBlock* b = NULL; + if (ring->top > 0) { b = ring->items[--ring->top]; } + else if (ap->page && ap->count > 0 && ap->bump < ap->end) { + b = (PoolBlock*)(void*)ap->bump; ap->bump += (HEADER_SIZE + g_class_sizes[class_idx]); ap->count--; if (ap->bump >= ap->end || ap->count<=0){ ap->page=NULL; ap->count=0; } + } + if (b) { + g_pool.hits[class_idx]++; + return hak_pool_block_to_user(b, class_idx, site_id); + } + } + + // Lock the shard freelist for this (class, shard) + pthread_mutex_t* lock = &g_pool.freelist_locks[class_idx][shard_idx].m; + HKM_TIME_START(t_lock); + struct timespec ts_lk1; int lk1 = hkm_prof_begin(&ts_lk1); + (void)ts_lk1; (void)lk1; // Unused profiling variables + pthread_mutex_lock(lock); + HKM_TIME_END(HKM_CAT_POOL_LOCK, t_lock); + hkm_prof_end(lk1, HKP_POOL_LOCK, &ts_lk1); + + // Try to pop from freelist + PoolBlock* block = g_pool.freelist[class_idx][shard_idx]; + + if (!block) { + // Before refilling, try draining remote stack and simple shard steal + int stole = 0; + const FrozenPolicy* pol = hkm_policy_get(); + if (pol) { + uint16_t cap = 0; + if (class_idx < 5) cap = pol->mid_cap[class_idx]; + else if (class_idx == 5 && pol->mid_dyn1_bytes != 0) cap = pol->mid_cap_dyn1; + else if (class_idx == 6 && pol->mid_dyn2_bytes != 0) cap = pol->mid_cap_dyn2; + // Drain remotes + if (atomic_load_explicit(&g_pool.remote_count[class_idx][shard_idx], memory_order_relaxed) != 0) { + drain_remote_locked(class_idx, shard_idx); + block = g_pool.freelist[class_idx][shard_idx]; + } + // Light shard steal when over cap + if (!block && cap > 0 && g_pool.pages_by_class[class_idx] >= cap) { + HKM_TIME_START(t_steal); + for (int d = 1; d <= 4 && !stole; d++) { + int s1 = (shard_idx + d) & (POOL_NUM_SHARDS - 1); + int s2 = (shard_idx - d) & (POOL_NUM_SHARDS - 1); + if (is_shard_nonempty(class_idx, s1)) { + pthread_mutex_t* l2 = &g_pool.freelist_locks[class_idx][s1].m; + pthread_mutex_lock(l2); + PoolBlock* b2 = g_pool.freelist[class_idx][s1]; + if (b2) { + g_pool.freelist[class_idx][s1] = b2->next; + if (!g_pool.freelist[class_idx][s1]) clear_nonempty_bit(class_idx, s1); + block = b2; stole = 1; + } + pthread_mutex_unlock(l2); + } + if (!stole && is_shard_nonempty(class_idx, s2)) { + pthread_mutex_t* l3 = &g_pool.freelist_locks[class_idx][s2].m; + pthread_mutex_lock(l3); + PoolBlock* b3 = g_pool.freelist[class_idx][s2]; + if (b3) { + g_pool.freelist[class_idx][s2] = b3->next; + if (!g_pool.freelist[class_idx][s2]) clear_nonempty_bit(class_idx, s2); + block = b3; stole = 1; + } + pthread_mutex_unlock(l3); + } + } + HKM_TIME_END(HKM_CAT_SHARD_STEAL, t_steal); + } + } + + if (!stole && !block) { + // Freelist empty, refill page + PoolTLSPage* tap = NULL; + if (g_tls_active_page_a[class_idx].page == NULL || g_tls_active_page_a[class_idx].count == 0) tap = &g_tls_active_page_a[class_idx]; + else if (g_tls_active_page_b[class_idx].page == NULL || g_tls_active_page_b[class_idx].count == 0) tap = &g_tls_active_page_b[class_idx]; + else if (g_tls_active_page_c[class_idx].page == NULL || g_tls_active_page_c[class_idx].count == 0) tap = &g_tls_active_page_c[class_idx]; + else tap = &g_tls_active_page_a[class_idx]; + HKM_TIME_START(t_alloc_page); + if (alloc_tls_page(class_idx, tap)) { + HKM_TIME_END(HKM_CAT_POOL_ALLOC_TLS_PAGE, t_alloc_page); + pthread_mutex_unlock(lock); + // Top-up ring and return + ap = tap; + if (g_tls_ring_enabled && ring->top < POOL_L2_RING_CAP) { + int need = POOL_L2_RING_CAP - ring->top; + (void)refill_tls_from_active_page(class_idx, ring, &g_tls_bin[class_idx], ap, need); + } + PoolBlock* takeb = NULL; + if (ring->top > 0) { + HKM_TIME_START(t_ring_pop2); + takeb = ring->items[--ring->top]; + HKM_TIME_END(HKM_CAT_POOL_TLS_RING_POP, t_ring_pop2); + } else if (ap->page && ap->count > 0 && ap->bump < ap->end) { + takeb = (PoolBlock*)(void*)ap->bump; + ap->bump += (HEADER_SIZE + g_class_sizes[class_idx]); + ap->count--; + if (ap->bump >= ap->end || ap->count == 0) { + ap->page = NULL; + ap->count = 0; + } + } + return hak_pool_block_to_user(takeb, class_idx, site_id); + } + HKM_TIME_START(t_refill); + struct timespec ts_rf; int rf = hkm_prof_begin(&ts_rf); + int ok = refill_freelist(class_idx, shard_idx); + HKM_TIME_END(HKM_CAT_POOL_REFILL, t_refill); + hkm_prof_end(rf, HKP_POOL_REFILL, &ts_rf); + if (!ok) { + t_pool_rng ^= t_pool_rng << 13; t_pool_rng ^= t_pool_rng >> 17; t_pool_rng ^= t_pool_rng << 5; + if ((t_pool_rng & ((1u<next; + mid_desc_adopt(block, class_idx, (uint64_t)(uintptr_t)pthread_self()); + if (g_pool.freelist[class_idx][shard_idx] == NULL) clear_nonempty_bit(class_idx, shard_idx); + pthread_mutex_unlock(lock); + + // Store to TLS then pop + PoolBlock* take; + if (g_tls_ring_enabled && ring->top < POOL_L2_RING_CAP) { ring->items[ring->top++] = block; take = ring->items[--ring->top]; } + else { block->next = g_tls_bin[class_idx].lo_head; g_tls_bin[class_idx].lo_head = block; g_tls_bin[class_idx].lo_count++; + if (g_tls_ring_enabled && ring->top > 0) { take = ring->items[--ring->top]; } + else { take = g_tls_bin[class_idx].lo_head; g_tls_bin[class_idx].lo_head = take->next; if (g_tls_bin[class_idx].lo_count) g_tls_bin[class_idx].lo_count--; } } + + return hak_pool_block_to_user(take, class_idx, site_id); +} + +static inline void hak_pool_free_v2_impl(void* ptr, size_t size, uintptr_t site_id) { + if (!ptr) return; + hak_pool_init(); + if (!hak_pool_is_poolable(size)) return; + + if (g_mf2_enabled) { mf2_free(ptr); return; } + + void* raw = (char*)ptr - HEADER_SIZE; + AllocHeader* hdr = (AllocHeader*)raw; + MidPageDesc* d_desc = mid_desc_lookup(ptr); + int mid_by_desc = d_desc != NULL; + if (!mid_by_desc && g_hdr_light_enabled < 2) { + if (hdr->magic != HAKMEM_MAGIC) { MF2_ERROR_LOG("Invalid magic 0x%X in pool_free, expected 0x%X", hdr->magic, HAKMEM_MAGIC); return; } + if (hdr->method != ALLOC_METHOD_POOL) { MF2_ERROR_LOG("Wrong method %d in pool_free, expected POOL (%d)", hdr->method, ALLOC_METHOD_POOL); return; } + } + int class_idx = mid_by_desc ? (int)d_desc->class_idx : hak_pool_get_class_index(size); + if (class_idx < 0) return; + PoolBlock* block = (PoolBlock*)raw; + uint64_t owner_tid = 0; + if (d_desc) owner_tid = d_desc->owner_tid; + else if (g_hdr_light_enabled < 2) owner_tid = hdr->owner_tid; + const uint64_t self_tid = (uint64_t)(uintptr_t)pthread_self(); + + if (g_pool.tls_free_enabled) { + const int same_thread = owner_tid != 0 && owner_tid == self_tid; + if (same_thread) { + PoolTLSRing* ring = &g_tls_bin[class_idx].ring; + if (g_tls_ring_enabled && ring->top < POOL_L2_RING_CAP) { ring->items[ring->top++] = block; } + else { + block->next = g_tls_bin[class_idx].lo_head; + g_tls_bin[class_idx].lo_head = block; + g_tls_bin[class_idx].lo_count++; + if ((int)g_tls_bin[class_idx].lo_count > g_tls_lo_max) { + size_t spill = g_tls_bin[class_idx].lo_count / 2; + int shard = hak_pool_get_shard_index(site_id); + // Spill half of local freelist to remote freelist + while (spill-- && g_tls_bin[class_idx].lo_head) { + PoolBlock* b = g_tls_bin[class_idx].lo_head; + g_tls_bin[class_idx].lo_head = b->next; + g_tls_bin[class_idx].lo_count--; + HKM_TIME_START(t_remote_push1); + uintptr_t old_head; + do { + old_head = atomic_load_explicit(&g_pool.remote_head[class_idx][shard], memory_order_acquire); + b->next = (PoolBlock*)old_head; + } while (!atomic_compare_exchange_weak_explicit(&g_pool.remote_head[class_idx][shard], + &old_head, (uintptr_t)b, + memory_order_release, memory_order_relaxed)); + atomic_fetch_add_explicit(&g_pool.remote_count[class_idx][shard], 1, memory_order_relaxed); + HKM_TIME_END(HKM_CAT_POOL_REMOTE_PUSH, t_remote_push1); + } + set_nonempty_bit(class_idx, shard); + } + } + } else { + if (g_tc_enabled && owner_tid != 0) { MidTC* otc = mid_tc_lookup_by_tid(owner_tid); if (otc) { mid_tc_push(otc, class_idx, block); return; } } + int shard = hak_pool_get_shard_index(site_id); uintptr_t old_head; HKM_TIME_START(t_remote_push2); + do { old_head = atomic_load_explicit(&g_pool.remote_head[class_idx][shard], memory_order_acquire); block->next = (PoolBlock*)old_head; } while (!atomic_compare_exchange_weak_explicit(&g_pool.remote_head[class_idx][shard], &old_head, (uintptr_t)block, memory_order_release, memory_order_relaxed)); + atomic_fetch_add_explicit(&g_pool.remote_count[class_idx][shard], 1, memory_order_relaxed); HKM_TIME_END(HKM_CAT_POOL_REMOTE_PUSH, t_remote_push2); set_nonempty_bit(class_idx, shard); + } + } else { + int shard_idx2 = hak_pool_get_shard_index(site_id); pthread_mutex_t* lock = &g_pool.freelist_locks[class_idx][shard_idx2].m; pthread_mutex_lock(lock); block->next = g_pool.freelist[class_idx][shard_idx2]; g_pool.freelist[class_idx][shard_idx2] = block; set_nonempty_bit(class_idx, shard_idx2); pthread_mutex_unlock(lock); + } + t_pool_rng ^= t_pool_rng << 13; t_pool_rng ^= t_pool_rng >> 17; t_pool_rng ^= t_pool_rng << 5; if ((t_pool_rng & ((1u<class_idx; if (c < 0 || c >= POOL_NUM_CLASSES) return 0; size_t sz = g_class_sizes[c]; if (sz == 0) return 0; if (out_size) *out_size = sz; return 1; } } + MidPageDesc* d = mid_desc_lookup(ptr); if (!d) return 0; int c = (int)d->class_idx; if (c < 0 || c >= POOL_NUM_CLASSES) return 0; size_t sz = g_class_sizes[c]; if (sz == 0) return 0; if (out_size) *out_size = sz; return 1; +} + +static inline void hak_pool_free_fast_v2_impl(void* ptr, uintptr_t site_id) { + if (!ptr || !g_pool.initialized) return; if (g_mf2_enabled) { MidPage* page = mf2_addr_to_page(ptr); if (page) { mf2_free(ptr); return; } } + MidPageDesc* d = mid_desc_lookup(ptr); if (!d) return; size_t sz = g_class_sizes[(int)d->class_idx]; if (sz == 0) return; hak_pool_free(ptr, sz, site_id); +} + + + +static inline void* hak_pool_try_alloc_v1_impl(size_t size, uintptr_t site_id) { // Debug: IMMEDIATE output to verify function is called static int first_call = 1; if (first_call) { HAKMEM_LOG("[Pool] hak_pool_try_alloc FIRST CALL EVER!\n"); first_call = 0; } @@ -289,7 +704,7 @@ void* hak_pool_try_alloc(size_t size, uintptr_t site_id) { return user4; } -void hak_pool_free(void* ptr, size_t size, uintptr_t site_id) { +static inline void hak_pool_free_v1_impl(void* ptr, size_t size, uintptr_t site_id) { if (!ptr) return; hak_pool_init(); if (!hak_pool_is_poolable(size)) return; @@ -353,14 +768,47 @@ void hak_pool_free(void* ptr, size_t size, uintptr_t site_id) { mid_page_inuse_dec_and_maybe_dn(raw); } -int hak_pool_mid_lookup(void* ptr, size_t* out_size) { +static inline int hak_pool_mid_lookup_v1_impl(void* ptr, size_t* out_size) { if (g_mf2_enabled) { MidPage* page = mf2_addr_to_page(ptr); if (page) { int c = (int)page->class_idx; if (c < 0 || c >= POOL_NUM_CLASSES) return 0; size_t sz = g_class_sizes[c]; if (sz == 0) return 0; if (out_size) *out_size = sz; return 1; } } MidPageDesc* d = mid_desc_lookup(ptr); if (!d) return 0; int c = (int)d->class_idx; if (c < 0 || c >= POOL_NUM_CLASSES) return 0; size_t sz = g_class_sizes[c]; if (sz == 0) return 0; if (out_size) *out_size = sz; return 1; } -void hak_pool_free_fast(void* ptr, uintptr_t site_id) { +static inline void hak_pool_free_fast_v1_impl(void* ptr, uintptr_t site_id) { if (!ptr || !g_pool.initialized) return; if (g_mf2_enabled) { MidPage* page = mf2_addr_to_page(ptr); if (page) { mf2_free(ptr); return; } } MidPageDesc* d = mid_desc_lookup(ptr); if (!d) return; size_t sz = g_class_sizes[(int)d->class_idx]; if (sz == 0) return; hak_pool_free(ptr, sz, site_id); } +// --- Public wrappers (env-gated) ---------------------------------------------- +static inline int hak_pool_v2_route(void) { return hak_pool_v2_enabled(); } + +void* hak_pool_try_alloc(size_t size, uintptr_t site_id) { + if (!hak_pool_v2_route()) { + return hak_pool_try_alloc_v1_impl(size, site_id); + } + return hak_pool_try_alloc_v2_impl(size, site_id); +} + +void hak_pool_free(void* ptr, size_t size, uintptr_t site_id) { + if (!hak_pool_v2_route()) { + hak_pool_free_v1_impl(ptr, size, site_id); + return; + } + hak_pool_free_v2_impl(ptr, size, site_id); +} + +void hak_pool_free_fast(void* ptr, uintptr_t site_id) { + if (!hak_pool_v2_route()) { + hak_pool_free_fast_v1_impl(ptr, site_id); + return; + } + hak_pool_free_fast_v2_impl(ptr, site_id); +} + +int hak_pool_mid_lookup(void* ptr, size_t* out_size) { + if (!hak_pool_v2_route()) { + return hak_pool_mid_lookup_v1_impl(ptr, out_size); + } + return hak_pool_mid_lookup_v2_impl(ptr, out_size); +} + #endif // POOL_API_INC_H diff --git a/core/box/ss_allocation_box.c b/core/box/ss_allocation_box.c index 13658277..21846163 100644 --- a/core/box/ss_allocation_box.c +++ b/core/box/ss_allocation_box.c @@ -361,6 +361,7 @@ void superslab_free(SuperSlab* ss) { if (lazy_zero_enabled) { #ifdef MADV_DONTNEED (void)madvise((void*)ss, ss_size, MADV_DONTNEED); + ss_os_stats_record_madvise(); #endif } return; @@ -399,6 +400,7 @@ void superslab_free(SuperSlab* ss) { atomic_load_explicit(&ss->total_active_blocks, memory_order_relaxed)); #endif + ss_os_stats_record_free(); munmap(ss, ss_size); // Update statistics for actual release to OS diff --git a/core/box/ss_cache_box.c b/core/box/ss_cache_box.c index 6fcb3030..2803ee83 100644 --- a/core/box/ss_cache_box.c +++ b/core/box/ss_cache_box.c @@ -159,6 +159,7 @@ void tiny_ss_cache_set_class_cap(int class_idx, size_t new_cap) { // Convert cache entry back to SuperSlab* and release it to OS. SuperSlab* ss = (SuperSlab*)entry; size_t ss_size = (size_t)1 << ss->lg_size; + ss_os_stats_record_free(); munmap((void*)ss, ss_size); // Update global stats to keep accounting consistent. diff --git a/core/box/ss_os_acquire_box.c b/core/box/ss_os_acquire_box.c index 709c22ad..3205aa82 100644 --- a/core/box/ss_os_acquire_box.c +++ b/core/box/ss_os_acquire_box.c @@ -10,8 +10,13 @@ #include // Global counters for debugging (non-static for external access) -_Atomic uint64_t g_ss_mmap_count = 0; -_Atomic uint64_t g_final_fallback_mmap_count = 0; +extern _Atomic uint64_t g_ss_mmap_count; +extern _Atomic uint64_t g_final_fallback_mmap_count; +extern _Atomic uint64_t g_ss_os_alloc_calls; +extern _Atomic uint64_t g_ss_os_free_calls; +extern _Atomic uint64_t g_ss_os_madvise_calls; +extern _Atomic uint64_t g_ss_os_huge_alloc_calls; +extern _Atomic uint64_t g_ss_os_huge_fail_calls; // ============================================================================ // OOM Diagnostics @@ -84,6 +89,55 @@ static void log_superslab_oom_once(size_t ss_size, size_t alloc_size, int err) { g_hakmem_lock_depth--; // Now safe to restore (all libc calls complete) } +// ============================================================================ +// HugePage (experimental) helper +// ============================================================================ + +static void* ss_os_acquire_hugepage_try(size_t ss_size, uintptr_t ss_mask, int populate) { +#ifdef MAP_HUGETLB + size_t huge_sz = ss_os_huge_size_bytes(); + if (ss_size != huge_sz) { + // For now, only attempt hugepage when requested SuperSlab size matches the HugePage size. + return NULL; + } + + int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB; +#ifdef MAP_POPULATE + if (populate) { + flags |= MAP_POPULATE; + } +#endif +#ifdef MAP_HUGE_2MB + // Best-effort: allow the kernel to pick 2MB huge pages explicitly when available. + if (huge_sz == (2ULL << 20)) { + flags |= MAP_HUGE_2MB; + } +#endif + + void* ptr = mmap(NULL, huge_sz, PROT_READ | PROT_WRITE, flags, -1, 0); + if (ptr == MAP_FAILED) { + ss_os_stats_record_huge_fail(); + return NULL; + } + + if (((uintptr_t)ptr & ss_mask) != 0) { + munmap(ptr, huge_sz); + ss_os_stats_record_huge_fail(); + return NULL; + } + + ss_os_stats_record_huge_alloc(); + ss_os_stats_record_alloc(); + atomic_fetch_add(&g_ss_mmap_count, 1); + return ptr; +#else + (void)ss_size; + (void)ss_mask; + (void)populate; + return NULL; +#endif +} + // ============================================================================ // OS Acquisition Implementation // ============================================================================ @@ -94,6 +148,14 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p (void)size_class; // Used only for logging in debug builds + // Experimental HugePage path (research-only, default OFF) + if (ss_os_huge_enabled()) { + void* huge = ss_os_acquire_hugepage_try(ss_size, ss_mask, populate); + if (huge != NULL) { + return huge; + } + } + #ifdef MAP_ALIGNED_SUPER int map_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_ALIGNED_SUPER; #ifdef MAP_POPULATE @@ -107,6 +169,7 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p -1, 0); if (ptr != MAP_FAILED) { atomic_fetch_add(&g_ss_mmap_count, 1); + ss_os_stats_record_alloc(); if (((uintptr_t)ptr & ss_mask) == 0) { // Successfully got aligned pointer from OS return ptr; @@ -140,6 +203,7 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p -1, 0); if (raw != MAP_FAILED) { uint64_t count = atomic_fetch_add(&g_ss_mmap_count, 1) + 1; + ss_os_stats_record_alloc(); #if !HAKMEM_BUILD_RELEASE if (log_count < 10) { fprintf(stderr, "[SUPERSLAB_MMAP] #%lu: class=%d size=%zu (total SuperSlab mmaps so far)\n", @@ -177,6 +241,7 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p #ifdef MADV_POPULATE_WRITE if (populate) { int ret = madvise(ptr, ss_size, MADV_POPULATE_WRITE); + ss_os_stats_record_madvise(); if (ret != 0) { // Fallback for kernels that support MADV_POPULATE_WRITE but it fails // Use explicit page-by-page touching with writes @@ -195,8 +260,25 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p p[i] = 0; } p[ss_size - 1] = 0; + ss_os_stats_record_madvise(); } #endif return ptr; } + +static void ss_os_stats_destructor(void) __attribute__((destructor)); +static void ss_os_stats_destructor(void) { + if (!ss_os_stats_enabled()) { + return; + } + fprintf(stderr, + "[SS_OS_STATS] alloc=%llu free=%llu madvise=%llu mmap_total=%llu fallback_mmap=%llu huge_alloc=%llu huge_fail=%llu\n", + (unsigned long long)atomic_load_explicit(&g_ss_os_alloc_calls, memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_ss_os_free_calls, memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_ss_os_madvise_calls, memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_ss_mmap_count, memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_final_fallback_mmap_count, memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_ss_os_huge_alloc_calls, memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_ss_os_huge_fail_calls, memory_order_relaxed)); +} diff --git a/core/box/ss_os_acquire_box.h b/core/box/ss_os_acquire_box.h index 8005ab06..fb85a539 100644 --- a/core/box/ss_os_acquire_box.h +++ b/core/box/ss_os_acquire_box.h @@ -18,6 +18,7 @@ #include #include #include +#include // ============================================================================ // Global Counters (for debugging/diagnostics) @@ -25,6 +26,91 @@ extern _Atomic uint64_t g_ss_mmap_count; extern _Atomic uint64_t g_final_fallback_mmap_count; +extern _Atomic uint64_t g_ss_os_alloc_calls; +extern _Atomic uint64_t g_ss_os_free_calls; +extern _Atomic uint64_t g_ss_os_madvise_calls; +extern _Atomic uint64_t g_ss_os_huge_alloc_calls; +extern _Atomic uint64_t g_ss_os_huge_fail_calls; + +static inline int ss_os_stats_enabled(void) { + static int g_ss_os_stats_enabled = -1; + if (__builtin_expect(g_ss_os_stats_enabled == -1, 0)) { + const char* e = getenv("HAKMEM_SS_OS_STATS"); + g_ss_os_stats_enabled = (e && *e && *e != '0') ? 1 : 0; + } + return g_ss_os_stats_enabled; +} + +static inline void ss_os_stats_record_alloc(void) { + if (!ss_os_stats_enabled()) { + return; + } + atomic_fetch_add_explicit(&g_ss_os_alloc_calls, 1, memory_order_relaxed); +} + +static inline void ss_os_stats_record_free(void) { + if (!ss_os_stats_enabled()) { + return; + } + atomic_fetch_add_explicit(&g_ss_os_free_calls, 1, memory_order_relaxed); +} + +static inline void ss_os_stats_record_madvise(void) { + if (!ss_os_stats_enabled()) { + return; + } + atomic_fetch_add_explicit(&g_ss_os_madvise_calls, 1, memory_order_relaxed); +} + +// ============================================================================ +// HugePage Experiment (research-only) +// ============================================================================ + +static inline int ss_os_huge_enabled(void) { + static int g_ss_os_huge_enabled = -1; + if (__builtin_expect(g_ss_os_huge_enabled == -1, 0)) { + const char* e = getenv("HAKMEM_SS_HUGEPAGE_EXPERIMENT"); + g_ss_os_huge_enabled = (e && *e && *e != '0') ? 1 : 0; + } + return g_ss_os_huge_enabled; +} + +// Parse HAKMEM_SS_HUGEPAGE_SIZE (only "2M" supported explicitly; otherwise +// falls back to default 2MB). This is intentionally soft/experimental. +static inline size_t ss_os_huge_size_bytes(void) { + static size_t g_huge_size = 0; + if (__builtin_expect(g_huge_size == 0, 0)) { + const char* e = getenv("HAKMEM_SS_HUGEPAGE_SIZE"); + if (e && *e) { + char* end = NULL; + unsigned long long v = strtoull(e, &end, 0); + if (end && (*end == 'M' || *end == 'm')) { + v *= 1024ULL * 1024ULL; + } + if (v > 0) { + g_huge_size = (size_t)v; + } + } + if (g_huge_size == 0) { + g_huge_size = (size_t)(2ULL << 20); // default 2MB + } + } + return g_huge_size; +} + +static inline void ss_os_stats_record_huge_alloc(void) { + if (!ss_os_stats_enabled()) { + return; + } + atomic_fetch_add_explicit(&g_ss_os_huge_alloc_calls, 1, memory_order_relaxed); +} + +static inline void ss_os_stats_record_huge_fail(void) { + if (!ss_os_stats_enabled()) { + return; + } + atomic_fetch_add_explicit(&g_ss_os_huge_fail_calls, 1, memory_order_relaxed); +} // ============================================================================ // OS Acquisition API diff --git a/core/box/tiny_front_stats_box.h b/core/box/tiny_front_stats_box.h new file mode 100644 index 00000000..93c00ced --- /dev/null +++ b/core/box/tiny_front_stats_box.h @@ -0,0 +1,42 @@ +// tiny_front_stats_box.h - Front class distribution counters (ENV gated) +#pragma once + +#include +#include +#include +#include "../hakmem_tiny_config.h" + +extern _Atomic uint64_t g_tiny_front_alloc_class[TINY_NUM_CLASSES]; +extern _Atomic uint64_t g_tiny_front_free_class[TINY_NUM_CLASSES]; + +static inline int tiny_front_class_stats_enabled(void) { + static int g = -1; + if (__builtin_expect(g == -1, 0)) { + const char* e = getenv("HAKMEM_TINY_FRONT_CLASS_STATS"); + g = (e && *e && *e != '0') ? 1 : 0; + } + return g; +} + +static inline int tiny_front_class_stats_dump_enabled(void) { + static int g = -1; + if (__builtin_expect(g == -1, 0)) { + const char* e = getenv("HAKMEM_TINY_FRONT_CLASS_STATS_DUMP"); + const char* e2 = getenv("HAKMEM_TINY_FRONT_CLASS_STATS"); + g = ((e && *e && *e != '0') || (e2 && *e2 && *e2 != '0')) ? 1 : 0; + } + return g; +} + +static inline void tiny_front_alloc_stat_inc(int class_idx) { + if (__builtin_expect(tiny_front_class_stats_enabled(), 0)) { + atomic_fetch_add_explicit(&g_tiny_front_alloc_class[class_idx], 1, memory_order_relaxed); + } +} + +static inline void tiny_front_free_stat_inc(int class_idx) { + if (__builtin_expect(tiny_front_class_stats_enabled(), 0)) { + atomic_fetch_add_explicit(&g_tiny_front_free_class[class_idx], 1, memory_order_relaxed); + } +} + diff --git a/core/box/tiny_heap_box.h b/core/box/tiny_heap_box.h index afb205ec..8347ca3b 100644 --- a/core/box/tiny_heap_box.h +++ b/core/box/tiny_heap_box.h @@ -264,6 +264,37 @@ 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_tls_try_resolve(int class_idx, + void* base, + SuperSlab** out_ss, + int* out_slab_idx, + TinySlabMeta** out_meta) { + if (class_idx < 0 || class_idx >= TINY_NUM_CLASSES) return 0; + TinyTLSSlab* tls = &g_tls_slabs[class_idx]; + if (!tls->ss || !tls->slab_base || !tls->meta) return 0; + + const size_t stride = (size_t)tiny_stride_for_class(class_idx); + const size_t cap = (size_t)tls->meta->capacity; + if (stride == 0 || cap == 0) return 0; + + uint8_t* low = tls->slab_base; + uint8_t* high = low + stride * cap; + if ((uint8_t*)base < low || (uint8_t*)base >= high) { + return 0; + } + + if (out_ss) { + *out_ss = tls->ss; + } + if (out_slab_idx) { + *out_slab_idx = (int)tls->slab_idx; + } + if (out_meta) { + *out_meta = tls->meta; + } + return 1; +} + 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)); @@ -630,14 +661,15 @@ static inline void tiny_heap_page_push_to_partial(tiny_heap_class_t* hcls, tiny_ 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; + const int mode = tiny_heap_meta_mode_effective(class_idx); 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 (mode == 1) { + // SAFE: delta を反映(C6/C7) if (class_idx == 6) { tiny_c6_mark_delta_site(page, C6_DELTA_EMPTY); } @@ -685,18 +717,43 @@ static inline void tiny_heap_page_mark_full(tiny_heap_class_t* hcls, tiny_heap_p } static inline void* tiny_heap_page_pop(tiny_heap_class_t* hcls, int class_idx, tiny_heap_page_t* page) { - 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; + + const int mode = tiny_heap_meta_mode_effective(class_idx); + if (class_idx == 7 && __builtin_expect(mode == 1, 1)) { + if (!page->meta || !page->ss || !page->base) return NULL; + 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) { + return NULL; + } + 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++; + } + } + page->used++; + page->used_delta++; + page->active_delta++; + if (tiny_heap_delta_should_flush(class_idx, page)) { + tiny_heap_meta_flush_page(class_idx, page); + } + return tiny_region_id_write_header(block, class_idx); + } + + const int c6_pop_dbg = (class_idx == 6) && tiny_c6_debug_pop_enabled(); if (!page->meta || !page->ss || !page->base) return NULL; - if (class_idx == 6 && mode == 1) { + if (c6_pop_dbg && mode == 1) { int fail = 0; const char* reason = NULL; SuperSlab* ss_chk = hak_super_lookup(page->base); @@ -1134,16 +1191,27 @@ static inline void tiny_heap_free_class_fast(tiny_heap_ctx_t* ctx, int class_idx #else void* base = (void*)((uint8_t*)ptr - 1); #endif - SuperSlab* ss = hak_super_lookup(base); - if (!ss || ss->magic != SUPERSLAB_MAGIC) { - 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); + SuperSlab* ss = NULL; + int slab_idx = -1; + if (class_idx == 7 || class_idx == 6) { + if (!tiny_heap_tls_try_resolve(class_idx, base, &ss, &slab_idx, NULL)) { + ss = NULL; } - tiny_heap_cold_drain_and_free(class_idx, base); - return; } - int slab_idx = slab_index_for(ss, base); + if (!ss) { + ss = hak_super_lookup(base); + if (!ss || ss->magic != SUPERSLAB_MAGIC) { + 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; + } + } + if (slab_idx < 0) { + slab_idx = slab_index_for(ss, base); + } if (slab_idx < 0 || slab_idx >= ss_slabs_capacity(ss)) { TinyHeapClassStats* stats = tiny_heap_stats_for_class(class_idx); if (__builtin_expect(stats != NULL, 0)) { diff --git a/core/box/tiny_heap_env_box.h b/core/box/tiny_heap_env_box.h index 42556f3c..b474f86b 100644 --- a/core/box/tiny_heap_env_box.h +++ b/core/box/tiny_heap_env_box.h @@ -4,14 +4,90 @@ // - デフォルト OFF(環境変数が未設定または 0 のとき)。 #pragma once +#include #include +#include +#include #include "c7_hotpath_env_box.h" // tiny_c7_hot_enabled() +// TinyHeap profile modes (HAKMEM_TINY_HEAP_PROFILE) +enum { + TINY_HEAP_PROFILE_LEGACY = 0, // TinyHeap OFF / 既存 front + TINY_HEAP_PROFILE_C7_SAFE = 1, // C7 TinyHeap + SAFE meta + TINY_HEAP_PROFILE_C7_ULTRA_BENCH = 2, // C7 TinyHeap + ULTRA (bench 専用) + TINY_HEAP_PROFILE_CUSTOM = 3, // クラス/メタを ENV で直接指定 +}; + +// ENV: HAKMEM_TINY_HEAP_PROFILE +static inline int tiny_heap_profile_mode(void) { + static int g_mode = -1; + if (__builtin_expect(g_mode == -1, 0)) { + const char* e = getenv("HAKMEM_TINY_HEAP_PROFILE"); + if (!e || !*e) { + g_mode = TINY_HEAP_PROFILE_LEGACY; + } else if (strcasecmp(e, "C7_SAFE") == 0) { + g_mode = TINY_HEAP_PROFILE_C7_SAFE; + } else if (strcasecmp(e, "C7_ULTRA_BENCH") == 0 || strcasecmp(e, "C7_ULTRA") == 0) { + g_mode = TINY_HEAP_PROFILE_C7_ULTRA_BENCH; + } else if (strcasecmp(e, "CUSTOM") == 0) { + g_mode = TINY_HEAP_PROFILE_CUSTOM; + } else if (strcasecmp(e, "LEGACY") == 0) { + g_mode = TINY_HEAP_PROFILE_LEGACY; + } else { + int v = atoi(e); + if (v < 0) v = 0; + if (v > 3) v = 3; + g_mode = v; + } + } + return g_mode; +} + +static inline unsigned tiny_heap_profile_default_class_mask(int profile_mode) { + switch (profile_mode) { + case TINY_HEAP_PROFILE_C7_SAFE: + case TINY_HEAP_PROFILE_C7_ULTRA_BENCH: + return 1u << 7; // C7 のみ + case TINY_HEAP_PROFILE_CUSTOM: + case TINY_HEAP_PROFILE_LEGACY: + default: + return 0u; + } +} + +static inline int tiny_heap_profile_default_c7_meta_mode(int profile_mode) { + switch (profile_mode) { + case TINY_HEAP_PROFILE_C7_SAFE: + return 1; + case TINY_HEAP_PROFILE_C7_ULTRA_BENCH: + return 2; + case TINY_HEAP_PROFILE_CUSTOM: + case TINY_HEAP_PROFILE_LEGACY: + default: + return 0; + } +} + // ENV: HAKMEM_TINY_HEAP_BOX=1 で TinyHeap front を有効化 static inline int tiny_heap_box_enabled(void) { static int g_enable = -1; if (__builtin_expect(g_enable == -1, 0)) { const char* e = getenv("HAKMEM_TINY_HEAP_BOX"); + if (e && *e) { + g_enable = (*e != '0') ? 1 : 0; + } else { + // profile が LEGACY 以外なら自動で ON + g_enable = (tiny_heap_profile_mode() != TINY_HEAP_PROFILE_LEGACY) ? 1 : 0; + } + } + return g_enable; +} + +// ENV gate: C6 Hot front (直線パス) を有効化するか +static inline int tiny_c6_hot_enabled(void) { + static int g_enable = -1; + if (__builtin_expect(g_enable == -1, 0)) { + const char* e = getenv("HAKMEM_TINY_C6_HOT"); g_enable = (e && *e && *e != '0') ? 1 : 0; } return g_enable; @@ -30,12 +106,12 @@ static inline int tiny_heap_class_enabled(int class_idx) { unsigned v = (unsigned)strtoul(e, NULL, 0); g_mask = v & 0xFFu; } else { - // デフォルト: C7 のみ - g_mask = 1u << 7; + g_mask = tiny_heap_profile_default_class_mask(tiny_heap_profile_mode()); } g_parsed = 1; } + if (!tiny_heap_box_enabled()) return 0; if (class_idx < 0 || class_idx >= TINY_NUM_CLASSES) return 0; return (g_mask & (1u << class_idx)) != 0; } @@ -45,6 +121,9 @@ static inline int tiny_heap_class_route_enabled(int class_idx) { if (class_idx == 7) { return tiny_heap_box_enabled() && tiny_c7_hot_enabled() && tiny_heap_class_enabled(class_idx); } + if (class_idx == 6) { + return tiny_heap_box_enabled() && tiny_c6_hot_enabled() && tiny_heap_class_enabled(class_idx); + } return tiny_heap_box_enabled() && tiny_heap_class_enabled(class_idx); } @@ -52,3 +131,43 @@ static inline int tiny_heap_class_route_enabled(int class_idx) { static inline int tiny_c7_heap_mode_enabled(void) { return tiny_heap_class_route_enabled(7); } + +// --------------------------------------------------------------------------- +// TinyHotHeap v2 (未配線、Phase30: skeleton 用) +// --------------------------------------------------------------------------- + +// ENV: HAKMEM_TINY_HOTHEAP_V2=1 で v2 Box を有効化(デフォルト OFF) +static inline int tiny_hotheap_v2_enabled(void) { + static int g_enable = -1; + if (__builtin_expect(g_enable == -1, 0)) { + const char* e = getenv("HAKMEM_TINY_HOTHEAP_V2"); + g_enable = (e && *e && *e != '0') ? 1 : 0; + } + return g_enable; +} + +// ENV: HAKMEM_TINY_HOTHEAP_CLASSES (bitmask) - v2 用クラスマスク(デフォルト 0 = 全 OFF) +static inline int tiny_hotheap_v2_class_enabled(uint8_t class_idx) { + static int g_parsed = 0; + static unsigned g_mask = 0; + + if (__builtin_expect(!g_parsed, 0)) { + const char* e = getenv("HAKMEM_TINY_HOTHEAP_CLASSES"); + if (e && *e) { + unsigned v = (unsigned)strtoul(e, NULL, 0); + g_mask = v & 0xFFu; + } else { + g_mask = 0; // デフォルトは全 OFF(v2 は明示的に ON にする) + } + g_parsed = 1; + } + + if (!tiny_hotheap_v2_enabled()) return 0; + if (class_idx >= TINY_NUM_CLASSES) return 0; + return (g_mask & (1u << class_idx)) != 0; +} + +// C7 用 v2 gate(C7 front からのみ参照) +static inline int tiny_c7_hotheap_v2_enabled(void) { + return tiny_hotheap_v2_class_enabled(7); +} diff --git a/core/box/tiny_hotheap_v2_box.h b/core/box/tiny_hotheap_v2_box.h index b5def7ca..6c3e9c04 100644 --- a/core/box/tiny_hotheap_v2_box.h +++ b/core/box/tiny_hotheap_v2_box.h @@ -2,6 +2,9 @@ // 役割: // - TinyHotHeap v2 の型と API を先行定義するが、現行経路には配線しない。 // - HAKMEM_TINY_HOTHEAP_V2 / HAKMEM_TINY_HOTHEAP_CLASSES で将来の A/B に備える。 +// メモ (Phase35): +// - 現状の v2 は C7-only でも v1 より遅く、mixed では大きな回帰が確認されている。 +// - 研究用/実験用フラグを明示的に立てたときだけ使用し、デフォルトは OFF とする。 #pragma once #include @@ -16,15 +19,15 @@ 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) + void* freelist; + uint16_t used; + uint16_t capacity; + uint16_t slab_idx; + uint16_t flags; // future use (HOT/PARTIAL/FULL) + void* base; + struct TinySlabMeta* meta; // Superslab slab meta(Cold 側は v1 が保持) + struct SuperSlab* ss; // Owning Superslab(Cold 側は v1 に委譲) + struct tiny_heap_page_t* lease_page; // v1 側の page 構造体(freelist/used は lease_page と同期させる) struct tiny_hotheap_page_v2* next; } tiny_hotheap_page_v2; @@ -34,7 +37,7 @@ typedef struct tiny_hotheap_class_v2 { tiny_hotheap_page_v2* full_pages; uint16_t stride; uint16_t _pad; - tiny_hotheap_page_v2 storage_page; // C7 専用の 1 枚だけをまず保持 + tiny_hotheap_page_v2 storage_page; // C7 専用の 1 枚だけをまず保持(Phase36: reuse when空き) } tiny_hotheap_class_v2; typedef struct tiny_hotheap_ctx_v2 { @@ -48,6 +51,28 @@ extern __thread tiny_hotheap_ctx_v2* g_tiny_hotheap_ctx_v2; 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); +void tiny_hotheap_v2_record_route_fallback(void); +void tiny_hotheap_v2_record_free_fallback(void); + +typedef struct tiny_hotheap_v2_stats_snapshot { + uint64_t route_hits; + uint64_t alloc_calls; + uint64_t alloc_fast; + uint64_t alloc_lease; + uint64_t alloc_refill; + uint64_t alloc_fallback_v1; + uint64_t alloc_route_fb; + uint64_t free_calls; + uint64_t free_fast; + uint64_t free_fallback_v1; + uint64_t prepare_calls; + uint64_t prepare_with_current_null; + uint64_t prepare_from_partial; + uint64_t free_made_current; + uint64_t page_retired; +} tiny_hotheap_v2_stats_snapshot_t; + +void tiny_hotheap_v2_debug_snapshot(tiny_hotheap_v2_stats_snapshot_t* out); static inline void tiny_hotheap_v2_page_reset(tiny_hotheap_page_v2* page) { if (!page) return; @@ -55,6 +80,7 @@ static inline void tiny_hotheap_v2_page_reset(tiny_hotheap_page_v2* page) { page->used = 0; page->capacity = 0; page->slab_idx = 0; + page->flags = 0; page->base = NULL; page->meta = NULL; page->ss = NULL; diff --git a/core/box/tiny_route_env_box.h b/core/box/tiny_route_env_box.h new file mode 100644 index 00000000..46bcd300 --- /dev/null +++ b/core/box/tiny_route_env_box.h @@ -0,0 +1,56 @@ +// tiny_route_env_box.h - Route snapshot for Tiny front (Heap vs Legacy) +// 役割: +// - 起動時に「各クラスが TinyHeap を使うか」をスナップショットし、ホットパスでは LUT 1 回に縮約する。 +// - HAKMEM_TINY_HEAP_PROFILE / HAKMEM_TINY_HEAP_BOX / HAKMEM_TINY_HEAP_CLASSES の組合せをここで解決する。 +#pragma once + +#include +#include +#include "../hakmem_tiny_config.h" +#include "tiny_heap_env_box.h" + +typedef enum { + TINY_ROUTE_LEGACY = 0, + TINY_ROUTE_HEAP = 1, // TinyHeap v1 + TINY_ROUTE_HOTHEAP_V2 = 2, // TinyHotHeap v2 +} tiny_route_kind_t; + +extern tiny_route_kind_t g_tiny_route_class[TINY_NUM_CLASSES]; +extern int g_tiny_route_snapshot_done; + +static inline void tiny_route_snapshot_init(void) { + for (int i = 0; i < TINY_NUM_CLASSES; i++) { + if (tiny_hotheap_v2_class_enabled((uint8_t)i)) { + g_tiny_route_class[i] = TINY_ROUTE_HOTHEAP_V2; + } else if (tiny_heap_box_enabled() && tiny_heap_class_route_enabled(i)) { + g_tiny_route_class[i] = TINY_ROUTE_HEAP; + } else { + g_tiny_route_class[i] = TINY_ROUTE_LEGACY; + } + } + g_tiny_route_snapshot_done = 1; +} + +static inline tiny_route_kind_t tiny_route_for_class(uint8_t ci) { + if (__builtin_expect(!g_tiny_route_snapshot_done, 0)) { + tiny_route_snapshot_init(); + } + if (__builtin_expect(ci >= TINY_NUM_CLASSES, 0)) { + return TINY_ROUTE_LEGACY; + } + return g_tiny_route_class[ci]; +} + +static inline int tiny_route_is_heap_kind(tiny_route_kind_t route) { + return route == TINY_ROUTE_HEAP || route == TINY_ROUTE_HOTHEAP_V2; +} + +// C7 front が TinyHeap を使うか(Route snapshot 経由で判定) +static inline int tiny_c7_front_uses_heap(void) { + return tiny_route_is_heap_kind(tiny_route_for_class(7)); +} + +// C6 front が TinyHeap を使うか(Route snapshot 経由で判定) +static inline int tiny_c6_front_uses_heap(void) { + return tiny_route_is_heap_kind(tiny_route_for_class(6)); +} diff --git a/core/box/tiny_stats_box.h b/core/box/tiny_stats_box.h new file mode 100644 index 00000000..3a9a2b5c --- /dev/null +++ b/core/box/tiny_stats_box.h @@ -0,0 +1,134 @@ +// tiny_stats_box.h - Cold Tiny Stats Box +// 役割: +// - TinyHeap/TinyFront のホットパスから meta->used / ss_active_* 更新を切り出す箱。 +// - C7 SAFE の delta flush を「即時適用 or バッチ適用」に切り替える足場。 +#pragma once + +#include +#include +#include + +// 前方宣言(利用側で tiny_heap_page_t を定義済みであることが前提) +typedef struct tiny_heap_page_t tiny_heap_page_t; +typedef struct SuperSlab SuperSlab; + +// ENV: HAKMEM_TINY_STATS_BOX=1 で Stats Box 経由の更新を有効化 +static inline int tiny_stats_box_enabled(void) { + static int g = -1; + if (__builtin_expect(g == -1, 0)) { + const char* e = getenv("HAKMEM_TINY_STATS_BOX"); + g = (e && *e && *e != '0') ? 1 : 0; + } + return g; +} + +// ENV: HAKMEM_TINY_STATS_BATCH=1 で meta/active をバッチ適用(pending に貯める) +static inline int tiny_stats_batch_enabled(void) { + static int g = -1; + if (__builtin_expect(g == -1, 0)) { + const char* e = getenv("HAKMEM_TINY_STATS_BATCH"); + g = (e && *e && *e != '0') ? 1 : 0; + } + return g; +} + +typedef struct { + uint32_t alloc_delta; + uint32_t free_delta; +} tiny_stats_accum_t; + +// C7 SAFE の pending を扱うための薄い構造体(page 内に埋め込まれる想定) +typedef struct { + int32_t used_delta; + int32_t active_delta; +} tiny_stats_pending_t; + +// Hot からのイベントを受け取るフック(現状は no-op) +static inline void tiny_stats_on_alloc(int class_idx, tiny_heap_page_t* page) { + (void)class_idx; + (void)page; + if (!tiny_stats_box_enabled()) return; + // TODO: ここにクラス別の軽量カウンタを積む余地を残す +} + +static inline void tiny_stats_on_free(int class_idx, tiny_heap_page_t* page) { + (void)class_idx; + (void)page; + if (!tiny_stats_box_enabled()) return; + // TODO: ここにクラス別の軽量カウンタを積む余地を残す +} + +// Superslab active counters(既存 Box の関数をここから呼ぶ) +void ss_active_add(SuperSlab* ss, uint32_t n); +void ss_active_dec_one(SuperSlab* ss); + +static inline void tiny_stats_apply_to_meta(tiny_heap_page_t* page, int32_t used_delta, int32_t active_delta) { + if (!page || !page->meta || !page->ss) return; + if (used_delta != 0) { + atomic_fetch_add_explicit(&page->meta->used, used_delta, memory_order_relaxed); + } + 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); + } + } + } +} + +// バッチの flush 判定(force=1 で無条件 flush) +static inline void tiny_stats_maybe_flush_for_page(int class_idx, tiny_heap_page_t* page, int force) { + if (!tiny_stats_box_enabled()) return; + if (!tiny_stats_batch_enabled()) return; + if (!page) return; + (void)class_idx; + + int32_t ud = page->stats_used_pending; + int32_t ad = page->stats_active_pending; + 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; + + uint16_t cap = page->capacity; + int32_t th = (cap > 0) ? ((int32_t)cap * 16) : 256; + if (th < 256) th = 256; + + if (!force && abs_max < th) { + return; + } + + if (ud != 0 || ad != 0) { + tiny_stats_apply_to_meta(page, ud, ad); + page->stats_used_pending = 0; + page->stats_active_pending = 0; + } +} + +// flush: delta を Cold Stats 側に渡す(即時 or pending) +static inline void tiny_stats_flush_for_page(int class_idx, tiny_heap_page_t* page, int32_t used_delta, int32_t active_delta) { + if (!page) return; + if (!tiny_stats_box_enabled()) { + tiny_stats_apply_to_meta(page, used_delta, active_delta); + return; + } + if (!page->meta || !page->ss) return; + if (used_delta == 0 && active_delta == 0) { + // すでに pending があれば empty 時に flush される + if (tiny_stats_batch_enabled()) { + tiny_stats_maybe_flush_for_page(class_idx, page, page->used == 0); + } + return; + } + + if (!tiny_stats_batch_enabled()) { + tiny_stats_apply_to_meta(page, used_delta, active_delta); + return; + } + + page->stats_used_pending += used_delta; + page->stats_active_pending += active_delta; + tiny_stats_maybe_flush_for_page(class_idx, page, page->used == 0); +} diff --git a/core/front/malloc_tiny_fast.h b/core/front/malloc_tiny_fast.h index 0fc9bf90..6c2714a1 100644 --- a/core/front/malloc_tiny_fast.h +++ b/core/front/malloc_tiny_fast.h @@ -24,6 +24,7 @@ #include #include #include +#include #include // For pthread_self() in cross-thread check #include "../hakmem_build_flags.h" #include "../hakmem_tiny_config.h" // For TINY_NUM_CLASSES @@ -38,7 +39,10 @@ #include "../box/tiny_front_cold_box.h" // Phase 4-Step2: Cold Path Box #include "../box/tiny_c7_hotbox.h" // Optional: C7 専用ホットボックス #include "../box/tiny_heap_box.h" // TinyHeap 汎用 Box +#include "../box/tiny_hotheap_v2_box.h" // TinyHotHeap v2 (Phase31 A/B) #include "../box/tiny_heap_env_box.h" // ENV gate for TinyHeap front (A/B) +#include "../box/tiny_route_env_box.h" // Route snapshot (Heap vs Legacy) +#include "../box/tiny_front_stats_box.h" // Front class distribution counters // Helper: current thread id (low 32 bits) for owner check #ifndef TINY_SELF_U32_LOCAL_DEFINED @@ -98,31 +102,47 @@ static inline int front_gate_unified_enabled(void) { // __attribute__((always_inline)) static inline void* malloc_tiny_fast(size_t size) { - // 1. size → class_idx (inline table lookup, 1-2 instructions) + // size → class_idx を 1 回だけ決定 int class_idx = hak_tiny_size_to_class(size); + if (__builtin_expect(class_idx < 0 || class_idx >= TINY_NUM_CLASSES, 0)) { + return NULL; + } + tiny_front_alloc_stat_inc(class_idx); - // Optional: TinyHeap front(ENV: HAKMEM_TINY_HEAP_BOX=1 + HAKMEM_TINY_HEAP_CLASSES bitmask) - const int use_tiny_heap = (class_idx == 7) - ? tiny_c7_heap_mode_enabled() - : tiny_heap_class_route_enabled(class_idx); - if (__builtin_expect(use_tiny_heap, 0)) { - tiny_heap_ctx_t* ctx = tiny_heap_ctx_for_thread(); - if (class_idx == 7 && size == 1024) { - return tiny_c7_alloc_fast(size); + tiny_route_kind_t route = tiny_route_for_class((uint8_t)class_idx); + switch (route) { + case TINY_ROUTE_HOTHEAP_V2: { + if (class_idx == 7) { + void* v2p = tiny_hotheap_v2_alloc(7); + if (TINY_HOT_LIKELY(v2p != NULL)) { + return v2p; + } + tiny_hotheap_v2_record_route_fallback(); + } + // fallthrough to TinyHeap v1 } - return tiny_heap_alloc_class_fast(ctx, class_idx, size); + case TINY_ROUTE_HEAP: { + void* heap_ptr = NULL; + if (class_idx == 7) { + heap_ptr = tiny_c7_alloc_fast(size); + } else { + heap_ptr = tiny_heap_alloc_class_fast(tiny_heap_ctx_for_thread(), class_idx, size); + } + if (heap_ptr) { + return heap_ptr; + } + break; + } + case TINY_ROUTE_LEGACY: + default: + break; } - // 2. Phase 4-Step2: Hot/Cold Path Box - // Try hot path first (cache hit, 1 branch) + // Legacy Tiny front void* ptr = tiny_hot_alloc_fast(class_idx); if (TINY_HOT_LIKELY(ptr != NULL)) { - // Hot path: Cache hit → return USER pointer return ptr; } - - // 3. Cold path: Cache miss → refill + alloc - // noinline, cold attribute keeps this code out of hot path return tiny_cold_refill_and_alloc(class_idx); } @@ -141,7 +161,7 @@ __attribute__((always_inline)) static inline int free_tiny_fast(void* ptr) { if (__builtin_expect(!ptr, 0)) return 0; - #if HAKMEM_TINY_HEADER_CLASSIDX +#if HAKMEM_TINY_HEADER_CLASSIDX // 1. ページ境界ガード: // ptr がページ先頭 (offset==0) の場合、ptr-1 は別ページか未マップ領域になる可能性がある。 // その場合はヘッダ読みを行わず、通常 free 経路にフォールバックする。 @@ -169,6 +189,9 @@ static inline int free_tiny_fast(void* ptr) { // 4. BASE を計算して Unified Cache に push void* base = (void*)((char*)ptr - 1); + tiny_front_free_stat_inc(class_idx); + tiny_route_kind_t route = tiny_route_for_class((uint8_t)class_idx); + const int use_tiny_heap = tiny_route_is_heap_kind(route); // TWO-SPEED: SuperSlab registration check is DEBUG-ONLY to keep HOT PATH fast. // In Release builds, we trust header magic (0xA0) as sufficient validation. @@ -192,9 +215,6 @@ static inline int free_tiny_fast(void* ptr) { #endif } - const int use_tiny_heap = (class_idx == 7) - ? tiny_c7_heap_mode_enabled() - : tiny_heap_class_route_enabled(class_idx); if (__builtin_expect(g_larson_fix || use_tiny_heap, 0)) { // Phase 12 optimization: Use fast mask-based lookup (~5-10 cycles vs 50-100) SuperSlab* ss = ss_fast_lookup(base); @@ -203,6 +223,7 @@ static inline int free_tiny_fast(void* ptr) { if (__builtin_expect(slab_idx >= 0 && slab_idx < ss_slabs_capacity(ss), 1)) { uint32_t self_tid = tiny_self_u32_local(); uint8_t owner_tid_low = ss_slab_meta_owner_tid_low_get(ss, slab_idx); + TinySlabMeta* meta = &ss->slabs[slab_idx]; // LARSON FIX: Use bits 8-15 for comparison (pthread TIDs aligned to 256 bytes) uint8_t self_tid_cmp = (uint8_t)((self_tid >> 8) & 0xFFu); #if !HAKMEM_BUILD_RELEASE @@ -226,24 +247,37 @@ static inline int free_tiny_fast(void* ptr) { fflush(stderr); } #endif - TinySlabMeta* meta = &ss->slabs[slab_idx]; if (tiny_free_remote_box(ss, slab_idx, meta, ptr, self_tid)) { return 1; // handled via remote queue } return 0; // remote push failed; fall back to normal path - } else if (__builtin_expect(use_tiny_heap, 0)) { - tiny_heap_ctx_t* ctx = tiny_heap_ctx_for_thread(); - if (class_idx == 7) { - tiny_c7_free_fast_with_meta(ss, slab_idx, base); - } else { - tiny_heap_free_class_fast_with_meta(ctx, class_idx, ss, slab_idx, base); + } + // Same-thread + TinyHeap route → route-based free + if (__builtin_expect(use_tiny_heap, 0)) { + switch (route) { + case TINY_ROUTE_HOTHEAP_V2: + tiny_hotheap_v2_free((uint8_t)class_idx, base, meta); + return 1; + case TINY_ROUTE_HEAP: { + tiny_heap_ctx_t* ctx = tiny_heap_ctx_for_thread(); + if (class_idx == 7) { + tiny_c7_free_fast_with_meta(ss, slab_idx, base); + } else { + tiny_heap_free_class_fast_with_meta(ctx, class_idx, ss, slab_idx, base); + } + return 1; + } + default: + break; } - return 1; } } } if (use_tiny_heap) { // fallback: lookup failed but TinyHeap front is ON → use generic TinyHeap free + if (route == TINY_ROUTE_HOTHEAP_V2) { + tiny_hotheap_v2_record_free_fallback(); + } tiny_heap_free_class_fast(tiny_heap_ctx_for_thread(), class_idx, ptr); return 1; } diff --git a/core/hakmem_tiny.c b/core/hakmem_tiny.c index 90032430..a35563cf 100644 --- a/core/hakmem_tiny.c +++ b/core/hakmem_tiny.c @@ -147,11 +147,331 @@ static void tiny_c7_delta_debug_destructor(void) { // ============================================================================= // TinyHotHeap v2 (Phase30/31 wiring). Currently C7-only thin wrapper. +// NOTE: Phase34/35 時点では v2 は C7-only でも v1 より遅く、mixed では大きな回帰がある。 +// 実験用フラグを明示 ON にしたときだけ使う前提で、デフォルトは v1 を推奨。 // ============================================================================= -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; +static inline int tiny_hotheap_v2_stats_enabled(void) { + static int g = -1; + if (__builtin_expect(g == -1, 0)) { + const char* e = getenv("HAKMEM_TINY_HOTHEAP_V2_STATS"); + g = (e && *e && *e != '0') ? 1 : 0; + } + return g; +} + +static _Atomic uint64_t g_tiny_hotheap_v2_c7_alloc_calls = 0; +static _Atomic uint64_t g_tiny_hotheap_v2_c7_route_hits = 0; +static _Atomic uint64_t g_tiny_hotheap_v2_c7_alloc_fast = 0; +static _Atomic uint64_t g_tiny_hotheap_v2_c7_alloc_lease = 0; +static _Atomic uint64_t g_tiny_hotheap_v2_c7_alloc_fallback_v1 = 0; +static _Atomic uint64_t g_tiny_hotheap_v2_c7_alloc_refill = 0; +static _Atomic uint64_t g_tiny_hotheap_v2_c7_alloc_route_fb = 0; +static _Atomic uint64_t g_tiny_hotheap_v2_c7_free_calls = 0; +static _Atomic uint64_t g_tiny_hotheap_v2_c7_free_fast = 0; +static _Atomic uint64_t g_tiny_hotheap_v2_c7_free_fallback_v1 = 0; + +typedef struct { + _Atomic uint64_t prepare_calls; + _Atomic uint64_t prepare_with_current_null; + _Atomic uint64_t prepare_from_partial; + _Atomic uint64_t free_made_current; + _Atomic uint64_t page_retired; +} TinyHotHeapV2PageStats; + +static TinyHotHeapV2PageStats g_tiny_hotheap_v2_page_stats = {0}; + +void tiny_hotheap_v2_record_route_fallback(void) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_route_fb, 1, memory_order_relaxed); +} + +void tiny_hotheap_v2_record_free_fallback(void) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_free_fallback_v1, 1, memory_order_relaxed); +} + +void tiny_hotheap_v2_debug_snapshot(tiny_hotheap_v2_stats_snapshot_t* out) { + if (!out) return; + memset(out, 0, sizeof(*out)); + out->route_hits = atomic_load_explicit(&g_tiny_hotheap_v2_c7_route_hits, memory_order_relaxed); + out->alloc_calls = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_calls, memory_order_relaxed); + out->alloc_fast = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_fast, memory_order_relaxed); + out->alloc_lease = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_lease, memory_order_relaxed); + out->alloc_refill = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_refill, memory_order_relaxed); + out->alloc_fallback_v1 = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_fallback_v1, memory_order_relaxed); + out->alloc_route_fb = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_route_fb, memory_order_relaxed); + out->free_calls = atomic_load_explicit(&g_tiny_hotheap_v2_c7_free_calls, memory_order_relaxed); + out->free_fast = atomic_load_explicit(&g_tiny_hotheap_v2_c7_free_fast, memory_order_relaxed); + out->free_fallback_v1 = atomic_load_explicit(&g_tiny_hotheap_v2_c7_free_fallback_v1, memory_order_relaxed); + out->prepare_calls = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.prepare_calls, memory_order_relaxed); + out->prepare_with_current_null = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.prepare_with_current_null, memory_order_relaxed); + out->prepare_from_partial = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.prepare_from_partial, memory_order_relaxed); + out->free_made_current = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.free_made_current, memory_order_relaxed); + out->page_retired = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.page_retired, memory_order_relaxed); +} + +static tiny_hotheap_page_v2* tiny_hotheap_v2_acquire_page_node(tiny_hotheap_class_v2* hc) { + if (!hc) return NULL; + if (hc->storage_page.meta == NULL && hc->storage_page.freelist == NULL && + hc->storage_page.capacity == 0) { + tiny_hotheap_v2_page_reset(&hc->storage_page); + return &hc->storage_page; + } + tiny_hotheap_page_v2* node = (tiny_hotheap_page_v2*)calloc(1, sizeof(tiny_hotheap_page_v2)); + if (!node) { + return NULL; + } + tiny_hotheap_v2_page_reset(node); + return node; +} + +static tiny_hotheap_page_v2* tiny_hotheap_v2_find_page(tiny_hotheap_class_v2* hc, + uint8_t class_idx, + void* p, + TinySlabMeta* meta) { + if (!hc || !p) return NULL; + const size_t stride = hc->stride ? hc->stride : tiny_stride_for_class(class_idx); + const size_t max_span = stride * (size_t)(hc->current_page ? hc->current_page->capacity : 0); + tiny_hotheap_page_v2* candidates[3] = {hc->current_page, hc->partial_pages, hc->full_pages}; + for (int i = 0; i < 3; i++) { + for (tiny_hotheap_page_v2* page = candidates[i]; page; page = page->next) { + if (meta && page->meta && page->meta != meta) continue; + if (!page->base || page->capacity == 0) continue; + uint8_t* base = (uint8_t*)page->base; + size_t span = stride * (size_t)page->capacity; + if ((uint8_t*)p >= base && (uint8_t*)p < base + span) { + (void)max_span; // silence unused warning in case stride==0 + return page; + } + } + } + return NULL; +} + +static inline void tiny_hotheap_v2_build_freelist(tiny_hotheap_page_v2* page, + uint8_t class_idx, + uint16_t stride) { + if (!page || stride == 0) { + return; + } + if (page->used >= page->capacity) { + page->freelist = NULL; + return; + } + void* head = NULL; + size_t start = page->capacity; + while (start > page->used) { + start--; + uint8_t* block = (uint8_t*)page->base + (start * (size_t)stride); + tiny_next_write(class_idx, block, head); + head = block; + } + page->freelist = head; + if (page->lease_page) { + page->lease_page->free_list = head; + page->lease_page->used = page->used; + if (page->lease_page->meta) { + atomic_store_explicit(&page->lease_page->meta->freelist, head, memory_order_release); + if (page->lease_page->meta->carved < page->capacity) { + page->lease_page->meta->carved = page->capacity; + } + } + } +} + +static void tiny_hotheap_v2_unlink_page(tiny_hotheap_class_v2* hc, tiny_hotheap_page_v2* target) { + if (!hc || !target) return; + if (hc->current_page == target) { + hc->current_page = NULL; + } + tiny_hotheap_page_v2** lists[2] = {&hc->partial_pages, &hc->full_pages}; + for (int i = 0; i < 2; i++) { + tiny_hotheap_page_v2** head = lists[i]; + tiny_hotheap_page_v2* prev = NULL; + tiny_hotheap_page_v2* cur = *head; + while (cur) { + if (cur == target) { + if (prev) { + prev->next = cur->next; + } else { + *head = cur->next; + } + cur->next = NULL; + break; + } + prev = cur; + cur = cur->next; + } + } +} + +static tiny_hotheap_page_v2* tiny_hotheap_v2_refill_slow(tiny_hotheap_ctx_v2* ctx, uint8_t class_idx) { + if (!ctx || class_idx != 7) { + return NULL; + } + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_refill, 1, memory_order_relaxed); + TinyHeapClassStats* stats = tiny_heap_stats_for_class(7); + if (__builtin_expect(stats != NULL, 0)) { + atomic_fetch_add_explicit(&stats->alloc_slow_prepare, 1, memory_order_relaxed); + } + tiny_hotheap_class_v2* hc = &ctx->cls[class_idx]; + TinyHeapPageLease lease = tiny_heap_c7_lease_page_for_v2(); + if (!lease.page) { + return NULL; + } + + if (hc->stride == 0) { + hc->stride = (uint16_t)tiny_stride_for_class(class_idx); + } + + tiny_hotheap_page_v2* page = tiny_hotheap_v2_acquire_page_node(hc); + if (!page) { + return NULL; + } + + page->lease_page = lease.page; + page->meta = lease.meta; + page->ss = lease.ss; + page->base = lease.base; + page->capacity = lease.capacity; + page->slab_idx = lease.slab_idx; + page->freelist = lease.freelist; + page->used = lease.page->used; + if (page->lease_page) { + page->lease_page->capacity = page->capacity; + page->lease_page->free_list = page->freelist; + page->lease_page->base = (uint8_t*)page->base; + } + const uint16_t stride = hc->stride ? hc->stride : (uint16_t)tiny_stride_for_class(class_idx); + if (page->freelist == NULL && page->base && page->capacity > page->used) { + tiny_hotheap_v2_build_freelist(page, class_idx, stride); + } else if (page->lease_page && page->lease_page->meta) { + atomic_store_explicit(&page->lease_page->meta->freelist, page->freelist, memory_order_release); + } + + tiny_hotheap_page_v2* old_cur = hc->current_page; + hc->current_page = page; + page->next = NULL; + if (old_cur && old_cur != page) { + old_cur->next = hc->partial_pages; + hc->partial_pages = old_cur; + } + if (!hc->current_page || !hc->current_page->freelist || hc->current_page->capacity == 0 || + hc->current_page->used > hc->current_page->capacity) { + fprintf(stderr, "[HOTHEAP_V2_REFILL_ASSERT] current_page missing freelist (page=%p freelist=%p cap=%u used=%u)\n", + (void*)hc->current_page, + hc->current_page ? hc->current_page->freelist : NULL, + hc->current_page ? (unsigned)hc->current_page->capacity : 0u, + hc->current_page ? (unsigned)hc->current_page->used : 0u); + abort(); + } + return hc->current_page; +} + +static void tiny_hotheap_v2_page_retire_slow(tiny_hotheap_ctx_v2* ctx, + uint8_t class_idx, + tiny_hotheap_page_v2* page) { + if (!ctx || !page) return; + tiny_hotheap_class_v2* hc = &ctx->cls[class_idx]; + tiny_hotheap_v2_unlink_page(hc, page); + TinyHeapPageLease lease = tiny_heap_page_lease_nil(); + lease.page = page->lease_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->freelist; + tiny_heap_c7_return_page_from_v2(&lease); + if (page != &hc->storage_page) { + free(page); + } else { + tiny_hotheap_v2_page_reset(page); + } + if (!hc->current_page && hc->partial_pages) { + hc->current_page = hc->partial_pages; + hc->partial_pages = hc->partial_pages->next; + if (hc->current_page) { + hc->current_page->next = NULL; + } + } + if (tiny_hotheap_v2_stats_enabled()) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats.page_retired, 1, memory_order_relaxed); + } +} + +static inline void* tiny_hotheap_v2_try_pop(tiny_hotheap_page_v2* candidate, + tiny_heap_class_t* v1hcls, + TinyHeapClassStats* stats, + int stats_on) { + if (!candidate || !candidate->lease_page || !v1hcls) { + return NULL; + } + tiny_heap_page_t* ipage = candidate->lease_page; + v1hcls->current_page = ipage; // keep v1 hot page pinned to avoid mark_full churn + if (!(ipage->free_list || ipage->used < ipage->capacity)) { + return NULL; + } + void* user = tiny_heap_page_pop(v1hcls, 7, ipage); + if (!user) { + return NULL; + } + 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); + } + if (stats_on) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_fast, 1, memory_order_relaxed); + } + candidate->freelist = ipage->free_list; + candidate->used = ipage->used; + return tiny_region_id_write_header(user, 7); +} + +__attribute__((destructor)) +static void tiny_hotheap_v2_stats_dump(void) { + if (!tiny_hotheap_v2_stats_enabled()) { + return; + } + uint64_t alloc_calls = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_calls, memory_order_relaxed); + uint64_t route_hits = atomic_load_explicit(&g_tiny_hotheap_v2_c7_route_hits, memory_order_relaxed); + uint64_t alloc_fast = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_fast, memory_order_relaxed); + uint64_t alloc_lease = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_lease, memory_order_relaxed); + uint64_t alloc_fb = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_fallback_v1, memory_order_relaxed); + uint64_t free_calls = atomic_load_explicit(&g_tiny_hotheap_v2_c7_free_calls, memory_order_relaxed); + uint64_t free_fast = atomic_load_explicit(&g_tiny_hotheap_v2_c7_free_fast, memory_order_relaxed); + uint64_t free_fb = atomic_load_explicit(&g_tiny_hotheap_v2_c7_free_fallback_v1, memory_order_relaxed); + + TinyHotHeapV2PageStats ps = { + .prepare_calls = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.prepare_calls, memory_order_relaxed), + .prepare_with_current_null = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.prepare_with_current_null, memory_order_relaxed), + .prepare_from_partial = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.prepare_from_partial, memory_order_relaxed), + .free_made_current = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.free_made_current, memory_order_relaxed), + .page_retired = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.page_retired, memory_order_relaxed), + }; + + if (alloc_calls || alloc_fast || alloc_lease || alloc_fb || free_calls || free_fast || free_fb || + ps.prepare_calls || ps.prepare_with_current_null || ps.prepare_from_partial || + ps.free_made_current || ps.page_retired) { + fprintf(stderr, + "[HOTHEAP_V2_C7_STATS] route_hits=%llu alloc_calls=%llu alloc_fast=%llu alloc_lease=%llu alloc_refill=%llu alloc_fb_v1=%llu alloc_route_fb=%llu free_calls=%llu free_fast=%llu free_fb_v1=%llu prep_calls=%llu prep_null=%llu prep_from_partial=%llu free_made_current=%llu page_retired=%llu\n", + (unsigned long long)route_hits, + (unsigned long long)alloc_calls, + (unsigned long long)alloc_fast, + (unsigned long long)alloc_lease, + (unsigned long long)atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_refill, memory_order_relaxed), + (unsigned long long)alloc_fb, + (unsigned long long)atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_route_fb, memory_order_relaxed), + (unsigned long long)free_calls, + (unsigned long long)free_fast, + (unsigned long long)free_fb, + (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.free_made_current, + (unsigned long long)ps.page_retired); + } +} 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)) { @@ -161,94 +481,81 @@ tiny_hotheap_ctx_v2* tiny_hotheap_v2_tls_get(void) { abort(); } g_tiny_hotheap_ctx_v2 = ctx; - // C7 用 stride を最初にだけ設定(他クラスは未使用のまま) - ctx->cls[7].stride = (uint16_t)tiny_stride_for_class(7); + for (int i = 0; i < TINY_HOTHEAP_MAX_CLASSES; i++) { + tiny_hotheap_v2_page_reset(&ctx->cls[i].storage_page); + ctx->cls[i].stride = (uint16_t)tiny_stride_for_class(i); + } } 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); + int stats_on = tiny_hotheap_v2_stats_enabled(); + if (stats_on) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_route_hits, 1, memory_order_relaxed); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_calls, 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_class_v2* vhcls = v2ctx ? &v2ctx->cls[7] : NULL; 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; + // Hot path: current_page → partial → refill + void* user = tiny_hotheap_v2_try_pop(v2page, v1hcls, stats, stats_on); + if (user) { + return user; + } + while (vhcls && vhcls->partial_pages) { + if (stats_on) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats.prepare_calls, 1, memory_order_relaxed); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats.prepare_from_partial, 1, memory_order_relaxed); + if (vhcls->current_page == NULL) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats.prepare_with_current_null, 1, memory_order_relaxed); } } - } - - 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); + v2page = vhcls->partial_pages; + vhcls->partial_pages = vhcls->partial_pages->next; + v2page->next = NULL; 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); + user = tiny_hotheap_v2_try_pop(v2page, v1hcls, stats, stats_on); 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); + // Lease a page from v1 (C7 SAFE) and wrap it + tiny_hotheap_page_v2* leased = tiny_hotheap_v2_refill_slow(v2ctx, 7); + if (!leased || !v1hcls) { + if (stats_on) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_fallback_v1, 1, memory_order_relaxed); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_route_fb, 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 + } + vhcls->current_page = leased; + v2page = leased; + if (v1hcls && v2page && v2page->lease_page) { + v1hcls->current_page = v2page->lease_page; + } + if (stats_on) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_lease, 1, memory_order_relaxed); + } + + user = tiny_hotheap_v2_try_pop(v2page, v1hcls, stats, stats_on); + if (user) { + return user; + } + + if (stats_on) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_fallback_v1, 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); } @@ -257,26 +564,61 @@ void tiny_hotheap_v2_free(uint8_t class_idx, void* p, void* meta) { if (__builtin_expect(class_idx != 7, 0)) { return; } - atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_free, 1, memory_order_relaxed); + int stats_on = tiny_hotheap_v2_stats_enabled(); + if (stats_on) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_free_calls, 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 + tiny_hotheap_page_v2* page = tiny_hotheap_v2_find_page(vhcls, 7, p, meta_ptr); + if (page && page->lease_page && v1hcls && tiny_heap_ptr_in_page_range(page->lease_page, p)) { + tiny_heap_page_free_local(v1ctx, 7, page->lease_page, p); + page->freelist = page->lease_page->free_list; + page->used = page->lease_page->used; + if (v1hcls) { + v1hcls->current_page = page->lease_page; + } + if (vhcls && vhcls->current_page != page) { + tiny_hotheap_v2_unlink_page(vhcls, page); + page->next = vhcls->current_page; + vhcls->current_page = page; + if (stats_on) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats.free_made_current, 1, memory_order_relaxed); + } + } else if (stats_on) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats.free_made_current, 1, memory_order_relaxed); + } + // C7-only: keep the page hot even when empty to avoid churn + if (vhcls) { + if (!vhcls->current_page) { + vhcls->current_page = page; + } else if (vhcls->current_page != page) { + tiny_hotheap_v2_unlink_page(vhcls, page); + page->next = vhcls->current_page; + vhcls->current_page = page; + } + } + if (page->used == 0 && vhcls && vhcls->partial_pages != page && vhcls->current_page == page) { + // park empty page in partial to allow re-use without immediate Superslab return + page->next = vhcls->partial_pages; + vhcls->partial_pages = page; + vhcls->current_page = page; // still treat as current + } + if (stats_on) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_free_fast, 1, memory_order_relaxed); + } return; } // Fallback: mimic v1 free path - atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_free_fallback, 1, memory_order_relaxed); + if (stats_on) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_free_fallback_v1, 1, memory_order_relaxed); + } SuperSlab* ss = hak_super_lookup(p); if (ss && ss->magic == SUPERSLAB_MAGIC) { int slab_idx = slab_index_for(ss, p); diff --git a/core/hakmem_tiny_init.inc b/core/hakmem_tiny_init.inc index b79dba99..2d1895b2 100644 --- a/core/hakmem_tiny_init.inc +++ b/core/hakmem_tiny_init.inc @@ -15,6 +15,9 @@ void hak_tiny_init(void) { // Step 1: Simple initialization (static global is already zero-initialized) g_tiny_initialized = 1; + // Route snapshot (TinyHeap vs Legacy) を起動時に固定 + tiny_route_snapshot_init(); + // Reset fast-cache defaults and apply preset (if provided) tiny_config_reset_defaults(); diff --git a/core/superslab_cache.c b/core/superslab_cache.c index 36cce5e5..2222c5e2 100644 --- a/core/superslab_cache.c +++ b/core/superslab_cache.c @@ -4,6 +4,7 @@ // Date: 2025-11-28 #include "hakmem_tiny_superslab_internal.h" +#include "box/ss_os_acquire_box.h" // ============================================================================ // Cache System - Global Variables @@ -60,10 +61,12 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p -1, 0); if (ptr != MAP_FAILED) { atomic_fetch_add(&g_ss_mmap_count, 1); + ss_os_stats_record_alloc(); if (((uintptr_t)ptr & ss_mask) == 0) { ss_stats_os_alloc(size_class, ss_size); return ptr; } + ss_os_stats_record_free(); munmap(ptr, ss_size); ptr = NULL; } else { @@ -79,6 +82,7 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p -1, 0); if (raw != MAP_FAILED) { uint64_t count = atomic_fetch_add(&g_ss_mmap_count, 1) + 1; + ss_os_stats_record_alloc(); #if !HAKMEM_BUILD_RELEASE if (log_count < 10) { fprintf(stderr, "[SUPERSLAB_MMAP] #%lu: class=%d size=%zu (total SuperSlab mmaps so far)\n", @@ -98,11 +102,13 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p size_t prefix_size = aligned_addr - raw_addr; if (prefix_size > 0) { + ss_os_stats_record_free(); munmap(raw, prefix_size); } size_t suffix_size = alloc_size - prefix_size - ss_size; if (suffix_size > 0) { // 余剰領域は常に munmap して、実際に使用する SuperSlab サイズだけを残す。 + ss_os_stats_record_free(); munmap((char*)ptr + ss_size, suffix_size); } @@ -111,6 +117,7 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p // CRITICAL FIX (2025-12-05): Use MADV_POPULATE_WRITE for efficiency #ifdef MADV_POPULATE_WRITE int ret = madvise(ptr, ss_size, MADV_POPULATE_WRITE); + ss_os_stats_record_madvise(); if (ret != 0) { // Fallback: explicit memset memset(ptr, 0, ss_size); @@ -118,6 +125,7 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p #else // Fallback for kernels < 5.14 memset(ptr, 0, ss_size); + ss_os_stats_record_madvise(); #endif ss_stats_os_alloc(size_class, ss_size); diff --git a/core/superslab_stats.c b/core/superslab_stats.c index 2db4268a..d605e325 100644 --- a/core/superslab_stats.c +++ b/core/superslab_stats.c @@ -30,6 +30,11 @@ uint64_t g_ss_freed_by_class[8] = {0}; // Global counters for debugging (non-static for external access) _Atomic uint64_t g_ss_mmap_count = 0; _Atomic uint64_t g_final_fallback_mmap_count = 0; +_Atomic uint64_t g_ss_os_alloc_calls = 0; +_Atomic uint64_t g_ss_os_free_calls = 0; +_Atomic uint64_t g_ss_os_madvise_calls = 0; +_Atomic uint64_t g_ss_os_huge_alloc_calls = 0; +_Atomic uint64_t g_ss_os_huge_fail_calls = 0; // Superslab/slab observability (Tiny-only; relaxed updates) _Atomic uint64_t g_ss_live_by_class[8] = {0}; @@ -200,6 +205,35 @@ void superslab_print_global_stats(void) { pthread_mutex_unlock(&g_superslab_lock); } +// ============================================================================ +// OS call counters (optional, ENV gated) +// ============================================================================ + +static int ss_os_stats_env_enabled(void) { + static int g = -1; + if (__builtin_expect(g == -1, 0)) { + const char* e = getenv("HAKMEM_SS_OS_STATS"); + g = (e && *e && *e != '0') ? 1 : 0; + } + return g; +} + +static void ss_os_stats_dump(void) __attribute__((destructor, used)); +static void ss_os_stats_dump(void) { + if (!ss_os_stats_env_enabled()) { + return; + } + fprintf(stderr, + "[SS_OS_STATS] alloc=%llu free=%llu madvise=%llu mmap_total=%llu fallback_mmap=%llu huge_alloc=%llu huge_fail=%llu\n", + (unsigned long long)atomic_load_explicit(&g_ss_os_alloc_calls, memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_ss_os_free_calls, memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_ss_os_madvise_calls, memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_ss_mmap_count, memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_final_fallback_mmap_count, memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_ss_os_huge_alloc_calls, memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_ss_os_huge_fail_calls, memory_order_relaxed)); +} + void ss_stats_dump_if_requested(void) { const char* env = getenv("HAKMEM_SS_STATS_DUMP"); if (!env || !*env || *env == '0') { diff --git a/core/tiny_region_id.h b/core/tiny_region_id.h index 5cf99e0a..4735654e 100644 --- a/core/tiny_region_id.h +++ b/core/tiny_region_id.h @@ -168,6 +168,47 @@ static inline void watch_alloc_trigger(void* base, int class_idx, AllocSource so // ========== Write Header (Allocation) ========== +// Header write mode (bench-only switch; default FULL) +enum tiny_header_mode +{ + TINY_HEADER_MODE_FULL = 0, + TINY_HEADER_MODE_LIGHT = 1, + TINY_HEADER_MODE_OFF = 2, +}; + +static inline int tiny_header_mode(void) +{ + static int g_header_mode = -1; + if (__builtin_expect(g_header_mode == -1, 0)) + { + const char* e = getenv("HAKMEM_TINY_HEADER_MODE"); + if (e && *e) + { + char c = e[0]; + if (c == 'l' || c == 'L' || c == '1') + { + g_header_mode = TINY_HEADER_MODE_LIGHT; + } + else if (c == 'o' || c == 'O' || c == '0') + { + g_header_mode = TINY_HEADER_MODE_OFF; + } + else + { + g_header_mode = TINY_HEADER_MODE_FULL; + } + } + else + { + // Backward compatibility: HAKMEM_TINY_WRITE_HEADER=0 behaves like "off". + const char* old = getenv("HAKMEM_TINY_WRITE_HEADER"); + g_header_mode = + (old && *old && *old == '0') ? TINY_HEADER_MODE_OFF : TINY_HEADER_MODE_FULL; + } + } + return g_header_mode; +} + // Write class_idx to header (called after allocation) // Input: base (block start from SuperSlab) // Returns: user pointer (base + 1, skipping header) @@ -238,19 +279,28 @@ static inline void* tiny_region_id_write_header(void* base, int class_idx) { } while (0); #endif // !HAKMEM_BUILD_RELEASE - // P3: Header writeをデフォルトON(TLS SLL向けに常時復元、ENVでOFF可能) - // class_map があっても TLS SLL 境界でヘッダーが必要になるため、A/B 切替は - // HAKMEM_TINY_WRITE_HEADER=0 でのみ OFF(旧デフォルト)にする。 - // Memory layout preserved: user = base + 1(ヘッダー領域は常に予約) - static int g_write_header = -1; - if (__builtin_expect(g_write_header == -1, 0)) { - const char* e = getenv("HAKMEM_TINY_WRITE_HEADER"); - g_write_header = (e && *e && *e == '0') ? 0 : 1; - } - if (__builtin_expect(g_write_header, 1)) { - // Legacy mode: write header for debugging or compatibility - *header_ptr = HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK); - PTR_TRACK_HEADER_WRITE(base, HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK)); + // Header write policy (bench-only switch, default FULL) + int header_mode = tiny_header_mode(); + uint8_t desired_header = (uint8_t)(HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK)); + uint8_t existing_header = *header_ptr; + + if (__builtin_expect(header_mode == TINY_HEADER_MODE_FULL, 1)) { + *header_ptr = desired_header; + PTR_TRACK_HEADER_WRITE(base, desired_header); + } else if (header_mode == TINY_HEADER_MODE_LIGHT) { + // Keep header consistent but avoid redundant stores. + if (existing_header != desired_header) { + *header_ptr = desired_header; + PTR_TRACK_HEADER_WRITE(base, desired_header); + } + } else { // TINY_HEADER_MODE_OFF (bench-only) + // Only touch the header if it is clearly invalid to keep free() workable. + uint8_t existing_magic = existing_header & 0xF0; + if (existing_magic != HEADER_MAGIC || + (existing_header & HEADER_CLASS_MASK) != (desired_header & HEADER_CLASS_MASK)) { + *header_ptr = desired_header; + PTR_TRACK_HEADER_WRITE(base, desired_header); + } } void* user = header_ptr + 1; // skip header for user pointer (layout preserved) PTR_TRACK_MALLOC(base, 0, class_idx); // Track at BASE (where header is) @@ -272,7 +322,7 @@ static inline void* tiny_region_id_write_header(void* base, int class_idx) { // ========== END ALLOCATION LOGGING ========== // Optional guard: log stride/base/user for targeted class - if (tiny_guard_is_enabled()) { + if (header_mode != TINY_HEADER_MODE_OFF && tiny_guard_is_enabled()) { size_t stride = tiny_stride_for_class(class_idx); tiny_guard_on_alloc(class_idx, base, user, stride); } diff --git a/docs/analysis/C6_HOTBOX_DESIGN.md b/docs/analysis/C6_HOTBOX_DESIGN.md new file mode 100644 index 00000000..0bab864f --- /dev/null +++ b/docs/analysis/C6_HOTBOX_DESIGN.md @@ -0,0 +1,60 @@ +# C6_HOTBOX_DESIGN + +目的: class6 を C7 SAFE と同じ TinyHeap ベースでホット化するための箱を定義する。まずは SAFE(meta きちんと保持)だけを対象とし、ULTRA(meta を大胆に省く)は C6 ではやらない。 + +## 境界(箱の責務) +- **内側 (C6 HotBox)**: TinyHeapBox 上での heap → page → block 管理、current_page ポリシー。 +- **外側 (Cold Box)**: Superslab / Tier / Guard / Stats。TLS SLL とは原則切断(TinyHeap クラスには SLL を使わせない)。 + +## ENV / A/B 切替 +- `HAKMEM_TINY_HEAP_BOX` … TinyHeap front 全体の ON/OFF。 +- `HAKMEM_TINY_HEAP_CLASSES` … クラスごとの TinyHeap マスク。デフォルトは `0x80` (C7 のみ)。 +- `HAKMEM_TINY_C6_HOT` … C6 Hot front を有効化するスイッチ(1 で C6 専用直線パスを許可)。 +- `HAKMEM_TINY_C6_META_MODE` … 0=OFF, 1=SAFE(当面は SAFE のみ。ULTRA はやらない)。 +- **注意**: meta_mode=1 は現時点では bench/実験専用。長めの C6-heavy でクラッシュする既知事象があり、通常運用は + meta_mode=0 または C6 TinyHeap OFF を推奨する。 +- デバッグ用(meta_mode=1 時の Fail-Fast/可視化): + - `HAKMEM_TINY_C6_DEBUG_POP=1` … pop まわりの Fail-Fast を有効化。 + - `HAKMEM_TINY_C6_DELTA_TRACE=1` … delta を触った箇所を last_delta_site に記録。 + - `HAKMEM_TINY_C6_DELTA_DEBUG=1` … destructor で delta サマリを dump。 + - `HAKMEM_TINY_C6_DELTA_TRACE=1` が有効なときは delta サイト (ALLOC/FREE/ATTACH/EMPTY/THRESHOLD) をページに刻み、 + `HAKMEM_TINY_C6_DEBUG_POP=1` では pop/free の Fail-Fast(範囲外 freelist/ss mismatch/cap0 等)と 512 行までの debug log を出す。 + +## 現状の事実(2025-12 時点) +- C6 TinyHeap(`HAKMEM_TINY_HEAP_CLASSES=0x40`)は C6-heavy / Mixed どちらも回帰気味(最新測定 2025-12-05)。 + - C6-heavy (min=257/max=768, ws=256, iters=20k, debug OFF) + - LEGACY: **41.74M ops/s**(HEAP_STATS 0) + - TinyHeap mode0 (`C6_META_MODE=0`): **36.07M ops/s**(cls6 fast=5381 / slow_prepare=1) + - TinyHeap mode1 (`C6_META_MODE=1` SAFE): **28.86M ops/s**(cls6 fast=2692 / slow_prepare=2690) + - Mixed 16–1024B (ws=256, iters=20k) + - LEGACY: **40.90M ops/s** + - C7_SAFE (C6 OFF): **40.96M ops/s**(cls7 fast=5691 / slow=1) + - C6+C7 SAFE (`HEAP_CLASSES=0xC0`、両方 meta_mode=1): **27.21M ops/s**(cls6 fast=1388 / slow=1366、cls7 fast=5664 / slow=19) +- mode0 は slow_prepare≈1 でも約 -14%(C6-heavy)と大きくマイナス、mode1 は slow_prepare が増えてさらに悪化。現状は bench/実験専用マスク(0x40/0xC0)とし、通常は LEGACY か C7_SAFE のみを推奨。 +- slow_prepare が小さいまま回帰するため、原因は Front/Hot の命令コストや meta 更新コスト(delta/flush 実装)の重さにあると推定。mode1 は delta/flush を触ることで slow_prepare も増えやすい点に注意。 +- meta_mode=1 の暫定防御(Phase21–22): + - attach で meta->freelist を範囲チェックし、OOB は NULL に潰す。 + - empty→release で meta->freelist を NULL にし、debug 時は page->free_list に poison。 + - pop で freelist OOB / ss mismatch / cap0 などを Fail-Fast。 + - 上記後、C6-heavy iters=1000/1500/2000/20000 で再現していた SIGSEGV は出なくなり、delta サマリは 0/0/0(ただし bench 専用扱いは継続)。 + +## 要件と設計方針 +- 当面は **SAFE のみ**(meta/active を delta + flush で正確寄りに保つ)。ULTRA は C6 では導入しない。 +- A/B できるように: + - C6 Hot front: `HAKMEM_TINY_C6_HOT=1` で直線パスを有効化。 + - C6 TinyHeap ON/OFF: `HAKMEM_TINY_HEAP_CLASSES` のビットで切替(デフォルトは OFF)。 +- Box Theory 準拠: + - HotBox(TinyHeap 内部)と Cold Box(Superslab/Tier/Guard/Stats)の境界は 1 箇所。 + - TLS SLL は C6 HotBox からは触らない。 + +## 今後のフォーカス +- C7 SAFE で効いた current_page 固定+ delta/閾値 flush を C6 に横展開するかを検討。 +- Front/Hot の命令数を減らすため、C6 専用の直線フロント(Gate → Heap 一本化)を C7 と対称に整える。 +- 成果の指標: + - C6-heavy で LEGACY と同等以上を目指す。 + - Mixed 16–1024B で C6 を載せてもマイナスが小さい(±1M 以内)こと。 + +v1 の結論と凍結方針 +-------------------- +- v1 では C6 TinyHeap/Hot はどのモードでも C6-heavy/Mixed で明確なマイナス。meta_mode=1 は bench/実験専用とし、通常は mode0 か OFF を推奨。 +- C6 の本格的な再設計は TinyHeap v2(C5–C7 をまとめて組み直す箱)で行う。C6 を触るときは bench/実験マスク (0x40/0xC0) とデバッグ ENV を明示的に有効にする。 diff --git a/docs/analysis/C7_HOTBOX_DESIGN.md b/docs/analysis/C7_HOTBOX_DESIGN.md index 938b762b..2ae04b09 100644 --- a/docs/analysis/C7_HOTBOX_DESIGN.md +++ b/docs/analysis/C7_HOTBOX_DESIGN.md @@ -36,6 +36,21 @@ 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 箇所に集約する。 +- TinyHeap プロファイル: `HAKMEM_TINY_HEAP_PROFILE` を導入(LEGACY/C7_SAFE/C7_ULTRA_BENCH/CUSTOM)。 + - C7_SAFE: class mask=0x80, C7 meta_mode=1(SAFE)、`HAKMEM_TINY_HEAP_BOX` 自動 ON。C7_HOT は別途 1 を推奨。 + - C7_ULTRA_BENCH: class mask=0x80, C7 meta_mode=2(bench 専用)。 + - CUSTOM: 既存の `HAKMEM_TINY_HEAP_CLASSES` / `HAKMEM_TINY_C7_META_MODE` を直接指定。 + - 推奨セット: + - 本番寄せ C7: `HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_LARSON_FIX=1` + - C7-only bench: `HAKMEM_TINY_HEAP_PROFILE=C7_ULTRA_BENCH HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_LARSON_FIX=1` + - 補足: class6 にも対称の Hot front(`HAKMEM_TINY_C6_HOT` + class mask)を追加済みだが、C6 は回帰が大きく bench/実験専用のまま。 + - Phase16: Route Snapshot Box (`tiny_route_env_box.h`) を追加し、Gate は「size→class→route LUT→heap/legacy」の 1 分岐構造に整理。C7 SAFE profile でも mixed での回帰を縮めつつ、C7-only は LEGACY≈39.7M / SAFE≈41.1M / ULTRA≈46.1M (20k/ws=64, Release, LARSON_FIX=1)。 + +現状の位置づけ (Phase 19) +-------------------------- +- C7 SAFE: C7-only 20k/ws64 ≈46.6M ops/s、Mixed 16–1024B は LEGACY と ±1M 以内。C7-heavy で推奨。 +- C7 ULTRA_BENCH: C7-only 20k/ws64 ≈52M(bench 専用、Superslab/Tier 統計は緩むため本番では OFF)。 +- C6 TinyHeap: C6-heavy / Mixed で throughput が明確に悪化(例: LEGACY≈44.3M → C6 TinyHeap≈38.6M)。`HAKMEM_TINY_HEAP_CLASSES=0x40/0xC0` は bench/実験専用マスクとして扱う。 メモ ---- @@ -119,6 +134,52 @@ Phase 9: Tiny lane 判定を TinyHeap と揃える - Mixed 16–1024B: OFF≈47.6M / C7 only TinyHeap≈36.9M / C6+C7 TinyHeap≈30.3M(警告なし)。 - 依然として性能は TinyHeap OFF より低いケースがあるため、C6/C7 の slow_prepare 削減や current_page 利用強化を次フェーズで行う。 +Phase ULTRA: C7 meta モードの三段化(bench 専用 ULTRA 追加) +------------------------------------------------------------ +- ENV `HAKMEM_TINY_C7_META_MODE` を追加(0:OFF, 1:SAFE meta-light=従来の delta+閾値 flush/clamp, 2:ULTRA)。未指定時は `HAKMEM_TINY_C7_META_LIGHT` を後方互換ゲートとして扱い SAFE=1 相当。 +- ULTRA(mode=2) は C7-only ベンチ専用。per-alloc で meta->used / ss_active_* を一切更新せず、delta/flush もスキップする高速モード。2024-xx-xx 時点で pop/push の meta->freelist/carved atomic も省略し、ベンチ専用の最小オーバーヘッド構成にしている(Box 境界は維持しつつ Superslab/Tier の統計整合は犠牲にする)。 +- SAFE(mode=1) はこれまでのページ境界 flush + 閾値 flush + attach clamp を維持し、本番で使うなら mode=0/1。ULTRA は「bench/研究用途のみ」と明記。 +- C7-only 20k/ws=64 (Release, HEAP_BOX=1, HEAP_CLASSES=0x80, HOT=1, LARSON_FIX=1): + - mode=0: ≈38.7M ops/s(alloc_fast_current=10052 / alloc_slow_prepare=7681) + - mode=1: ≈34.1M ops/s(alloc_fast_current=5837 / alloc_slow_prepare=5179) + - mode=2: ≈41.6M ops/s(alloc_fast_current=5948 / alloc_slow_prepare=5068) +- current_page 可視化(Phase 11): `g_c7_page_stats` を追加し、prepare_calls / prepare_with_current_null / prepare_from_partial / current_set_from_free / current_dropped_to_partial を `HAKMEM_TINY_C7_HEAP_STATS=1` でダンプ。C7-only ULTRA (20k/ws=64) では `prepare_with_current_null=prepare_calls` となり、free 側で current_page を保持できていないことが見えたため、current 固定化ポリシーを継続検討中。 +- current 固定化の初期版(ULTRA 専用): empty/full での unlink を避け、free 後は常に current_page に据え置く + prepare で current を優先するように変更。C7-only 20k/ws=64 (mode=2) で ops≈52.0M、alloc_fast_current=11015 / alloc_slow_prepare=1 / prepare_calls=1 まで改善。SAFE への逆輸入は未定(ULTRA は bench 限定)。 +- SAFE (mode=1) への current ポリシー逆輸入 (Phase 12): + - free で used>0 の page を current に据え直し、empty でも delta flush のみで detach/publish を避けて current を保持。mark_full でも C7 meta_light が current を指す場合は unlink しない。 + - prepare_page も C7 meta_light で current に空きがあれば即 return(refill へ降りない)。 + - ベンチ (C7-only, ws=64, HEAP_BOX=1, HEAP_CLASSES=0x80, HOT=1, LARSON_FIX=1): SAFE mode=1 20k ≈46.6M ops/s(alloc_fast_current=11015 / alloc_slow_prepare=1 / free_fast_local=8726, prepare_calls=1)。長時間 100k≈46.7M / 200k≈44.99M、`HAKMEM_TINY_C7_DELTA_DEBUG=1` でも delta 残 0 を確認。 + - ULTRA(mode=2) は bench 専用のまま。本番寄り構成は mode=0/1 を推奨。 + +Phase 13: mixed での SAFE 効果と multi-class stats +--------------------------------------------------- +- Stats を `TinyHeapClassStats[]` に拡張(ENV: `HAKMEM_TINY_HEAP_STATS` / `_DUMP`、旧 `_C7_` 互換)し、C6/C7 の fast/slow/fallback を同時に計測可能にした。 +- 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): + - TinyHeap OFF: ≈43.8M ops/s。 + - C6 TinyHeap のみ: ≈38.5M ops/s(`HEAP_STATS[6] fast=5372 slow=1`)。 + - C6+C7 TinyHeap: ≈40.6M ops/s(`HEAP_STATS[6] fast=5372 slow=1`, `HEAP_STATS[7] fast=5691 slow=1`)。 +- 方針: C7 SAFE は mixed でも悪化せず、C7-only では legacy 超え → デフォルト TinyHeap 候補。C6 は slow_prepare 自体は少ないが経路オーバーヘッドで低下するため、当面は bench/実験用 (HEAP_CLASSES=0x40/0xC0)。C7-only を突き抜けるベンチは ULTRA (META_MODE=2) を手動で使う。 + +Phase 17: C7 フロント超直線化(HotPipeline 前倒し) +---------------------------------------------------- +- Route Snapshot を使った C7 判定ヘルパ `tiny_c7_front_uses_heap()` を追加。Gate から class7 が TinyHeap 経路かどうかを 1 LUT で判定できるようにした。 +- `malloc_tiny_fast` の冒頭に「size==1024 かつ C7 route=HEAP」専用パスを追加。クラス判定/LUT/route を飛ばして `tiny_c7_alloc_fast` へ直行し、miss 時だけ Legacy Tiny slow (`tiny_cold_refill_and_alloc(7)`) に静かにフォールバック。 +- `free_tiny_fast` も C7 route=HEAP を先に評価し、Larson fix ブロックの owner 判定後に `tiny_c7_free_fast_with_meta` へ直行する経路を明示(route はスナップショットから 1 回だけ読む)。 +- ベンチ (Release, iters=20k, ws=64, LARSON_FIX=1, HOT=1): + - C7-only: 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 で詰めるかを次フェーズで決める。 + +Phase 18 メモ(C6 SAFE 実験とプロファイル整理) +------------------------------------------------ +- TinyHeap プロファイル箱に加え、`HAKMEM_TINY_C6_META_MODE`(0/1)を追加。現状の C6 SAFE は整合優先で挙動は mode 0 相当(delta/flush 未使用、meta/active は per-alloc 更新)だが、クラスマスクで C6 を TinyHeap に載せたまま A/B 計測できる。 +- C6 偏重 20k/ws=256 では LEGACY ≈44.3M → C6 TinyHeap (mask=0x40, META_MODE=0/1) ≈38.6〜38.8M、C6+C7 TinyHeap (0xC0, META_MODE=1) ≈39.9M。Mixed 16–1024B でも C6 TinyHeap は ≈38.5〜38.7M、C6+C7 TinyHeap ≈39.5M(slow_prepare はいずれも ≈1 と低い)。 +- デフォルト profile は引き続き C7 SAFE / C7 ULTRA_BENCH / LEGACY の 3 択。C6 を TinyHeap に載せるのは bench/研究用(クラスマスク 0x40/0xC0 を明示)とし、本番では 0x80=C7 のみを推奨。C6 向け meta-light を安全に再導入する場合は Superslab 解放まわりの安全性を再確認する。 + TinyHeapBox への載せ替え(Phase 1.0 構造) ------------------------------------------ - C7HotBox の実体を `core/box/tiny_heap_box.h` の汎用 TinyHeapBox 上に配置し、型は `tiny_heap_ctx_t` / `tiny_heap_page_t` へ統一。 diff --git a/docs/analysis/COLD_TINY_STATS_BOX_DESIGN.md b/docs/analysis/COLD_TINY_STATS_BOX_DESIGN.md new file mode 100644 index 00000000..01399587 --- /dev/null +++ b/docs/analysis/COLD_TINY_STATS_BOX_DESIGN.md @@ -0,0 +1,74 @@ +Cold Tiny Stats Box (初期メモ) +============================= + +目的 +---- +- Tiny のホットパス (TinyHeap/Front) では「page->used の増減だけ」に集中し、Superslab/Tier/Guard 用の meta->used / ss_active_* / 学習系カウンタの更新は Cold 側に押し出す。 +- C7 SAFE で導入した page 内 delta + flush を一般化し、「Stats Box」がイベントを受け取って統計を更新する構造に寄せる。 + +境界の考え方 +------------ +- Hot 側 (TinyHeap / TinyFront / HotBox): + - やること: page->used の増減、必要なら「EVENT_ALLOC / EVENT_FREE」などの軽い通知を 1 回投げるだけ。 + - やらないこと: meta->used / ss_active_* / Tier 判定用カウンタを直接いじる。 +- Cold Stats Box 側: + - Hot からのイベントをバッファ or 簡易カウンタで受け取り、 + - Superslab/Tier/Guard が参照する統計値に反映する(当面は flush 境界で即時更新。次フェーズでバッチ化を検討)。 + +C7 SAFE との関係 +---------------- +- C7 SAFE ではすでに page 内に used_delta/active_delta を持ち、empty/threshold/attach 時に flush して meta/active を更新する「ミニ Aggregator」がある。 +- 本フェーズ以降は「flush 部分だけを Stats Box に委譲」→「Stats Box で pending に貯め、所定のトリガでまとめて meta/active を反映」という二段構えに進化させる。 +- 将来的には Stats Box 内で本格的なバッチ更新・学習カウンタ更新を担う余地を残す。 + +遅延の許容と不変条件(C7 基準) +-------------------------------- +- Superslab/Tier/Guard が前提にする不変条件: + - meta->used / ss_active_* は「ページが Superslab を離れる前」に必ず整合した値に戻っていること。 + - Tier 判定や Guard/OOM 判定は「一時的に +α(最大 K ページぶん)」までなら許容するが、K は環境から明示的に決める。 +- 今回の方針: + - 1 ページあたり最大「capacity×16」程度までの pending なら遅延を許容(ENV で ON のときのみ)。 + - ページが empty になり Superslab/Tier に返る直前、または pending が閾値を超えたときに必ず flush。 + - Superslab を完全 release する前に pending=0 になる(empty で flush を強制する)ため、Tier/Guard の破綻は避ける。 + +ENV / A/B ポリシー +------------------ +- HAKMEM_TINY_STATS_BOX=0/1 で Stats Box 経由を切替。 + - 0: これまで通り TinyHeap 内で meta/active を直接更新。 + - 1: C7 SAFE の flush を Stats Box 経由に分離。 +- HAKMEM_TINY_STATS_BATCH=0/1 で meta/active の適用タイミングを切替(Stats Box が有効な場合のみ有効)。 + - 0: flush ごとに即 meta->used / ss_active_* を更新(従来挙動)。 + - 1: flush で得た delta を page pending に貯め、閾値/empty/release/destructor でまとめて反映。 +- 当面は C7 のみ対象。他クラスや C6 への適用は次フェーズ以降の検討事項。 + +初期 A/B(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) +- いずれも delta summary=0 で挙動差なし(場所だけ Box に分離)。 + +今フェーズの実装(概要) +------------------------- +- Stats Box に page 単位の pending(used_delta/active_delta) を追加。 +- C7 SAFE の flush は「delta を Stats Box に渡すだけ」に変更し、Stats Box が + - STATS_BATCH=0: 即座に meta/active 更新。 + - STATS_BATCH=1: pending に加算し、閾値超え/empty でまとめて meta/active へ反映。 +- Hot 側(TinyHeap/C7 SAFE)のコード量は変えず、Cold 側だけで更新タイミングを差し替えられる形にした。 + +Phase27 A/B(バッチ有効時の影響) +--------------------------------- +- C7-only 20k/ws=64(PROFILE=C7_SAFE, HOT=1, LARSON_FIX=1, HEAP_STATS=ON) + - STATS_BOX=0: 43.31M ops/s + - STATS_BOX=1, BATCH=0: 43.06M ops/s(fast/slow 同一) + - STATS_BOX=1, BATCH=1: 35.10M ops/s(大幅マイナス) + - STATS_BOX=1, BATCH=1, META_MODE=2(ULTRA bench): 48.55M ops/s +- Mixed 16–1024B 20k/ws=256 + - 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 +- 判断: BATCH=1 は C7-only/Mixed とも大きく劣化。bench 専用に留め、標準は STATS_BOX=1 & BATCH=0(または STATS_BOX=0)とする。 + +標準設定 (v1) +-------------- +- 推奨: PROFILE=C7_SAFE 時は `HAKMEM_TINY_STATS_BOX=1 HAKMEM_TINY_STATS_BATCH=0` を標準とし、レガシー比較時は STATS_BOX=0 でも可。 +- Bench/実験: `HAKMEM_TINY_STATS_BATCH=1` は bench 専用(C7-only/Mixed とも大幅マイナス)。C7_ULTRA_BENCH で試すときのみ併用を許容。 diff --git a/docs/analysis/FINAL_PERF_STATUS_2025XX.md b/docs/analysis/FINAL_PERF_STATUS_2025XX.md new file mode 100644 index 00000000..89afccbd --- /dev/null +++ b/docs/analysis/FINAL_PERF_STATUS_2025XX.md @@ -0,0 +1,31 @@ +# FINAL_PERF_STATUS_2025XX + +箱理論で進めた v1 系の最終スナップショット。ベスト構成と残差、次の大きなテーマを一枚にまとめる。 + +## ベスト構成(デフォルト想定) +- Tiny: `HAKMEM_TINY_HEAP_PROFILE=C7_SAFE`, `HAKMEM_TINY_HOTHEAP_V2=0`, `HAKMEM_TINY_STATS_BOX=1`, `HAKMEM_TINY_STATS_BATCH=0`, `HAKMEM_TINY_LARSON_FIX=1` +- Pool: `HAKMEM_POOL_V2_ENABLED=0`(回帰防止のため v1 を標準) +- HugePage/ヘッダ-light/実験系: すべて OFF(研究箱扱い) + +## 性能一覧(リリースビルド、代表プロファイル) +- C7-only (ws=64, iters=20k): ≈40.6M ops/s + - mimic/system との差: mimalloc ≈112.9M, system ≈92.1M → HAKMEM は mimic の ~36%、system の ~44% +- Mixed 16–1024B (ws=256, iters=20k): ≈38–39M ops/s(v2 OFF, C7_SAFE) + - mimalloc/system は未計測同条件だが C7-only 比率から推定し ~40% 前後 +- mid/smallmid (bench_mid_large_mt_hakmem 1 1,000,000 400 1, v2 OFF): ≈27.4–28.4M ops/s + - mimalloc ≈54.2M → HAKMEM は ~50% 近辺 + +## 埋めた「大きな穴」 +- Tiny front/route フラット化(size→class→route→alloc/free を 1 LUT+switch に縮約) +- Warm Pool / Superslab OS stats で pf/sys の大穴を特定し、OS alloc/free はほぼゼロ化 +- SS_OS_STATS/Cold Stats Box で Superslab⇔OS/Stats を可視化し、デフォルトは A/B で安全側 +- C7 SAFE TinyHeap を標準に固定(v2 は研究箱)。Tiny v2 は完全ゲート付きでデフォルト OFF +- Pool v1/v2 ゲート追加:`HAKMEM_POOL_V2_ENABLED=0` を標準にし、回帰を即切り戻せるようにした + +## ここから先(v3 テーマ候補) +- TinyHeap v2 をゼロから設計(C5–C7 を統合する HotHeap、現在の実験 v2 とは別物) +- First-touch / header-light / HugePage の本格対応(bench 専用から昇格させるか検証) +- mid/smallmid の pool 系さらなる軽量化 or 別フロント +- Tiny front 以外(mid/large)での route フラット化・命令数削減 + +備考: v2 系・ヘッダ light・HugePage はすべて「明示的に ENV を立てた研究モード」のまま据え置き。標準ベンチ/比較は上記ベスト構成で見る。*** diff --git a/docs/analysis/FIRST_TOUCH_PAGEFAULT_REDUCTION_PLAN.md b/docs/analysis/FIRST_TOUCH_PAGEFAULT_REDUCTION_PLAN.md new file mode 100644 index 00000000..16072231 --- /dev/null +++ b/docs/analysis/FIRST_TOUCH_PAGEFAULT_REDUCTION_PLAN.md @@ -0,0 +1,51 @@ +# FIRST_TOUCH_PAGEFAULT_REDUCTION_PLAN + +## 現状サマリ(基準プロファイル: 16–1024B, ws=400, iters=1M, C7_SAFE, v2 OFF) +- HAKMEM: ~6,600 page-faults / ~40.6M ops/s, user≈29 ms / sys≈18.5 ms +- mimalloc: ~150 page-faults / ~112.9M ops/s +- system malloc: ~130 page-faults / ~92.1M ops/s +- Superslab OS 呼び出し(HAKMEM_SS_OS_STATS=1): alloc=2 / free=3 / madvise=2 → OS mmap/munmap は支配的でない +- WarmPool (C7): ヒット ~99% でほぼ効いている。残存 pf は first-write が主因と推定。 + +## 方針(研究用 / デフォルト OFF) +- Mode A: Superslab を HugePage / 大きめサイズで確保し、first-write そのものを減らす実験 +- Mode B: allocator 側の初期書き込み(ヘッダ/ゼロ埋め)を減らし、first-touch をアプリ側に寄せる実験 +- いずれも ENV で opt-in。デフォルトは OFF(本番プロファイルは現状維持)。 + +## Mode A: HugePage / 大きめ Superslab 実験 Box(実験実装済み) +- Box 案: SuperSlabHugePageBox(Cold 側で実験用) +- ENV: + - `HAKMEM_SS_HUGEPAGE_EXPERIMENT=1` で HugePage を試行(デフォルト 0) + - `HAKMEM_SS_HUGEPAGE_SIZE` でページサイズ指定(未指定なら 2MB を仮定) +- 実装(Phase52): + - ss_os_acquire が ENV ON のときだけ `MAP_HUGETLB`(`MAP_HUGE_2MB` も併用できる環境では付与)を試し、失敗時は静かに通常 mmap にフォールバック。Stats で huge_alloc / huge_fail を出力。 + - Superslab サイズが 2MB と一致するときのみ HugePage を試す(その他サイズは従来経路)。 + +## Mode B: allocator 側 first-write 削減案 +- ヘッダ書き込みを減らすオプション(C5/C6/C7 region_id/guard ヘッダを実験的に省略)。安全性とトレードオフなので研究用 BOX (FirstTouchPolicyBox) に閉じ込める。 +- ゼロ初期化の重複を洗う: + - どこで memset/clear をしているか(Tiny front / mid / Superslab)を列挙し、アプリが必ず書く領域を allocator 側で二重初期化していないか確認。 +- Box Theory ルール: + - ポリシーは FirstTouchPolicyBox に集約し、ホットパスは policy snapshot を読むだけにする。 +- Tiny ヘッダ/初期化の 3 モード(bench 専用 ENV 実装済み): + - `full`(デフォルト): region_id_write_header を常に書き戻す(従来どおり)。 + - `light`: 既存ヘッダと一致する場合は再書き込みを避け、必要最小限だけ書く。 + - `off`(bench 専用): 既存ヘッダが正しければ書かず、壊れているときだけ最小限を書き戻す(free の整合性確保のための最低限)。guard/memset もスキップ。 + - ENV: `HAKMEM_TINY_HEADER_MODE=full|light|off`(未指定は full)。旧 `HAKMEM_TINY_WRITE_HEADER=0` は `off` 相当として互換維持。 +- 実験結果(Mixed 16–1024B, ws=400, iters=1M, C7_SAFE, v2 OFF): + - HEADER_MODE=full: ≈42.40M ops/s, page-faults ≈6,662, cycles ≈176M。 + - HEADER_MODE=light: ≈38.75M ops/s, page-faults ≈6,661, cycles ≈187M。 + - HEADER_MODE=off: ≈39.33M ops/s, page-faults ≈6,662, cycles ≈184M。 +- 所感: + - pf 回数は 3 モードとも ≈6.66k でほぼ同一。ヘッダ write 軽量化では first-touch page-fault は減らない。 + - cycles/ops は full が最良で、light/off は判定・分岐コストの増加により約 7〜9% の性能低下。 + - 現時点では運用デフォルトは full のままが最良。light/off は bench 専用の research モードとして維持し、本番プロファイルでは使用しない。 + +## 評価プロファイル(pf/sys A/B 用に固定) +- プロファイル: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1` +- メトリクス: ops/s, page-faults, user/sys(perf stat: cycles,instructions,task-clock,page-faults) +- このプロファイルで Mode A/B を A/B し、pf がどこまで下がるかを見る。 + +## ガードレール +- HugePage / header-skip / zero-skip は研究専用でデフォルト OFF。ENV 明示なしでは有効化しない。 +- 本番プロファイルに入れる場合は Fail-Fast / Guard(明示 opt-in)と長時間安定性テストを必須にする。 diff --git a/docs/analysis/MID_LARGE_CPU_HOTPATH_ANALYSIS.md b/docs/analysis/MID_LARGE_CPU_HOTPATH_ANALYSIS.md new file mode 100644 index 00000000..700ac7ac --- /dev/null +++ b/docs/analysis/MID_LARGE_CPU_HOTPATH_ANALYSIS.md @@ -0,0 +1,46 @@ +# MID/Large CPU Hotpath (Phase54) + +- 条件: `./bench_mid_large_mt_hakmem 1 1000000 400 1` (Release) + `HAKMEM_TINY_HEAP_PROFILE=C7_SAFE`, `HAKMEM_TINY_C7_HOT=1`, `HAKMEM_TINY_HOTHEAP_V2=0`, `HAKMEM_TINY_LARSON_FIX=1` +- スループット: HAKMEM ≈28.1M ops/s(mimalloc 54.2M / system 15.3M は Phase53 より) +- perf stat (cycles:u): IPC≈2.4、page-faults≈7.4k(Phase53 と同等) + +## cycles:u ホットシンボル(self%) +- hak_pool_try_alloc.part.0 … 14.7% (pool alloc ホットパス) +- worker_run … 9.2% (ドライバ側のループと malloc 呼び出しを含む) +- free / hak_free_at.constprop.0 … ~9–10%(glibc free 連携+自前 free) +- __memset_avx2_unaligned_erms … ~9%(pool 初期化/clear と推定) +- mid_desc_lookup … 3.8% +- hak_super_lookup … 1.4% +- hak_pool_free.part.0 … 0.7% + +## 所感 +- pool 系(hak_pool_try_alloc / hak_pool_free)と free/memset が支配的で、mid_desc_lookup と super_lookup も目立つ。 +- kernel 枠の大きな inclusive% は free/memset 配下にぶら下がっており、userland 側の pool/front の命令数削減が効果的そう。 + +## Phase55 方針(pool allocator ホットパス軽量化) +- スコープ: core/hakmem_pool.c / core/hakmem_smallmid.c / core/box/pool_* の pool fast path。 +- hak_pool_try_alloc を直線化: + - 「TLS/local freelist hitなら即 return」を先頭に寄せ、debug/統計/slow は unlikely 側へ。 + - mid_desc_lookup/クラス情報は入口で 1 回だけ計算し、TLS へのキャッシュを検討。 +- hak_pool_free / hak_free_at: + - self-thread free は pool push を最優先にし、cross-thread/debug は unlikely に寄せる。 + - free 時の memset/初期化が不要なケースを洗い出し、スキップ余地をメモ。 +- mid_desc_lookup のキャッシュ: + - size→class→desc を入口で 1 回だけ決める仕組み(既存キャッシュの再利用も含め)を検討。 +- 成功ライン(bench_mid_large_mt_hakmem 1 1000000 400 1, Release): + - ops/s を 28–29M → 30–32M(+5〜10%)へ。 + - perf report で hak_pool_try_alloc + free 周辺 self% が数ポイント低下。 + +## Phase56 結果(pool fast path 初期実装) +- 変更: PoolBlock→user 変換をヘルパに寄せ、TLS ring/lo pop と self-thread free push を直線化。owner 判定は mid_desc の 1 回 lookup で共有。 +- ベンチ(C6-heavy, ws=256, iters=20k, C7_SAFE, v2 OFF): **25.9M ops/s**(目標 30–32M に届かず、前回 28–29M から回帰)。 +- perf stat(同条件 1M/400): cycles=225,766,496、instructions=528,335,613、task-clock=57.88ms、ops/s≈25.7M。 +- 所感: fast-path整理だけでは効果薄く、むしろ低下。pool 内の memset/desc まわりやリング補充順序をより大胆に削らないと改善しない。次のステップとして追加の枝削減・キャッシュ導入を検討。 + +## Phase57 回帰トリアージ(pool v2 をゲート化) +- 変更: `HAKMEM_POOL_V2_ENABLED` を追加し、v1/v2 の pool 実装を env で切替。細分スイッチとして `HAKMEM_POOL_V2_BLOCK_TO_USER` / `HAKMEM_POOL_V2_TLS_FAST_PATH` を用意(デフォルト ON, v2 時のみ有効)。 +- ベンチ(C6-heavy, 1M/400, Release): + - v1 (POOL_V2_ENABLED=0): **27.40M ops/s** + - v2 (POOL_V2_ENABLED=1): **24.73M ops/s** +- 所感: v2 の変更が回帰要因と判明。標準は v1 に戻し、スイッチ単位の A/B でどの改変が悪いかを切り分ける方針。 diff --git a/docs/analysis/PERF_ANALYSIS_MID_LARGE_PHASE53.md b/docs/analysis/PERF_ANALYSIS_MID_LARGE_PHASE53.md new file mode 100644 index 00000000..dd292ecd --- /dev/null +++ b/docs/analysis/PERF_ANALYSIS_MID_LARGE_PHASE53.md @@ -0,0 +1,29 @@ +# Phase53: Mid/Smallmid ベンチ簡易計測(シングルスレッド) + +- 条件: 1 thread / cycles=1,000,000 / ws=400 / reps=1 +- ビルド: `make bench_mid_large_mt_hakmem bench_mid_large_mt_mi bench_mid_large_mt_system -j4` +- プロファイル: TinyHotHeap v2 OFF、C7_SAFE プロファイル、LARSON_FIX=1、その他デフォルト + +## スループット比較 + +- HAKMEM: `./bench_mid_large_mt_hakmem 1 1000000 400 1` → **28.43M ops/s**(別 run perf 時 29.02M) +- mimalloc: `./bench_mid_large_mt_mi 1 1000000 400 1` → **54.22M ops/s** +- system malloc: `./bench_mid_large_mt_system 1 1000000 400 1` → **15.29M ops/s** + +## perf stat (HAKMEM, user+sys) + +- コマンド: `perf stat -e cycles,instructions,task-clock,page-faults ./bench_mid_large_mt_hakmem 1 1000000 400 1` +- 結果: + - cycles: 211,394,722 + - instructions: 513,342,673 (IPC ≈ 2.43) + - task-clock: 57.48 ms (user 33.29 ms / sys 25.22 ms) + - page-faults: 7,374 + - スループット: 29.02M ops/s(計測 run の値) + +## 所感 / 次の判断材料 + +- mid/smallmid (257–768B主体) では mimalloc が HAKMEM の約 1.9×。system はさらに低い。 +- page-faults は 7.3k と Tiny (16–1024B) よりやや多めだが、主因は CPU 側命令量と sys 部分の比率。 +- 次の選択肢: + - mid/smallmid パス(Tiny 以外)のホットパスを狙う箱 + - もしくは Tiny 側での残り課題と比較して優先度を決める材料に。 diff --git a/docs/analysis/PERF_ANALYSIS_TINY_FRONT_FLATTEN_PHASE42.md b/docs/analysis/PERF_ANALYSIS_TINY_FRONT_FLATTEN_PHASE42.md new file mode 100644 index 00000000..1dcd148e --- /dev/null +++ b/docs/analysis/PERF_ANALYSIS_TINY_FRONT_FLATTEN_PHASE42.md @@ -0,0 +1,205 @@ +# Phase42 Front Route Flatten ベースライン計測 + +- ビルド: `make bench_random_mixed_hakmem -j4` + +## C7-only (ws=64, iters=20000, v2 OFF) +- ENV: `HAKMEM_BENCH_C7_ONLY=1 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_STATS_BOX=1 HAKMEM_TINY_STATS_BATCH=0 HAKMEM_TINY_LARSON_FIX=1` +- 結果: `Throughput = 41365988 ops/s` +- 備考: HEAP_STATS のダンプは出ず(要調査)。FRONT_CLASS: cls7 alloc=11016。 + +## Mixed 16–1024B (ws=256, iters=20000, v2 OFF) +- ENV: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_FRONT_CLASS_STATS=1` +- 結果: `Throughput = 43538552 ops/s` +- 備考: FRONT_CLASS alloc counts(cls2:147, cls3:341, cls4:720, cls5:1420, cls6:2745, cls7:5692)。HEAP_STATS ダンプは出ず。 + +## Tiny-only 8–128B (ws=256, iters=20000, v2 OFF) ※任意測定 +- ENV: `HAKMEM_BENCH_MIN_SIZE=8 HAKMEM_BENCH_MAX_SIZE=128 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_FRONT_CLASS_STATS=1` +- 結果: `Throughput = 65979164 ops/s` +- 備考: FRONT_CLASS alloc counts(cls2:147, cls3:341, cls4:720, cls5:9857)。HEAP_STATS ダンプは出ず。 + +--- + +# Phase43 (HEAP_STATS 再有効化の再計測) ※v2 OFF + +- HEAP_STATS 用 ENV を明示 (`HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1`) したが、現状はダンプが出ていない。fast/slow の値は別途要確認。 + +## C7-only (ws=64, iters=20000) +- ENV: `HAKMEM_BENCH_C7_ONLY=1 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_STATS_BOX=1 HAKMEM_TINY_STATS_BATCH=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1 HAKMEM_TINY_FRONT_CLASS_STATS=1` +- Throughput: `37,777,547 ops/s` +- HEAP_STATS cls7: fast=11015 slow=1 / free_fast_local=8726、C7_PAGE_STATS: prepare_calls=1 prepare_with_current_null=1 +- FRONT_CLASS: cls7 alloc=11016 free=8726 + +## Mixed 16–1024B (ws=256, iters=20000) +- ENV: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1 HAKMEM_TINY_FRONT_CLASS_STATS=1` +- Throughput: `42,029,698 ops/s` +- HEAP_STATS cls7: fast=5691 slow=1 / free_fast_local=4573(cls5/6 未出力) +- FRONT_CLASS alloc: cls2=147 cls3=341 cls4=720 cls5=1420 cls6=2745 cls7=5692 + +## Tiny-only 8–128B (ws=256, iters=20000) +- ENV: `HAKMEM_BENCH_MIN_SIZE=8 HAKMEM_BENCH_MAX_SIZE=128 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1 HAKMEM_TINY_FRONT_CLASS_STATS=1` +- Throughput: `65,890,043 ops/s` +- HEAP_STATS: 出力なし(cls7 も 0)→ クラス0〜6は計測対象外の可能性。要調査。 + +### メモ +- C7-only では HEAP_STATS/PAGE_STATS が出ることを確認。Mixed/Tiny-only では cls7 以外の HEAP_STATS が落ちておらず、環境/計測対象の条件を再確認する余地あり。 + +# Phase43'': 計測専用で C6/C7 を TinyHeap に載せた場合(v2 OFF) + +## Mixed 16–1024B (ws=256, iters=20000, class mask=0xC0, C6+C7) +- ENV: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HEAP_CLASSES=0xC0 HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_C6_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1 HAKMEM_TINY_FRONT_CLASS_STATS=1` +- Throughput: `32,227,500 ops/s`(性能は大幅に低下、計測専用) +- HEAP_STATS: + - cls6: fast=2744 slow=1 free_fast_local=2745 + - cls7: fast=5691 slow=1 free_fast_local=4572 +- FRONT_CLASS alloc: cls2=147 cls3=341 cls4=720 cls5=1420 cls6=2745 cls7=5692 + +## Tiny-only 8–128B (ws=256, iters=20000, class mask=0xC0, C6+C7) +- ENV: `HAKMEM_BENCH_MIN_SIZE=8 HAKMEM_BENCH_MAX_SIZE=128 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HEAP_CLASSES=0xC0 HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_C6_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1 HAKMEM_TINY_FRONT_CLASS_STATS=1` +- Throughput: `63,341,251 ops/s` +- HEAP_STATS: 出力なし(対象クラスが 5 未満中心のため?) + +### 所感 +- C6 を TinyHeap 経由にすると Mixed で大きくマイナス(32M)。cls6/7 の slow はいずれも 1 なので、性能低下はフロント/ホットパスの命令数増が主因と見られる。 +- 計測目的でのみクラスマスクを広げるのは有効だが、本番プロファイルは C7 のみ TinyHeap (0x80) を維持するのが安全。 + +# Phase48: first-touch/page-fault 実験用プロファイルの固定 +- 評価は以下で統一し、pf/sys A/B の基準にする: + - `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1` + - perf stat メトリクス: ops/s, page-faults, task-clock(user/sys), cycles, instructions +- 目的: HugePage/first-touch 削減などの実験をこのプロファイルで比較し、pf がどれだけ減るかを確認する。 + +# Phase45: 16–1024B perf stat A/B(HAKMEM vs mimalloc vs system, ws=400, iters=1,000,000, v2 OFF, C7 SAFE) + +共通ENV: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024` + +| Allocator | Throughput (ops/s) | cycles | instructions | IPC | task-clock | user/sys | page-faults | +|-----------|-------------------:|-------:|-------------:|----:|-----------:|---------:|------------:| +| HAKMEM (C7_SAFE, v2 OFF) | 40,623,450 | 181,136,824 | 372,126,696 | 2.05 | 46.82 ms | 29.4 ms / 18.5 ms | 6,659 | +| mimalloc | 112,895,946 | 38,800,615 | 59,184,115 | 1.53 | 10.66 ms | 10.31 ms / 1.41 ms | 146 | +| system malloc | 92,111,481 | 52,144,974 | 107,607,240 | 2.06 | 12.54 ms | 12.27 ms / 1.36 ms | 133 | + +所感/次手候補: +- HAKMEM は sys 時間と page-fault が顕著(pf が mimallocの ~45x, sys 時間が高い)。pf/sys 寄りのボトルネックが支配的に見える。 +- 次の箱候補: + - pf/sys 改善を狙う: Superslab/Warm Pool/pagefault 周りのCold Box見直し。 + - 命令数削減: front整理は済みなので、Tiny v1ホットパスや mid/large を軽くする路線も候補。 + +### メモ +- HEAP_STATS(fast/slow)のダンプが出ない状態。今後のベンチでは `HAKMEM_TINY_HEAP_STATS[_DUMP]` の有効化経路を再確認する必要あり。 + +# Phase46: pf/sys 内訳の初期確認(HAKMEM, 16–1024B, ws=400, iters=1,000,000, v2 OFF, C7_SAFE) + +- `perf record -o perf.data.pf -e page-faults -- ./bench_random_mixed_hakmem 1000000 400 1` +- `perf report --stdio -i perf.data.pf` +- サマリ: page-fault サンプルの ~96% が `__memset_avx2_unaligned_erms`(初回タッチ系)。カーネル側の munmap/madvise 由来はほぼ見えず、残っている 6.6k faults は first-write が中心と推定。 +- 基準プロファイル(以降 pf/sys A/B の基準にする想定): + - `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1` +- TODO / 次の箱候補: + - WarmPool / Superslab 再利用が十分効いているかの stats 確認(該当 ENV を後続で調査)。 + - empty Superslab を即 OS 返却していないかのポリシー確認。 + - pf/sys 系の変更は上記プロファイルで揃えて A/B を取る。 + +# Phase46': WarmPool stats 1 回だけ確認(Mixed 16–1024B, ws=256, iters=20k, v2 OFF, C7_SAFE) +- ENV: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_WARM_POOL_STATS=1` +- 結果(stderr 抜粋): + - WarmPool-STATS: C7 hits=173 / misses=1 / prefilled=1(約 99.4% ヒット)。C2/C3/C4/C6 は warmup/prefill の 1 miss/1 prefill のみ。C0/C1/C5 は 0。 + - WARM logs: `REL_C7_WARM_PREFILL calls=1 slabs=1`, `REL_C7_CARVE attempts=173 success=173`, `REL_C7_WARM pop=174 push=173`. + - Throughput: 43,809,979 ops/s(ws=256, iters=20k)。 +- 所感: + - C7 は warm pool でほぼヒットしており、残り pf は first-write 優位という推定と矛盾しない。 + - 他クラスの warm pool はほぼ未使用(prefill 1 回のみ)。Mixed での pf/sys は C7 以外の Superslab 利用状況も別途見る必要あり。 + +# Phase49: Mixed 16–1024B userland-only CPU パス観測(v2 OFF, C7_SAFE) +- ENV: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1` +- perf stat(1M iters, ws=400, userlandのみ): throughput=41,000,086 ops/s, cycles=126,239,279, instructions=324,810,642(IPC≈2.57), branch-misses=1,186,675, time=0.0438s(user 0.0295s / sys 0.0143s) +- perf record(cycles:u, 1M/400)上位シンボル(self%基準、子なし集計): + - free 24.3%(libc wrapper経由の前段処理) + - malloc 18.0% + - main 15.3%(ベンチハーネス) + - tiny_heap_page_pop 8.8%(TinyHeapBox hot pop) + - hak_super_lookup 7.9%(frontからの Superslab 判定) + - tiny_heap_page_becomes_empty 5.9%(empty 処理) + - __memset_avx2_unaligned_erms 4.0%(ユーザランド側の初期化) + - tiny_region_id_write_header 2.4% +- 所感: ベンチ前段の malloc/free/main が重いが、TinyHeapBox 側では pop / super_lookup / empty が目立つ。Phase50 では TinyHeapBox(pop/empty + super_lookup 周辺)の枝削減を優先候補にする。 + +# Phase47: Superslab OS ポリシーと OS 呼び出し可視化 +- Superslab取得パス: LRU pop → 旧 cache pop → ss_os_acquire(mmap、必要なら MAP_POPULATE / prefault touch) の順。mmap 成功時に g_ss_mmap_count + SS_OS_STATS alloc を更新。 +- Superslab解放パス: registry unregister → LRU push(成功時は MADV_DONTNEED で lazy zero、今回 madvise カウント追加)→ 旧 cache push → どちらも満杯なら即 munmap(SS_OS_STATS free を加算)。ss_cache が容量縮小で munmap した場合も free をカウント。 +- 新 ENV: `HAKMEM_SS_OS_STATS=1` で alloc/free/madvise を destructor 1 行でダンプ(`[SS_OS_STATS] alloc=… free=… madvise=…`)。基準プロファイル(16–1024B, ws=400, iters=1M, C7_SAFE, v2 OFF)で 1 回回して OS 呼び出し回数を確認予定。 +- SS_OS_STATS(16–1024B, ws=400, iters=1M, C7_SAFE, v2 OFF): alloc=2 / free=3 / madvise=2 / mmap_total=2 → OS 呼び出しは少数。残る 6.6k pf は first-write 優位と見るのが妥当。 + +# Phase47: C6-heavy CPU ホットパスの初期観測(v2 OFF, C7_SAFE) + +- ベンチ (ws=256, iters=20k, C6-heavy): + - ENV: `HAKMEM_BENCH_MIN_SIZE=257 HAKMEM_BENCH_MAX_SIZE=768 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_FRONT_CLASS_STATS=1 HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1` + - Throughput: `39,457,304 ops/s` + - FRONT_CLASS: cls6 alloc/free=5373、cls7 alloc=5692 free=4573 + - HEAP_STATS cls7: fast=5691 slow=1、C7_PAGE_STATS prepare_calls=1 +- perf stat (1M, ws=400, same ENV): + - Throughput: `41,295,709 ops/s` + - cycles=175,790,796 / instructions=368,496,560 (IPC≈2.10) / task-clock=42.36 ms (user 27.26 ms / sys 16.13 ms) / branch-misses=2,206,657 +- perf record (cycles, 1M, ws=400): + - ホットスポットは匿名ページフォルト経路に集中(`__memset_avx2_unaligned_erms` → `handle_mm_fault` → `do_anonymous_page` が包含 60%超)。 + - ユーザランド側では `free`/`malloc` が目立つがカーネルの first-touch が支配的。 +- 所感: C6-heavy でも pf/sys が依然大きく、C6/Tiny フロントより先に first-touch/zero の扱いを考える必要がある(prefault/warm 再利用の検討)。 + +# Phase50: Tiny ヘッダモード (full/light/off) A/B(first-touch 実験の一部) +- 目的: `tiny_region_id_write_header` のヘッダ書き込み/guard 初期化を軽量化することで page-fault / cycles がどこまで動くかを確認。 +- 実装概要: + - ENV `HAKMEM_TINY_HEADER_MODE=full|light|off` を追加(未指定は full)。 + - `full`: 従来どおり region_id ヘッダと guard/初期化を常に書き戻す。 + - `light`: 既存ヘッダと一致する場合は再書き込みを避け、差分のみ最小限書く。 + - `off`: 既存ヘッダが壊れている場合のみ最小限のヘッダを書き戻し、guard 呼び出しや追加初期化はスキップ(free 整合性の最低限のみ保証)。旧 `HAKMEM_TINY_WRITE_HEADER=0` は off 相当として互換維持。 + - `FIRST_TOUCH_PAGEFAULT_REDUCTION_PLAN.md` に 3 モードの設計と ENV を明記(bench 専用 opt-in モードとして扱う)。 + +## Mixed 16–1024B (ws=400, iters=1M, PROFILE=C7_SAFE, v2 OFF, LARSON_FIX=1) +- 共通 ENV: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1` +- HEADER_MODE=full: + - Throughput: **42,399,649 ops/s** + - cycles: 176,079,783 + - page-faults: 6,662 + - task-clock: 42.16 ms(user 25.1 ms / sys 17.8 ms) +- HEADER_MODE=light: + - Throughput: **38,752,779 ops/s** + - cycles: 187,089,553 + - page-faults: 6,661 + - task-clock: 44.16 ms(user 31.5 ms / sys 13.2 ms) +- HEADER_MODE=off: + - Throughput: **39,330,655 ops/s** + - cycles: 183,795,801 + - page-faults: 6,662 + - task-clock: 43.43 ms(user 29.3 ms / sys 15.2 ms) + +### 所感(ヘッダモード実験) +- page-fault 回数は full/light/off いずれも ≈6.66k とほぼ同一で、ヘッダ書き込み軽量化では first-touch page-fault は減らない。 +- cycles/ops はむしろ full が最も良く、light/off はそれぞれ約 -9% / -7% 程度の性能低下。判定・分岐コストがヘッダ書き込み削減効果を上回っている。 +- 結論として、現時点の実装では **運用デフォルトは full のままが最良**。light/off は bench 専用の実験モードとして残し、本番プロファイルでは使用しない前提とする。 + +# Phase52: Superslab HugePage 実験 (Mode A) – 初期結果 +- 目的: Superslab を HugePage (2MB) で確保する実験経路を追加し、page-fault / sys 時間がどこまで動くかを見る(研究専用)。 +- 実装概要: + - ENV: + - `HAKMEM_SS_HUGEPAGE_EXPERIMENT=1` で HugePage 経路を opt-in(デフォルト 0)。 + - `HAKMEM_SS_HUGEPAGE_SIZE` でページサイズを指定(現状は "2M" 前提で実装)。 + - `ss_os_acquire` 周辺に HugePage 試行を追加し、`MAP_HUGETLB | MAP_HUGE_2MB` で `mmap` を試し、失敗時は即通常の 1MB Superslab 経路にフォールバック。 + - SS_OS_STATS に `huge_alloc` / `huge_fail` を追加し、`HAKMEM_SS_OS_STATS=1` で `[SS_OS_STATS] alloc=.. free=.. madvise=.. huge_alloc=.. huge_fail=..` を 1 行出力。 + +## Mixed 16–1024B (ws=400, iters=1M, PROFILE=C7_SAFE, v2 OFF, LARSON_FIX=1) +- 共通 ENV: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_SS_OS_STATS=1` +- HugePage OFF: + - Throughput: **41,464,379 ops/s** + - cycles: 180,862,510 + - page-faults: 6,660 + - SS_OS_STATS: `alloc=2 free=4 madvise=2 mmap_total=2 fallback_mmap=0 huge_alloc=0 huge_fail=0` +- HugePage ON (`HAKMEM_SS_HUGEPAGE_EXPERIMENT=1`, `HAKMEM_SS_HUGEPAGE_SIZE=2M`): + - Throughput: **41,372,856 ops/s** + - cycles: 177,305,948 + - page-faults: 6,662 + - SS_OS_STATS: `alloc=2 free=4 madvise=2 mmap_total=2 fallback_mmap=0 huge_alloc=0 huge_fail=0` + +### 所感(HugePage 実験初期結果) +- 現状の Superslab サイズ/条件では HugePage 経路が一度もヒットせず(`huge_alloc=0` / `huge_fail=0`)、通常 Superslab 経路のみが使用されている。 +- そのため HugePage ON/OFF で throughput / cycles / page-fault はほぼ同一となり、初期実装の段階では効果は「ゼロに近い」。 +- 2MB Superslab 専用クラスを設計するか、サイズ条件を緩めて HugePage を実際に使うかを決めないと、Mode A の効果は評価できない。 +- 一方で pf はすでに ≈6.6k と小さく、たとえ HugePage でさらに削っても全体への寄与は限定的なため、現時点では **CPU ホットパス側の最適化を優先し、HugePage 実験は research-only の位置付けに留める** のが妥当と見える。 diff --git a/docs/analysis/TINY_C6_HOTPATH_ANALYSIS.md b/docs/analysis/TINY_C6_HOTPATH_ANALYSIS.md new file mode 100644 index 00000000..2e2161da --- /dev/null +++ b/docs/analysis/TINY_C6_HOTPATH_ANALYSIS.md @@ -0,0 +1,18 @@ +# C6-heavy Hotpath 分析 (Phase47, v2 OFF) + +- プロファイル: `HAKMEM_BENCH_MIN_SIZE=257 HAKMEM_BENCH_MAX_SIZE=768 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1` +- ベンチ: + - `./bench_random_mixed_hakmem 20000 256 1` → `Throughput = 39,457,304 ops/s` + - FRONT_CLASS: cls6 alloc/free=5373、cls7 alloc=5692 free=4573 +- perf stat (cycles,instructions,task-clock,branch-misses): + - `./bench_random_mixed_hakmem 1000000 400 1` + - cycles=175,790,796 / instructions=368,496,560 (IPC≈2.10) + - task-clock=42.36 ms (user 27.26 ms / sys 16.13 ms), branch-misses=2,206,657 +- perf record (cycles, 1M, ws=400): + - 上位は匿名ページフォルト経路が支配的(`__memset_avx2_unaligned_erms` → `handle_mm_fault` → `do_anonymous_page` → `alloc_anon_folio` 系で包含 60%+)。 + - ユーザランド側では `free`/`malloc` が目立つが、カーネル first-touch が主因。 + +## 次に削る箱(候補) +- 最重: first-touch/ゼロイング由来の匿名ページフォルト + - 候補策: class6 向けの温存ページ再利用 / prefault を増やし、ベンチの 1M/400 で pf/sys をさらに削る。 +- Tiny v1 自体の命令数は二次的(perf で目立たず)。C6 を TinyHeap に載せるよりも、先に pf 削減箱を検討するのが妥当。 diff --git a/docs/analysis/TINY_CPU_HOTPATH_USERLAND_ANALYSIS.md b/docs/analysis/TINY_CPU_HOTPATH_USERLAND_ANALYSIS.md new file mode 100644 index 00000000..db3be595 --- /dev/null +++ b/docs/analysis/TINY_CPU_HOTPATH_USERLAND_ANALYSIS.md @@ -0,0 +1,34 @@ +# TINY CPU Hotpath Userland Analysis (Phase49) + +- プロファイル: Mixed 16–1024B, ws=400, iters=1,000,000 + `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1` +- コマンド: `perf stat -e cycles:u,instructions:u,branch-misses:u ./bench_random_mixed_hakmem 1000000 400 1` + `perf record -g -e cycles:u -o perf.data.mixed_u ./bench_random_mixed_hakmem 1000000 400 1` + +## perf stat(userlandのみ) +- Throughput: **41,000,086 ops/s** +- cycles:u=126,239,279 / instructions:u=324,810,642 → IPC≈2.57 +- branch-misses:u=1,186,675 +- time=0.0438s(user 0.0295s / sys 0.0143s) + +## perf record(cycles:u)上位シンボル(self%) +- free: 24.3% — front入口/libc wrapper 部分 +- malloc: 18.0% — 同上 +- main: 15.3% — ベンチハーネス +- tiny_heap_page_pop.lto_priv.0: 8.8% — TinyHeapBox ホット pop +- hak_super_lookup.lto_priv.*: 7.9% — Superslab 判定(front→TinyHeapBox 境界前) +- tiny_heap_page_becomes_empty.constprop.0: 5.9% — empty 遷移処理 +- __memset_avx2_unaligned_erms: 4.0% — ユーザランド初期化(first-touch) +- tiny_region_id_write_header: 2.4% — header 書き込み +- その他: __pthread_self / hak_free_at / tiny_heap_meta_flush_page などが1〜2%台 + +## 所感 +- ベンチ自身の malloc/free/main が大きいが、allocator 側では **tiny_heap_page_pop / hak_super_lookup / tiny_heap_page_becomes_empty** が目立つ。 +- userland側でも memset が残っており、first-touch 削減(ヘッダ書き込み削減や初期化遅延)余地がある。 + +## Phase50 で削るターゲット箱(提案) +- **TinyHeapBox(C7/C6)の pop/empty + hak_super_lookup 前段** + - super_lookup 依存の範囲チェックを軽量化 or キャッシュ化。 + - pop/empty 内の分岐を整理し、C7 SAFE の理想パス(current_page固定)に寄せる。 + - header write / memset を最小化する実験スイッチを検討。 + diff --git a/docs/analysis/TINY_HEAP_BOX_DESIGN.md b/docs/analysis/TINY_HEAP_BOX_DESIGN.md index f9ddb2fe..1ddcad79 100644 --- a/docs/analysis/TINY_HEAP_BOX_DESIGN.md +++ b/docs/analysis/TINY_HEAP_BOX_DESIGN.md @@ -10,6 +10,32 @@ TinyHeapBox Design (C7 先行載せ替え) の 1 箇所に集約し、ホットパスは TinyHeap 内で完結させる。 - A/B: `HAKMEM_TINY_HEAP_BOX=0` → 従来 Unified front、`=1` → TinyHeap front (いまは C7 のみ)。 +TinyHeap Profile (ENV ショートカット) +-------------------------------------- +- `HAKMEM_TINY_HEAP_PROFILE` を新設し、プロファイルで TinyHeap のデフォルト構成を切り替え可能にした(ENV が未指定のときのデフォルトを決める箱)。 + - LEGACY (デフォルト): TinyHeap OFF(class mask=0x00, meta_mode=0)。 + - C7_SAFE: TinyHeap ON, class mask=0x80 (C7 のみ), C7 meta_mode=1(SAFE)、`HAKMEM_TINY_HEAP_BOX` も自動 ON。C7_HOT は別途 1 にする。 + - C7_ULTRA_BENCH: class mask=0x80, meta_mode=2(ULTRA, bench専用)、`HAKMEM_TINY_HEAP_BOX` も ON。 + - CUSTOM: `HAKMEM_TINY_HEAP_CLASSES` / `HAKMEM_TINY_C7_META_MODE` を直接指定する従来挙動。 +- 優先順位: + 1. `HAKMEM_TINY_HEAP_BOX=0` なら TinyHeap 無効。 + 2. `HAKMEM_TINY_HEAP_CLASSES` があればそのビットマスクを最優先。 + 3. 無ければ `HAKMEM_TINY_HEAP_PROFILE` からデフォルト mask/meta_mode を決める。 + 4. `HAKMEM_TINY_C7_META_MODE` があれば C7 meta_mode はそれを優先(無ければ profile→旧 `HAKMEM_TINY_C7_META_LIGHT` の順)。 +- 推奨フラグ例: + - C7-only 比較: `HAKMEM_BENCH_C7_ONLY=1 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_LARSON_FIX=1` + - Mixed 16–1024B: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1`(C6 TinyHeap はデフォルト OFF のまま) + +標準プロファイル (v1 締め) +-------------------------- +- 推奨候補: + - LEGACY … TinyHeap 全無効(基準)。 + - C7_SAFE … class mask=0x80, meta_mode=1(SAFE)に加え `HAKMEM_TINY_STATS_BOX=1 HAKMEM_TINY_STATS_BATCH=0` をセットするのが標準。C6 は OFF。 +- bench/実験専用: + - C7_ULTRA_BENCH(meta_mode=2)、C6 TinyHeap マスク (0x40/0xC0)、`HAKMEM_TINY_STATS_BATCH=1`。 +- デフォルト運用: PROFILE=LEGACY か PROFILE=C7_SAFE を手で選択し、C6 は明示しない限り OFF。詳細の次ステップ案は `docs/analysis/TINY_NEXT_STEPS.md` に記載。 +- Phase 20 追加: C6 Hot front を C7 と対称に導入(ENV `HAKMEM_TINY_C6_HOT=1` + class mask で有効化)。Front を薄くしても C6 TinyHeap は依然回帰が大きい。C6 は v1 では bench/実験用の位置づけに凍結。 + 主要構造 (core/box/tiny_heap_box.h) ----------------------------------- - `tiny_heap_page_t`: ページ内 freelist/used/capacity と Superslab メタ (ss/meta/slab_idx/base) を保持。 @@ -37,6 +63,7 @@ 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()` で安全側。 +- Phase16: Route Snapshot Box (`tiny_route_env_box.h`) を追加し、起動時に `g_tiny_route_class[cls]` に TinyHeap/Legacy を確定。`malloc_tiny_fast` / `free_tiny_fast` は「size→class→route LUT→heap or legacy」の 1 分岐構造に整理。`HAKMEM_TINY_FRONT_CLASS_STATS(_DUMP)=1` で front クラス分布も取得可能。 C7 は TLS SLL を使わない(TinyHeap front ON 時) ----------------------------------------------- @@ -128,8 +155,78 @@ Phase 9: Tiny lane 判定の整理(TinyHeap を「成功」とみなす) - Mixed 16–1024B: OFF≈47.6M / C7のみ TinyHeap≈36.9M / C6+C7≈30.3M(警告なし)。 - 今後: TinyHeap route の性能はまだ OFF より下がるケースがあるため、slow_prepare/hit 率の改善や C6 向け最適化を別フェーズで実施する。 +Phase ULTRA: C7 meta モードの三段化(bench 専用 ULTRA 追加) +------------------------------------------------------------ +- ENV `HAKMEM_TINY_C7_META_MODE` を追加(0:OFF, 1:SAFE meta-light=従来の delta+閾値 flush/clamp, 2:ULTRA)。従来の `HAKMEM_TINY_C7_META_LIGHT` は mode 未指定時の後方互換ゲートとして残し、mode を指定しない場合は SAFE=1 相当。 +- ULTRA (mode=2) は C7-only ベンチ専用。per-alloc で meta->used / ss_active_* を一切更新せず、delta/flush もスキップする。2024-xx-xx 時点で pop/push の meta->freelist/carved atomic も省略してオーバーヘッドを削減(Box 境界は維持するが Superslab/Tier の統計整合性は保証しない)。 +- SAFE (mode=1) はこれまでのページ境界 flush + 閾値 flush + attach clamp を維持し、本番で使うならこちら。 +- ベンチ (C7-only 20k/ws=64, Release, HEAP_BOX=1, HEAP_CLASSES=0x80, HOT=1, LARSON_FIX=1): + - mode=0 (meta-light OFF): ≈38.7M ops/s(alloc_fast_current=10052 / alloc_slow_prepare=7681) + - mode=1 (SAFE): ≈34.1M ops/s(alloc_fast_current=5837 / alloc_slow_prepare=5179) + - mode=2 (ULTRA, bench): ≈41.6M ops/s(alloc_fast_current=5948 / alloc_slow_prepare=5068) +- C7 current_page 固定化(ULTRA 専用): + - ULTRA で tiny_heap_page_becomes_empty / mark_full の unlink を避け、free で常に current_page に据え置くように調整。 + - prepare は current_page が生きていれば即 return する軽量パスに変更し、C7-only 20k/ws=64 (mode=2) で ops≈52.0M / alloc_fast_current=11015 / alloc_slow_prepare=1 / prepare_calls=1 まで改善。 + - SAFE への逆輸入可否は別途検討(ULTRA は bench 限定、本番は OFF/SAFE を推奨)。 +- C7 current_page 可視化: `g_c7_page_stats`(prepare_calls / prepare_with_current_null / prepare_from_partial / current_set_from_free / current_dropped_to_partial)を `HAKMEM_TINY_C7_HEAP_STATS=1` で出力。現状 C7-only ULTRA では `prepare_with_current_null=prepare_calls` で free 側が current を保持できていないことが判明(要ポリシー見直し)。 +- 本番ドキュメントでは ULTRA を必ず OFF(mode=0/1)にすること。ULTRA で得た知見は安全側に逆輸入する前提。 + +Phase 12: SAFE (mode=1) への current_page 逆輸入 +------------------------------------------------- +- SAFE でも C7 current_page を極力保持するように変更: + - free: used>0 かつ free_list がある page は current に据え直し、empty でも delta を flush したうえで detach/publish せず current を保持(meta/active 整合性は維持)。 + - mark_full: C7 meta_light が current を指す場合は unlink せず据え置き。 + - prepare_page: C7 meta_light で current に空きがあれば即 return(refill へ降りない)。 +- ベンチ (C7-only, ws=64, HEAP_BOX=1, HEAP_CLASSES=0x80, HOT=1, LARSON_FIX=1): + - SAFE mode=1 (meta-light) 20k: ≈46.6M ops/s、alloc_fast_current=11015 / alloc_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 専用のまま。SAFE は meta/active の整合性を保ちつつ slow_prepare をほぼゼロに抑える構成になった。 + 今後の拡張ステップ ------------------ - 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 の二層構造に限定する。 + +Phase 13: クラス汎用 TinyHeap stats + C6/C7 混在ベンチ +---------------------------------------------------- +- Stats をクラス配列 `TinyHeapClassStats g_tiny_heap_stats[TINY_NUM_CLASSES]` に拡張。ENV は `HAKMEM_TINY_HEAP_STATS` / `_DUMP`(従来の `_C7_` も互換)。fast/slow/fallback/prepare_fail/fail をクラスごとに記録できる。 +- 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 でも悪化せず、C7-only では legacy 超え → デフォルトで TinyHeap に載せる候補。 + - C6 は slow_prepare はほぼ 0 でも経路オーバーヘッドで throughput が落ちるため、当面は bench/実験用 (HEAP_CLASSES=0x40/0xC0) に留める。C6 を本格移行するなら current_page ポリシーや meta-light 相当の最適化が必要。 + - 推奨例: 本番寄せ C7 = `HEAP_BOX=1 HEAP_CLASSES=0x80 META_MODE=1`、C7-only bench = `META_MODE=2`(ULTRA, bench 専用)。C6 TinyHeap はデフォルト OFF のまま。 + +Phase 18: C6 SAFE 再計測とメタモード足場(挙動は安全寄せのまま) +---------------------------------------------------------------- +- ENV `HAKMEM_TINY_C6_META_MODE` を追加(0/1)。現状は整合優先のため behavior mode=0 扱い(C6 は meta/active を per-alloc 更新、delta/flush は未使用)。C6 を TinyHeap に載せるときは `HAKMEM_TINY_HEAP_BOX=1 HAKMEM_TINY_HEAP_CLASSES=0x40/0xC0` を明示。 +- ベンチ (Release, iters=20k, ws=256, LARSON_FIX=1): + - C6 偏重 (257–768B): + - LEGACY (TinyHeap OFF): ≈44.28M ops/s。 + - C6 TinyHeap mask=0x40, META_MODE=0: ≈38.81M ops/s(cls6 fast=5372 / slow_prepare=1)。 + - C6 TinyHeap mask=0x40, META_MODE=1: ≈38.59M ops/s(同じく slow_prepare≒1)。 + - C6+C7 TinyHeap 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: + - LEGACY: ≈44.27M ops/s。 + - PROFILE=C7_SAFE (mask=0x80, C7 META=1): ≈43.64M ops/s。 + - C6 TinyHeap mask=0x40, META_MODE=0: ≈38.48M ops/s(cls6 fast=2744/slow=1)。 + - C6 TinyHeap mask=0x40, META_MODE=1: ≈38.66M ops/s(cls6 fast=2744/slow=1)。 + - C6+C7 TinyHeap 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 がほぼゼロでも回帰しているため、次は meta-light の安全な適用か Gate/Route 側の命令削減を検討。現状の C6 SAFE は「挙動は mode 0 相当(安全重視)」で、ベンチ比較用のスイッチとして位置付け。 + +Phase 17: C7 フロント超直線化(Gate→Heap を 1 本に) +--------------------------------------------------- +- Route Snapshot ベースのヘルパ `tiny_c7_front_uses_heap()` を追加し、class7 が TinyHeap route かどうかを 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 回だけ読む)。C7 以外は既存の Larson/SLL/UC 経路を継続。 +- ベンチ (Release, iters=20k, LARSON_FIX=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 で詰めるかを検討。 diff --git a/docs/analysis/TINY_HEAP_V2_DESIGN.md b/docs/analysis/TINY_HEAP_V2_DESIGN.md index c676ce6f..f32263e5 100644 --- a/docs/analysis/TINY_HEAP_V2_DESIGN.md +++ b/docs/analysis/TINY_HEAP_V2_DESIGN.md @@ -6,6 +6,13 @@ TinyHeap v2 Design (入口メモ) - 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 と同等を維持するのが目的。 +Phase33: C7 v2 ON/OFF A/B(現状評価) +----------------------------------- +- 条件: C7 SAFE (`PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_LARSON_FIX=1`), class mask 0x80, HEAP_STATS=ON。 +- C7-only (ws=64, iters=20k): v2 OFF **39.42M ops/s** / v2 ON **43.55M ops/s**(cls7 fast=11015 / slow=1、v2 alloc/free カウンタ増加)。 +- Mixed 16–1024B (ws=256, iters=20k): v2 OFF **40.44M ops/s** / v2 ON **36.58M ops/s**(cls7 fast=5691 / slow=1、v2 カウンタ増加)。 +- 現状の v2 は current/freelist を自前化しつつ、pop/free は v1 の `tiny_heap_page_pop/free_local` を呼ぶラッパ。C7-only はわずかにプラスだが、Mixed では lease 判定や v1 呼び出し重複のオーバーヘッドが目立つ。Phase34 で枝/ロード削減を検討。 + ゴール ----- - mimalloc の heap→page→block に近い形で C5/C6/C7 を 1 つの TinyHotHeap に統合し、Gate/UC/TLS-SLL をさらに薄くする。 @@ -93,3 +100,236 @@ Phase33 以降の選択肢 -------------------- - A) v2 で current_page+freelist の多ページ対応・ページ返却まで拡張し、Superslab への触れ方を v2 専用に寄せる(v1 はページ供給だけの Box にする)。 - B) 当面 v2 は C7 限定のラッパ+単一ページ管理のまま据え置き、mid サイズや他クラスの箱に時間を回す。 + +Phase34: C7-only 専用モードに一旦限定& v2 用 stats 追加 +------------------------------------------------------ +- 方針: `HAKMEM_TINY_HOTHEAP_V2` は当面 **C7-only ベンチ/実験専用**。Mixed 16–1024B では v2 OFF 推奨(v1 C7_SAFE を使用)。 +- v2 専用 stats(ENV: `HAKMEM_TINY_HOTHEAP_V2_STATS=1`)を追加: + - `alloc_calls / alloc_fast / alloc_lease / alloc_fb_v1` + - `free_calls / free_fast / free_fb_v1` + - destructor で `[HOTHEAP_V2_C7_STATS]` を 1 行 dump。 +- これで Mixed 時に「v2 がどこまでヒットしているか」「どれだけ v1 へフォールバックしているか」を切り分ける予定。 +- 次フェーズ候補: + - パターンA: Mixed でも v2 のヒット率が高い → 枝/チェックの純コスト削減(hot path flatten)を検討。 + - パターンB: Mixed で v1 fallback が多い → v2 を C7-heavy 専用に割り切る or C7 判定をさらに絞る。 + +Phase35: v2 をいったん棚上げ(標準は v1+C7_SAFE に集中) +--------------------------------------------------------- +- 観測結果: C7-only/Mixed いずれも v2 ON で大幅に劣化(alloc_fast がほぼ当たらず、lease→v1 fallback が支配)。 +- 運用方針: + - `HAKMEM_TINY_HOTHEAP_V2=0` を標準(C7_SAFE v1 がデフォルト)。 + - v2 は C7-only の研究用に **明示的に ON** したときだけ使用。Mixed では OFF 推奨。 +- 設計メモ: + - 現行 v2 は v1 へのフォールバック前提で、Hot path の枝コストと fallback 多発が主因で負けている。 + - 続けるなら current/freelist を完全に自前管理し、Superslab との境界だけ v2 専用に切る再設計が必要。 + +Phase36+: TinyHotHeap v2 を「Hot Box」として再定義する +==================================================== + +背景(棚上げからの再出発) +-------------------------- +- Phase35 までの v2 は「v1 TinyHeap/C7 SAFE の上に乗るラッパ」であり、 + - ページ供給・meta/ss_active/Remote/Stats はすべて v1 に委譲 + - current/freelist も最終的には v1 の API を介して触る構造 + だったため、**Mixed では構造的に v1 に勝てない** 状態だった。 +- Box Theory 的に見ると、 + - v2 の Hot 部と v1 TinyHeap の境界が曖昧(箱が二重になっている) + - Cold Box(Superslab/Tier/Stats)との接点も v1/v2 の両方に分散 + しており、「境界 1 箇所」の原則から外れている。 +- そこで Phase36 以降は: + > **TinyHeap v2 自体を per-thread Hot Box として再定義し、 + > Superslab / Tier / Remote / Stats / Learning をすべて外側の Cold Box に落とす** + という方針に切り替える。 + +3 つの設計案(A/B/C) +-------------------- +ここからの v2 には、Tiny 層をどこまで巻き取るかで 3 案ある。 + +1. A案: C5〜C7 を 1 つの TinyHotHeap に統合 + - `TinyHeapCtx`(per-thread)と `TinyClassHeap`(per-class)を定義し、 + current/partial/full/page freelist を 1 箱にまとめる標準案。 + - Superslab/Warm/Tier/Guard との境界は + - alloc 側: `tiny_heap_refill_slow(th, ci)` + - free 側: `tiny_heap_page_retire_slow(th, page)` + だけに集約する。 +2. B案: C7 超ホットレーン + C5/C6 セミホットレーン + - A案の上に「C7 専用 fast lane (`c7_fastlist + c7_current`)」を乗せて、 + mimalloc クラスの C7-only 性能を狙う二車線構造。 +3. C案: mimalloc 風 Segment+Page+Block をフル導入 + - Superslab/Tier/Guard を 1 つの Segment Box として再定義し、 + TinyHotHeap v2 の下に「Segment→Page→Block」の階層を敷き直す v3 相当案。 + +このドキュメントでは **A案(TinyHotHeap v2 = Hot Box)を第一候補** とし、 +実装を進めやすい形に具体化する。B/C 案は A が安定したあとに検討する。 + +TinyHotHeap v2 の箱構造(A案) +------------------------------ + +Box Theory 観点での分割は次の通り: + +- Hot Box: TinyHotHeapBox v2(per-thread) + - 中身: `TinyHeapCtx` / `TinyClassHeap` / `TinyPageMeta` + - 役割: heap→page→block のみを扱い、**Tiny 層の命令数を支配する唯一のホット層**。 +- Cold Box: Superslab/Segment + Tier + Guard + Remote + - 役割: 大きなチャンク管理、Tier/HOT/DRAINING/FREE 状態管理、Remote Queue、Guard/Budget。 + - Hot からは `refill_slow` / `page_retire_slow` でしか触らない。 +- Policy Box: TinyPolicySnapshot + - 役割: クラスごとの `heap_enabled` / `max_partial_pages` / `warm_cap` などを保持。 + - Hot は「現在のスナップショットを読むだけ」で、学習の有無を意識しない。 +- Stats Box / Learning Box: + - 役割: page 単位の delta を受け取り、Cold 側で集計・可視化・学習を行う。 + - Hot は「page を refill/retire するとき」にだけ delta を渡す。 + +Hot Box のデータ構造(案) +-------------------------- + +```c +// ページ内メタデータ(ホット側が見る最小限) +typedef struct TinyPageMeta { + void* free_list; // ページ内の block 単位 LIFO freelist + uint16_t used; // 使用中 block 数 + uint16_t capacity; // page あたりの総 block 数 + uint16_t class_idx; // Tiny class (C5〜C7) + uint16_t flags; // HOT/PARTIAL/FULL など + void* slab_ref; // Superslab/Segment 側への back pointer(Cold Box 用) +} TinyPageMeta; + +// クラスごとの Hot heap 状態 +typedef struct TinyClassHeap { + TinyPageMeta* current; // 直近で使用中の page + TinyPageMeta* partial_head; // 空きあり page のリスト + TinyPageMeta* full_head; // full page(レアイベントのみ参照) +} TinyClassHeap; + +// TLS ごとの TinyHotHeap コンテキスト +typedef struct TinyHeapCtx { + TinyClassHeap cls[8]; // とりあえず C0〜C7。v2 では C5〜C7 を優先して有効化 +} TinyHeapCtx; +``` + +Hot パス API(alloc/free) +------------------------- + +### alloc(Hot) + +```c +void* tiny_heap_alloc_fast(TinyHeapCtx* th, uint32_t ci) { + TinyClassHeap* h = &th->cls[ci]; + TinyPageMeta* p = h->current; + + // 1. current page から pop + if (likely(p && p->free_list)) { + void* block = p->free_list; + p->free_list = *(void**)block; + p->used++; + return block; + } + + // 2. partial list から page を 1 枚取って current に昇格 + if (h->partial_head) { + p = h->partial_head; + h->partial_head = p->next; // intrusive list 想定 + h->current = p; + // 取り直した current から改めて pop + ... + } + + // 3. ここで初めて Cold Box に降りる + return tiny_heap_refill_slow(th, ci); +} +``` + +### free(Hot) + +```c +void tiny_heap_free_fast(TinyHeapCtx* th, void* ptr) { + TinyPageMeta* p = tiny_page_of(ptr); // base から逆算 or side meta + + // ページ内 freelist に push + *(void**)ptr = p->free_list; + p->free_list = ptr; + p->used--; + + if (unlikely(p->used == 0)) { + // page 完全 free → Cold Box に返却 + tiny_heap_page_retire_slow(th, p); + } +} +``` + +Hot→Cold 境界(1 箇所ルール) +------------------------------ + +- `tiny_heap_refill_slow(th, ci)` + - Warm/Shared/Superslab/Segment/Tier/Guard に対して + 「class ci 用の page を 1〜数枚ください」と要求する唯一の関数。 + - 返ってきた page 群を `current` または `partial_head` に接続するだけで、 + meta->used / ss_active_* / Tier 状態は Cold Box 側が責任を持つ。 +- `tiny_heap_page_retire_slow(th, p)` + - `p->used == 0` のときだけ呼ぶ。 + - page の返却/再利用(Warm Pool への push や Superslab への返却)と、 + Cold Stats/Guard の更新をまとめて処理する。 + +この 2 関数だけが Hot→Cold の境界となるため、 +Box Theory の「境界 1 箇所」に近い構造でデバッグ/A/B を行える。 + +Stats / Learning の配置 +---------------------- + +- Hot Box: + - `TinyPageMeta` に軽量なローカルカウンタのみを持たせる(例: `uint16_t local_allocs;` など)。 + - `tiny_heap_refill_slow` / `tiny_heap_page_retire_slow` のタイミングで + page 単位の delta を Cold Stats Box に flush する。 +- Cold Stats Box: + - Superslab/Segment/class 単位の累積 stats を保持。 + - Learning Box からのみ読まれ、Hot Box は直接読まない。 +- Learning Box: + - 一定周期で Stats から `TinyPolicySnapshot` を更新し、 + `heap_enabled[class]` / `max_partial_pages[class]` / `warm_cap[class]` を調整する。 + - Hot Box は `ci` 決定後に snapshot を読むだけで、 + Learning の ON/OFF によって命令数が変わらないようにする。 + +ENV / Gate / A/B 方針(v2 再開時のルール) +----------------------------------------- + +- ENV: + - `HAKMEM_TINY_HOTHEAP_V2` … v2 Hot Box を有効化(デフォルト 0)。 + - `HAKMEM_TINY_HOTHEAP_CLASSES` … v2 適用クラスの bitmask。初期は C7-only (=0x80)、 + 様子を見ながら 0xC0(C6+C7)→0xE0(C5〜C7) へ広げる。 +- Route Snapshot: + - `g_tiny_route_class[ci]` に `TINY_ROUTE_HOTHEAP_V2 / TINY_ROUTE_HEAP_V1 / TINY_ROUTE_LEGACY` を設定。 + - Gate は `size→class→route` を 1 LUT + 1 分岐で決める薄い front を維持する。 +- A/B: + - C7-only: `HAKMEM_TINY_HOTHEAP_V2=1 HAKMEM_TINY_HOTHEAP_CLASSES=0x80` で v2 経路に切り替え、 + `=0` で v1 C7_SAFE に戻せるようにする。 + - Mixed 16–1024B: 当面は v1 C7_SAFE を基準とし、C7-only が安定してから C6/C5 を昇格させる。 + +将来の C 案(Segment+Page+Block)への橋渡し +------------------------------------------ + +- 上記 A 案(TinyHotHeap v2 = Hot Box)は、 + mimalloc 風の Segment+Page+Block 構造(C 案)の **Hot 部分** とほぼ一致している。 +- 今後 C 案へ進める場合も、 + - Hot Box の API (`tiny_heap_alloc_fast/free_fast/refill_slow/page_retire_slow`) は変えず、 + - Cold Box 側で Superslab/Tier/Guard を「Segment Box」として整理する + ことで、段階的に v3 へ移行できる。 +- このため、Phase36 以降の v2 実装は **「C 案の Hot 部だけを先に完成させる」** 方針で進める。 + +次にやること(実装タスクの箱化) +-------------------------------- + +- docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md に、 + 実装手順(C7-only → C5〜C7 への拡張)と関数名/API の具体案をまとめる。 +- CURRENT_TASK.md に Phase36 として「TinyHotHeap v2 再定義(Hot Box 化)」を追加し、 + v2 の現状ステータスと今後のタスク窓口をこのドキュメントに集約する。 + +Phase36 (C7-only Hot Box 暫定実装) +---------------------------------- + +- 状態: v2 は C7-only 実験箱のまま。デフォルトは `HAKMEM_TINY_HOTHEAP_V2=0`(v1 C7_SAFE が標準)。 +- 実装ポイント: + - TLS 取得で全 class の storage_page を reset+stride 初期化。page node は storage を優先し、足りなければ calloc。 + - Hot パス: `tiny_hotheap_v2_alloc/free` が current_page → partial → refill を処理し、lease した v1 page を pop/push して meta/ss を同期。used==0 は retire_slow でリセット。 + - Cold 境界: `tiny_hotheap_v2_refill_slow`(`tiny_heap_c7_lease_page_for_v2()` 経由で 1 枚借りる)と `tiny_hotheap_v2_page_retire_slow`(返却+reset)の 2 箇所のみ。Superslab/Tier/Stats は v1 が保持。 + - Route/Gate: Route Snapshot に `TINY_ROUTE_HOTHEAP_V2` を設定し、C7 直線パス/汎用パスとも route==v2 のときだけ HotHeap v2 を呼ぶ。その他は v1 → legacy slow へフォールバック。 +- 今後: v2 は C7-only bench 用の A/B 前提。Mixed では v2 OFF 推奨。より本格的に進めるなら、v1 依存の lease/pop/push を減らし、Cold Stats Box 経由の更新に寄せる必要がある。 diff --git a/docs/analysis/TINY_NEXT_STEPS.md b/docs/analysis/TINY_NEXT_STEPS.md new file mode 100644 index 00000000..994ff6de --- /dev/null +++ b/docs/analysis/TINY_NEXT_STEPS.md @@ -0,0 +1,46 @@ +Tiny Next Steps (Phase 19 メモ) +=============================== + +事実スナップショット +-------------------- +- 標準プロファイル (v1 締め) + - LEGACY … TinyHeap 全無効。 + - C7_SAFE … class mask=0x80, meta_mode=1 に加え `HAKMEM_TINY_STATS_BOX=1 HAKMEM_TINY_STATS_BATCH=0` をセット。C6 は OFF。 + - Bench/実験 … C7_ULTRA_BENCH、C6 TinyHeap マスク (0x40/0xC0)、`HAKMEM_TINY_STATS_BATCH=1` は bench 専用。 +- プロファイルと性能 + - C7_SAFE: C7-only 20k/ws64 ≈46.6M ops/s、Mixed 16–1024B は LEGACY と ±1M 以内(軽いマイナス〜誤差)。C7-heavy 向けの本番寄りプロファイル。 + - C7_ULTRA_BENCH: C7-only 20k/ws64 ≈52M ops/s(bench 専用、Superslab/Tier 整合は緩む)。 + - C6 TinyHeap: C6-heavy (min=257/max=768) LEGACY≈44.3M → TinyHeap≈38.6M。Mixed 16–1024B でも ≈38.5M〜39.5M と明確なマイナス。slow_prepare は ≒1 でも遅く、経路オーバーヘッドが支配的。 + - Cold Stats Box: STATS_BOX=1 は C7 SAFE で同等〜わずかプラス、BATCH=1 は C7-only/Mixed とも大幅マイナス(bench 専用扱い)。 +- クラス分布の目安(HEAP_STATS, ws=256/iters=20k) + - Mixed 16–1024B + C7 SAFE: cls7 fast≈5691 / slow≈1。C6 を載せた場合でも cls6 fast≈2744 / slow≈1 と slow_prepare は低い。 + - C6-heavy: cls6 fast≈5372 / slow≈1。slow_prepare が少なくても throughput は低下。 +- 結論(現状) + - C7 SAFE は C7-only で明確なプラス、Mixed でも許容レンジ → 推奨プロファイル候補。 + - C6 TinyHeap は現状ベンチ/実験専用。デフォルトでは OFF(mask=0x80 のまま)。 + +次の箱候補(2 本に絞る) +----------------------- +1) C6 TinyHeap を C7 SAFE 流に攻める箱 + - やること: current 固定・slow_prepare 抑制、meta-light/delta/閾値 flush の安全版を C6 に移植。Superslab/Tier の整合を Fail-Fast で確認しながら命令を削る。 + - リスク: 実装が複雑化するため delta debug/assert を増やす必要あり。効果が出なければ C6 TinyHeap は Bench 専用のまま。 + +2) Tiny front をさらに薄くする箱 + - 方向: Route Snapshot の上に C6/C7 用の直線 front(C7 HotPipeline 相当)を広げ、Gate/UC/TLS SLL 経路の命令数を削る。 + - 位置づけ: C6 TinyHeap 攻めとほぼ同じ課題の裏表。C6 current 固定+軽量 meta と合わせて検討。 + +3) Cold Tiny Stats Box を詰める箱 + - 役割: C7 SAFE で始めた page 内 delta/flush を「Cold Stats Box」に委譲し、meta->used / ss_active_* の更新をホットパスから切り離す。 + - 進捗: STATS_BOX を導入済み。BATCH=0 は性能同等、BATCH=1 は C7-only/Mixed とも大きく劣化(bench 専用)。`HAKMEM_TINY_STATS_BATCH` は当面 OFF 推奨。 + - 次の一手: BATCH=0 を前提に C5/C6 など他クラスへの適用可否を検討するか、別の箱(front/UC など)を優先するかを再評価。 + +当面の運用ルール +---------------- +- デフォルト: PROFILE=LEGACY か PROFILE=C7_SAFE を手動選択。C7_SAFE 時は `HAKMEM_TINY_STATS_BOX=1 HAKMEM_TINY_STATS_BATCH=0` をセット。C6 TinyHeap は明示しない限り OFF。 +- mimalloc 比較フラグ: +- 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 は研究用)。 +- 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 を触るときは必ず HEAP_CLASSES で class6 を明示し、`HAKMEM_TINY_HEAP_STATS[_DUMP]=1` で fast/slow を一緒に記録する。C6 は bench 専用のまま。 +- HotHeap v2 (C7 専用) の扱い: + - 現状は C7-only でも v1 より遅く、Mixed では大きく回帰。標準は `HAKMEM_TINY_HOTHEAP_V2=0`(v1 C7_SAFE を使用)。 + - 研究/実験で使う場合のみ `HAKMEM_TINY_HOTHEAP_V2=1 HAKMEM_TINY_HOTHEAP_CLASSES=0x80` を明示し、`HAKMEM_TINY_HOTHEAP_V2_STATS=1` で fallback/fast 比率を観測する。結果が悪くてもよいベンチ専用モードとする。 diff --git a/docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md b/docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md new file mode 100644 index 00000000..76d65a90 --- /dev/null +++ b/docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md @@ -0,0 +1,398 @@ +# TinyHotHeap v2 Implementation Guide (A案 → C案の入口) + +日付: 2025-12-05 +対象: 実装担当 AI / 開発者向け指示書 +関連ドキュメント: +- `docs/analysis/TINY_HEAP_V2_DESIGN.md` (Phase36+ 設計 / A/B/C 案・箱構造) +- `docs/analysis/TINY_HEAP_BOX_DESIGN.md` (v1 TinyHeapBox / C7 HotBox) +- `docs/analysis/C7_HOTBOX_DESIGN.md` (既存 C7 HotBox 設計) +- `CURRENT_TASK.md` (Phase36 状況メモ) +- ステータス: Phase36 で C7-only の Hot 部(current_page+freelist)を v2 で自前管理する暫定実装を投入済み。Superslab/Tier/Stats は v1 に委譲する lease/refill/retire 境界で A/B できる状態。 + +--- + +## 0. ゴールと非ゴール + +- ゴール + - TinyHotHeap v2 を **per-thread Hot Box** として実装し、C7-only から段階的に C6/C5 へ拡張できる骨格を作る。 + - Hot Box 側は **heap→page→block のみ** を扱い、Superslab/Tier/Remote/Stats/Learning との境界は + - alloc 側: `tiny_hotheap_v2_refill_slow(...)` + - free 側: `tiny_hotheap_v2_page_retire_slow(...)` + の 1 箇所に集約する(Box Theory: 境界 1 箇所)。 + - A/B: `HAKMEM_TINY_HOTHEAP_V2` / `HAKMEM_TINY_HOTHEAP_CLASSES` / Route Snapshot により、 + C7-only で v1 C7_SAFE と v2 Hot Box を即時切り替え可能にする。 +- 非ゴール(このガイドの範囲外) + - Superslab/Tier/Guard/Remote のフル再設計(C 案の Segment+Page+Block までは行かない)。 + - C0〜C4 や mid/large サイズ帯の設計変更。 + - 学習層 (ACE/ELO) の仕様変更。Hot path は従来どおり Snapshot を読むだけとする。 + +--- + +## 1. 箱の対応関係(概念 → 既存型へのマッピング) + +設計ドキュメントでは便宜的に: + +- `TinyPageMeta` / `TinyClassHeap` / `TinyHeapCtx` + +という名称で説明しているが、実装では既に存在する v2 skeleton を利用する: + +- ファイル: `core/box/tiny_hotheap_v2_box.h` + - `tiny_hotheap_page_v2` … 概念上の `TinyPageMeta` + - `tiny_hotheap_class_v2` … 概念上の `TinyClassHeap` + - `tiny_hotheap_ctx_v2` … 概念上の `TinyHeapCtx` + +実装では **この既存ヘッダを Hot Box の中核** とし、 +他のファイルからは基本的に `tiny_hotheap_v2_*` API 経由でしか触らない。 + +--- + +## 2. Hot Box 実装ステップ + +### Step 2-1: 型の見直しと拡張 (core/box/tiny_hotheap_v2_box.h) + +目的: `tiny_hotheap_page_v2` / `tiny_hotheap_class_v2` / `tiny_hotheap_ctx_v2` を +Phase36 設計の A 案(Hot Box)に揃える。 + +作業指示: +- `tiny_hotheap_page_v2` + - すでに `freelist/used/capacity/base/meta/ss/slab_idx/next` を持っているので、 + **ページ内 freelist と Cold 側リンク用の最小限メタ** として継続利用する。 + - 必要であれば `flags`(HOT/PARTIAL/FULL)などの軽量フラグを追加してよい。 + - `lease_page` フィールドは「v1 page への橋渡し用」なので、 + Phase1 では残しておき、将来的には A 案が安定した時点で削除/無効化を検討する。 +- `tiny_hotheap_class_v2` + - `current_page` / `partial_pages` / `full_pages` が A 案の `TinyClassHeap` に相当する。 + - `storage_page` は Phase32 の「C7 1 枚 lease」専用だが、Phase1 では + - C7-only: storage を current_page として使う + - 複数ページ化後: storage は「最初の page」または「固定 1 枚キャッシュ」として扱う + 形で再利用してよい(完全削除は後のフェーズでも可)。 + - `stride` はクラスごとの block サイズ(バイト数)として維持。 +- `tiny_hotheap_ctx_v2` + - `cls[TINY_HOTHEAP_MAX_CLASSES]` の配列で全 Tiny class をカバー済み。 + Phase1 では **C7-only を有効** にし、C6/C5 は未使用のままでよい。 + +このステップでは **まだ関数実装をいじらず、型だけを A 案に整合** させる。 + +### Step 2-2: TLS 取得 API の実装 (core/hakmem_tiny.c など) + +目的: Hot Box の入口を 1 箇所に揃える。 + +作業指示: +- `tiny_hotheap_v2_tls_get()` の実装: + - `g_tiny_hotheap_ctx_v2`(`__thread`)を lazily `malloc`/`calloc` し、`memset` でゼロ初期化。 + - 初期化時に `tiny_hotheap_v2_page_reset(&cls[i].storage_page)` など、 + 最低限のリセットを行う(Box 内に初期化を閉じ込める)。 + - 返り値は `tiny_hotheap_ctx_v2*`。以降、Hot Box の関数は **必ず ctx を通して状態にアクセス** する。 + +### Step 2-3: alloc Hot パスの実装 (tiny_hotheap_v2_box.h) + +目的: C7-only の `tiny_hotheap_v2_alloc()` を **page 内 freelist + current/partial** だけで完結させる。 + +推奨構造: +- `static inline void* tiny_hotheap_v2_alloc_fast(tiny_hotheap_ctx_v2* ctx, uint8_t class_idx);` +- `void* tiny_hotheap_v2_alloc(uint8_t class_idx);` は Gate から呼ばれる薄いラッパとして、 + - `ctx = tiny_hotheap_v2_tls_get();` + - `return tiny_hotheap_v2_alloc_fast(ctx, class_idx);` + だけを行う。 + +Hot パスのロジック(C7 専用版): +- `tiny_hotheap_class_v2* hc = &ctx->cls[class_idx];` +- 優先順位: + 1. `hc->current_page` があり、その `freelist` が非 NULL → 1 block pop して `used++`。 + 2. `hc->partial_pages` リストがあれば 1 枚取り出し、`current_page` に昇格させてから pop。 + 3. どちらも空なら `tiny_hotheap_v2_refill_slow(ctx, class_idx)` へ降りる。 + +注意: +- Phase1 では **C7-only** を対象とし、`class_idx == 7` 以外は即座に v1 経路へフォールバックしてよい。 +- `tiny_hotheap_v2_refill_slow` は次節の Cold Box との境界関数として後から実装する。 + +### Step 2-4: free Hot パスの実装 (tiny_hotheap_v2_box.h) + +目的: C7-only の `tiny_hotheap_v2_free()` を page 内 freelist push + empty 判定だけにする。 + +推奨構造: +- `static inline void tiny_hotheap_v2_free_fast(tiny_hotheap_ctx_v2* ctx, uint8_t class_idx, void* p, struct TinySlabMeta* meta);` +- `void tiny_hotheap_v2_free(uint8_t class_idx, void* p, void* meta);` は Gate 用ラッパとして、 + - `ctx = tiny_hotheap_v2_tls_get();` + - `tiny_hotheap_v2_free_fast(ctx, class_idx, p, (struct TinySlabMeta*)meta);` + のみ行う。 + +Hot パスのロジック(C7 専用版): +- `tiny_hotheap_page_v2* page = tiny_hotheap_v2_page_of(ctx, class_idx, p, meta);` + - Phase1 では meta/base/slab_idx から page を特定するヘルパを 1 箱に閉じ込める(実装場所はこのヘッダ or 専用 C ファイル)。 +- `*(void**)p = page->freelist; page->freelist = p; page->used--;` +- `page->used == 0` になったら `tiny_hotheap_v2_page_retire_slow(ctx, class_idx, page);` を呼ぶ。 + +備考: +- Phase1 では **Remote Queue / cross-thread free は従来経路にフォールバック** してよい。 + 具体的には: + - 所有スレッド以外からの free であれば v1 の remote 経路を即呼び出す。 + - 所有スレッド判定には既存の TLS/owner 情報を使う(Ownership Box を汚さない)。 + +--- + +## 3. Cold Box 境界実装ステップ + +### Step 3-1: refill_slow(v1 TinyHeap / Superslab からのページ供給) + +目的: Hot Box から見て **唯一の「ページ供給」入口** を 1 箇所に集約する。 + +作業指示: +- 関数案(実装ファイルは `core/hakmem_tiny.c` か、専用の `core/box/tiny_hotheap_v2_cold_box.h` でもよい): + +```c +tiny_hotheap_page_v2* +tiny_hotheap_v2_refill_slow(tiny_hotheap_ctx_v2* ctx, uint8_t class_idx); +``` + +- Phase1 では実装を **既存 v1 TinyHeap/C7 SAFE に橋渡しするだけ** に留める: + - `tiny_heap_c7_lease_page_for_v2()` など、既存の C7 lease API を利用して 1 枚 page をもらう。 + - 取得した `TinySlabMeta*` / `SuperSlab*` / `base` / `capacity` を `tiny_hotheap_page_v2` にコピーし、`current_page` or `partial_pages` に追加。 + - freelist の初期化は **Hot Box 側で実施** し、以降の pop/push は v2 が握る(v1 に再委譲しない)。 + +### Step 3-2: page_retire_slow(全 free になったページの返却) + +目的: ページが empty になった瞬間だけ Cold Box に touch する。 + +作業指示: +- 関数案: + +```c +void +tiny_hotheap_v2_page_retire_slow(tiny_hotheap_ctx_v2* ctx, + uint8_t class_idx, + tiny_hotheap_page_v2* page); +``` + +- Phase1 の挙動: + - page を `current_page` / `partial_pages` / `full_pages` から unlink。 + - v1 側に「ページ free」イベントとして渡す: + - Superslab / Warm Pool / Tier / Guard の扱いは v1 に任せる(v2 は触らない)。 + - Cold Stats Box があれば、ここで delta を flush する。 +- 将来フェーズ: + - Warm Pool / Superslab への返却ロジックを v2 専用に寄せることで、 + C 案(Segment+Page+Block)への移行パスとして利用する。 + +--- + +## 4. Gate / Route / ENV の配線ステップ + +### Step 4-1: Route Snapshot への v2 統合 + +作業指示: +- ファイル: `core/box/tiny_route_box.h` 付近(`g_tiny_route_class[]` の決定ロジック)。 +- ENV: + - `HAKMEM_TINY_HOTHEAP_V2` / `HAKMEM_TINY_HOTHEAP_CLASSES` を読み、 + - bit7 が立っていれば `g_tiny_route_class[7] = TINY_ROUTE_HOTHEAP_V2;` + - それ以外は従来通り `TINY_ROUTE_HEAP_V1` / `TINY_ROUTE_LEGACY` + を設定する。 +- これにより front 側は `class_idx` 決定後に + - `route = g_tiny_route_class[class_idx];` + - `switch (route) { HOTHEAP_V2 / HEAP_V1 / LEGACY }` + だけで経路を決定できる。 + +### Step 4-2: front からの呼び出し(malloc_tiny_fast / free_tiny_fast) + +作業指示: +- ファイル: `core/hakmem_tiny.c` + 各種 `tiny_alloc_fast*.inc.h` / `tiny_free_fast_v2.inc.h`。 +- C7-only モードの Gate で: + - `route == TINY_ROUTE_HOTHEAP_V2` かつ `size` が C7 クラスにマップされる場合にのみ + `tiny_hotheap_v2_alloc(7)` / `tiny_hotheap_v2_free(7, p, meta)` を呼ぶ。 + - それ以外は従来通り v1 TinyHeap / legacy 経路へフォールバックする。 +- Free 側では: + - C7 ptr かつ Route=HOTHEAP_V2 かつ owner==self の場合のみ `tiny_hotheap_v2_free` を試す。 + - 条件に当てはまらない場合は Remote Queue / v1 free 経路にすぐ落とす。 + +--- + +## 5. 可視化・Fail-Fast・A/B の運用 + +### Step 5-1: v2 専用 Stats の追加(任意だが推奨) + +作業指示: +- 既存の `tiny_stats_box` / `tiny_class_stats_box` に倣って、 + - `alloc_calls / alloc_fast / alloc_slow_refill / alloc_fb_v1`、 + - `free_calls / free_fast / free_fb_v1` + のような v2 専用カウンタを追加。 +- ENV: + - `HAKMEM_TINY_HOTHEAP_V2_STATS=1` のときだけ destructor で 1 行ログを出す。 + +### Step 5-2: Fail-Fast ポリシー + +作業指示: +- 範囲外 ptr / page_of 失敗 / meta 不整合など、**Hot Box の前提が壊れた場合は即 abort** する。 +- v2 実装中は「誤魔化しのフォールバック」を避け、 + 問題が出たら Fail-Fast で原因を特定する(AGENTS.md の方針通り)。 + +### Step 5-3: 推奨ベンチコマンド(実装後に利用) + +実装完了後、以下のようなプロファイルで A/B を行う(ここでは指示のみ、実行は別フェーズ): +- C7-only: + - v1 基準: `HAKMEM_BENCH_C7_ONLY=1 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=0 ...` + - v2: `HAKMEM_BENCH_C7_ONLY=1 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=1 HAKMEM_TINY_HOTHEAP_CLASSES=0x80 ...` +- Mixed 16–1024B: + - 当面は v1 C7_SAFE を基準に残し、v2 は C7-only が十分安定してから評価する。 + +--- + +## 6. 実装順序のまとめ(チェックリスト) + +- [ ] 型整理: `tiny_hotheap_page_v2` / `tiny_hotheap_class_v2` / `tiny_hotheap_ctx_v2` を Phase36 A 案に整合させる。 +- [ ] TLS 初期化: `tiny_hotheap_v2_tls_get()` を実装し、Hot Box 内に状態を閉じ込める。 +- [ ] alloc Hot パス: `tiny_hotheap_v2_alloc_fast()` / `tiny_hotheap_v2_alloc()` を C7-only で実装。 +- [ ] free Hot パス: `tiny_hotheap_v2_free_fast()` / `tiny_hotheap_v2_free()` を C7-only で実装。 +- [ ] Cold 境界: `tiny_hotheap_v2_refill_slow()` / `tiny_hotheap_v2_page_retire_slow()` を v1 TinyHeap への橋渡しとして実装。 +- [ ] Route/Gate: `tiny_route_box` と front を更新し、C7-only で v1/v2/legacy を A/B 切り替え可能にする。 +- [ ] Stats/Fail-Fast: v2 専用 stats と assert/abort を追加し、問題発生時にすぐ気付けるようにする。 + +このガイドは「箱」と「境界」の位置だけを固定するものであり、 +細かな最適化(C7 超ホットレーン / C6/C5 への拡張 / C 案への橋渡し)は Phase36 以降のサブフェーズで順次詰める前提とする。 + +--- + +## 7. Phase37: C7 current_page ポリシー修正指示書 + +ベンチ結果サマリ(現状 v2 の問題) +---------------------------------- + +Release, PROFILE=C7_SAFE での観測: +- C7-only (ws=64, iters=20k) + - v2 OFF: 39.64M ops/s, `HEAP_STATS[7] fast=11015 slow=1` + - v2 ON : 26.89M ops/s, `HEAP_STATS[7] fast=97 slow=32758`(ほぼ全て slow_prepare) +- Mixed 16–1024B (ws=256, iters=20k) + - v2 OFF: 38.78M ops/s, `fast=5691 slow=1` + - v2 ON : 33.26M ops/s, `fast=141 slow=16654` + +結論: +- v2 ON では `current_page` がほぼ活きておらず、**毎回 slow_prepare に落ちている**。 +- 現状の v2 を運用で使うのは危険なため、当面のデフォルトは **v2 OFF (C7_SAFE + HOTHEAP_V2=0)** を維持する。 + +この Phase37 の目的 +------------------- + +- 目的: + - C7-only で **`current_page` をきちんと保持・再利用し、slow_prepare をほぼゼロに抑える**。 + - v1 C7 SAFE / TinyHeapBox の current_page ポリシーを v2 Hot Box に移植する。 +- 非ゴール: + - C6/C5 への展開。 + - Cold Box (Superslab/Tier/Remote/Stats) の構造変更。 + +ざっくりタスク一覧 +------------------ + +- [ ] v2 専用 current_page デバッグ統計の追加 +- [ ] refill_slow → current_page セット手順の見直し +- [ ] free 側で current_page を再利用するポリシーの導入 +- [ ] retire 条件の見直し(empty でもすぐ返さない) +- [ ] v1 C7 SAFE current_page ポリシーとの比較・差分吸収 +- [ ] C7-only / Mixed ベンチで slow_prepare を再確認 + +以下、それぞれの具体指示。 + +Step 7-1: current_page デバッグ統計の追加 +---------------------------------------- + +目的: +- v2 が「どこで current_page を失って slow_prepare に落ちているか」を**1 箇所で見える化**する。 + +作業指示: +- 参考: `docs/analysis/TINY_HEAP_BOX_DESIGN.md` にある C7 current_page stats(`prepare_calls` など)。 +- 新規 struct を追加(場所は `tiny_hotheap_v2_box.h` か、専用 stats ファイルでも良い): + - `uint64_t prepare_calls;` + - `uint64_t prepare_with_current_null;` + - `uint64_t prepare_from_partial;` + - `uint64_t free_made_current;` + - `uint64_t page_retired;` +- ENV `HAKMEM_TINY_HOTHEAP_V2_STATS=1` のときのみカウンタを更新・dump する。 +- カウントポイント: + - `tiny_hotheap_v2_alloc_fast` 内: + - entry ごとに `prepare_calls++` + - `hc->current_page==NULL` のとき `prepare_with_current_null++` + - partial から current に昇格したとき `prepare_from_partial++` + - `tiny_hotheap_v2_free_fast` 内: + - free で page が current になる/保持される場合 `free_made_current++` + - `tiny_hotheap_v2_page_retire_slow` 内: + - `page_retired++` + +Step 7-2: refill_slow → current_page セットの修正 +----------------------------------------------- + +目的: +- refill 後に必ず「C7 用の current_page が 1 枚セットされる」ようにする。 + +作業指示: +- `tiny_hotheap_v2_refill_slow` の振る舞いを以下のように揃える: + 1. C7 用 lease API(`tiny_heap_c7_lease_page_for_v2()` など)から 1 枚 page を取得。 + 2. `tiny_hotheap_page_v2` へ: + - `base / capacity / meta / ss / slab_idx / stride` をコピー。 + - freelist を page 内 carving で初期化(pop は v2 が握る)。 + 3. `tiny_hotheap_class_v2* hc = &ctx->cls[7];` に対して: + - `hc->current_page` が NULL なら **必ずここで current_page にセット**。 + - 既に current がある場合は `hc->partial_pages` に push。 +- refill 後に `hc->current_page` が NULL のままにならないよう、Fail-Fast assert を入れても良い(デバッグ時)。 + +Step 7-3: free 側で current_page を維持・再利用する +----------------------------------------------- + +目的: +- C7-only では「同じ page 内で alloc/free が揺れる」ケースが多いため、 + free 側で current_page をなるべく保持・再利用する。 + +作業指示: +- `tiny_hotheap_v2_free_fast` で、以下のようなポリシーを入れる(v1 TinyHeapBox SAFE と同等イメージ): + - `page->used > 0` かつ `page->freelist != NULL` の page は **current_page 候補**。 + - `hc->current_page` が NULL の場合は、free した page を current に据え直す。 + - `hc->current_page` が他の page を指していても、C7-only フェーズでは「free された page に空きがあるなら current に切り替える」実験をして良い(後で safe なポリシーに絞る)。 +- これにより、C7-only ベンチで `prepare_calls` に対して `prepare_with_current_null` がほぼ 0 に近づくことを期待する。 + +Step 7-4: retire 条件の見直し +----------------------------- + +目的: +- v2 ON で empty page を早々に Cold Box に返しすぎると、current_page を毎回失って slow_prepare 連発になる。 + +作業指示: +- `tiny_hotheap_v2_page_retire_slow` を呼ぶ条件を見直す: + - Phase37 では「C7-only ベンチでは page が empty になってもすぐには返さない」実験を許容する。 + - 具体案: + - `page->used == 0` でも、まず `hc->partial_pages` の一員として残す(一定枚数を超えたら返却)。 + - C7-only フェーズでは `max_partial_pages` を 1〜2 に固定して試す。 +- 本番運用向けには、後続フェーズで安全側にチューニングし直す前提で、「今は slow_prepare を殺すことを最優先」にしてよい。 + +Step 7-5: v1 C7 SAFE current_page ポリシーとの比較 +----------------------------------------------- + +目的: +- 「すでにうまく動いている v1 C7 SAFE / TinyHeapBox の current_page 設計」を v2 に再利用する。 + +作業指示: +- 参照: + - `docs/analysis/TINY_HEAP_BOX_DESIGN.md` + - `docs/analysis/C7_HOTBOX_DESIGN.md` +- この2つから: + - alloc 時: `prepare_calls=1` に抑えるための current 固定ロジック + - free 時: empty page の扱い(ULTRA/SAFE の違い) + を抜き出し、v2 Hot Box の `alloc_fast` / `free_fast` / `page_retire_slow` に反映する。 +- 差分: + - v1 TinyHeapBox は meta/ss_active 更新も Hot 側が握っていたが、v2 では Cold Stats Box に寄せる方針。 + - そのため meta/active の更新部分だけは v1 と同じにせず、**current_page に関わるポリシー部分だけをコピー** する。 + +Step 7-6: 再ベンチと判定基準 +--------------------------- + +作業指示: +- C7-only (ws=64, iters=20k, PROFILE=C7_SAFE) で: + - v2 OFF: 39〜40M ops/s(既存ベースライン) + - v2 ON : 目標として **少なくとも v2 OFF と同等 ±5% 以内** を目指す。 + - `HEAP_STATS[7] fast≈11015 slow≈1` に戻ることを確認。 + - v2 current_page stats では: + - `prepare_calls` に対して `prepare_with_current_null` が ≪1% 程度 +- Mixed 16–1024B (ws=256, iters=20k) では: + - v2 OFF の 38〜39M ops/s に対して **±5% 以内** を許容範囲とする。 + - C7-only が十分安定・同等以上であれば、Mixed の調整は後続フェーズで良い。 + +判定: +- 上記基準を満たせば「Phase37: current_page ポリシー修正完了」とし、 + 次フェーズで C6/C5 への拡張や C7 超ホットレーン(B案)を検討できる。 +- 満たせない場合は v2 を引き続き **デフォルト OFF / 研究用箱** としたまま、 + current_page / retire ポリシーの再修正に戻る。 diff --git a/hakmem.d b/hakmem.d index 9e85a7ce..07b79f3b 100644 --- a/hakmem.d +++ b/hakmem.d @@ -34,15 +34,16 @@ hakmem.o: core/hakmem.c core/hakmem.h core/hakmem_build_flags.h \ core/box/../hakmem_tiny_superslab.h \ core/box/../superslab/superslab_inline.h core/box/../tiny_tls.h \ core/box/../hakmem_tiny_superslab.h core/box/../tiny_box_geometry.h \ - core/box/tiny_c7_hotbox.h core/box/mid_large_config_box.h \ - core/box/../hakmem_config.h core/box/../hakmem_features.h \ - core/box/hak_free_api.inc.h core/hakmem_tiny_superslab.h \ - core/box/../hakmem_trace_master.h core/box/front_gate_v2.h \ - core/box/external_guard_box.h core/box/../hakmem_stats_master.h \ - core/box/ss_slab_meta_box.h core/box/../superslab/superslab_types.h \ - core/box/slab_freelist_atomic.h core/box/fg_tiny_gate_box.h \ - core/box/tiny_free_gate_box.h core/box/ptr_type_box.h \ - core/box/ptr_conversion_box.h core/box/tiny_ptr_bridge_box.h \ + core/box/tiny_stats_box.h core/box/tiny_c7_hotbox.h \ + core/box/mid_large_config_box.h core/box/../hakmem_config.h \ + core/box/../hakmem_features.h core/box/hak_free_api.inc.h \ + core/hakmem_tiny_superslab.h core/box/../hakmem_trace_master.h \ + core/box/front_gate_v2.h core/box/external_guard_box.h \ + core/box/../hakmem_stats_master.h core/box/ss_slab_meta_box.h \ + core/box/../superslab/superslab_types.h core/box/slab_freelist_atomic.h \ + core/box/fg_tiny_gate_box.h core/box/tiny_free_gate_box.h \ + core/box/ptr_type_box.h core/box/ptr_conversion_box.h \ + core/box/tiny_ptr_bridge_box.h \ core/box/../hakmem_tiny_superslab_internal.h \ core/box/../hakmem_build_flags.h core/box/../box/ss_hot_cold_box.h \ core/box/../box/../superslab/superslab_types.h \ @@ -91,6 +92,9 @@ hakmem.o: core/hakmem.c core/hakmem.h core/hakmem_build_flags.h \ core/box/../front/../box/../front/tiny_unified_cache.h \ core/box/../front/../box/tiny_layout_box.h \ core/box/../front/../box/tiny_front_cold_box.h \ + core/box/../front/../box/tiny_hotheap_v2_box.h \ + core/box/../front/../box/tiny_route_env_box.h \ + core/box/../front/../box/tiny_front_stats_box.h \ core/box/tiny_alloc_gate_box.h core/box/tiny_route_box.h \ core/box/tiny_front_config_box.h core/box/wrapper_env_box.h \ core/box/../hakmem_internal.h @@ -173,6 +177,7 @@ core/box/../superslab/superslab_inline.h: core/box/../tiny_tls.h: core/box/../hakmem_tiny_superslab.h: core/box/../tiny_box_geometry.h: +core/box/tiny_stats_box.h: core/box/tiny_c7_hotbox.h: core/box/mid_large_config_box.h: core/box/../hakmem_config.h: @@ -258,6 +263,9 @@ core/box/../front/../box/../tiny_region_id.h: core/box/../front/../box/../front/tiny_unified_cache.h: core/box/../front/../box/tiny_layout_box.h: core/box/../front/../box/tiny_front_cold_box.h: +core/box/../front/../box/tiny_hotheap_v2_box.h: +core/box/../front/../box/tiny_route_env_box.h: +core/box/../front/../box/tiny_front_stats_box.h: core/box/tiny_alloc_gate_box.h: core/box/tiny_route_box.h: core/box/tiny_front_config_box.h: diff --git a/perf.data.c6heavy b/perf.data.c6heavy new file mode 100644 index 0000000000000000000000000000000000000000..605ca0871ae6dad8dec6bd6027c42e0bd345f717 GIT binary patch literal 52544 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

b?f?_su@*CM|#cD5>9_weD5W=j6B0 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 literal 0 HcmV?d00001 diff --git a/perf.data.mid_u b/perf.data.mid_u new file mode 100644 index 0000000000000000000000000000000000000000..30124fc7bf583737b19c076132a00fd2bbda0317 GIT binary patch literal 48792 zcmeHQ3s_Xe-yW2_bQ4Q0%?p~Qrpp2X(#70N4f7J8W|C+K%L*&(vhIRnmoKO+EG;Wb z3oCD>mX|csvQo3sZkB#&cG0r`S7v2pX=NWJ-^Ch75?$W#TnQrU#Vck_d4k`4O3+B!AvEF-(pCHlHsD&XMW4GHoH# zmNIQ6)AM9{zD!%ow2e$JkZD_)wv%alnRbwAv`o1dWz~G1*GwFAmGx>N^PIhBWmbco z@Q-Nc63KimrOB*=Jnpimy@P*jbI#_{ipt7Lc{-R#ci8|s6V)I}=AeCpK>6W~naT7C z?GAgS$VY!PkakFskA7+>^N}JS{TC_oks=@cdYa5fihT5UBbkpB`4|U{Wj<2mV|+A` z`ACtEann@hBSk*O)9ErFDe^JS&XD;?k&p3rrp!l*e2mMpWIj^lW4xX%^N}JS<5&(` zh7|dO$gUex`&H5flWXWx6chd1j5Vj@6vjrk+@uY+N~R4c&XMICpHb+~^!N($-CkEl zQbIy}LUK~?#9l!r{Rc4~Vj1^`zkM$Mc$=%(<PBH# zKA>_Lw{JR7q^sf@kbiyNji59(LimgF*QG2CmfwImn7!j7I!5^!2Sxc9x46Dw54>m( zADi3FrSt!?qryH$wjjng{0Bbqji1Tyj)8rY#)a{>&7kW3rm(x_-*?Ftt+U|o!NxnN z{VU~w!u1Y+fp2+4*m1ubu-M47r2BO|ku3zzKrzs?ewHuUZ@@P{Hk;bZO09O_*ReGY z{@LR|=!Ju2cYH>tlO0oqUKGD7bVAih;Dt^mNA?VK(COqA4L>B^dix?_St{iQItl!Q zxC_bdj$DRTS{s^W9nY;^ZW4_F&Uy5kKr?jQJ(J}Em;Ft&<6X^-F+<6s;coc{yqKiZ)*`X_1!-sb#;c__vK z@DJIxxk}ejCqS$r&xV`#?BMG9!bdik-xwTd`;_<9x&>+GSM&q=bfYXe;$mdIPdaL1#jD{#E6zxF#`|qLXS$SuD6-*i|c-q zoU!f`e&M?JG!fT*`O0x$@*M6j=~4LW-ks&)7kd&Bao_m6(`SdO=}xusOBRUoQ(F_B zl?cyK{tdGHAI4qrAP3Z_d}nC+OP=_d$`{wGk2UT1e>y#w-TfEJ|KMWLBkJ`8{PD*1 zGaE&}{Hcj1U$YbC<+9&L>GkK2J-<_VpEnidVZ8w35BPA`t$JS)31f`gANaamXSwX4635)Ivn!Q1^(s-G zsw2SH?t1pI0m?Xj`Gy`;&ac-P*9-W%jjPapzwpBtDlg}HyFO9jX-16`=RyUoR!z3!WCd*cX8L9PnEsI@7w!YB{b>%s2_HXTEpK3X?)XbL@4u^bCh>Y+G9G`xpQ!P- z@A6qxUWKXaD71Y#|2&&`M{Y1Pu0wg(EiEVBy{4|0G?uK~mlxgor11AGV}F7FdwmY| z_me&giFe0VW8Ruw-=Vzr%BP9f^Ntzb(E0P7wr7d=llKI#8Yk9v{=7_?=U!U&Jn`T9 zqOsq=|9w0ASd>$*15dBpM!Y%YV#kQ8+rZcKy5Ty?S1v^ySoM5`?-Am8U)?44#XxV5 zO%=yt-77iLRaq5(bDJlY?<4=-O&0!@%JnMfG4Lhwxw7?~_@BKG!P;kH^>6k4MnX5n z>7F-n-PrW(ew}WpdW`2y>b}75zZd&Pq|C^5@iO+#?-vE1qjCO0+rA_I%){b22lkgj zmw>->r?6|px|02$GQHKA?pUGlDi82_p4iAn z%D(4~`;HLLj$tBlEzw+|muwH*aGJiD!P9Scz5r z0bcKqgPlHF?@#~B8WYdZu(O;3t_@6h+>a#|8k)`x;e)j{A9 zcK)DgjlXRVKkn$EpFcb`sx9$U#0wtP58&Yk+6nb&|9S_qe>L6M58%~yMg73jedC41 zvu36-5AgbSO<}kXf~U8?EAiYv-Ixb>)gPP(k7sQU;%Qu78=la9e&u>Q@yu`w9<_Z@ zFSPHj5&HY(w;#om{p3<(KY(Wr{doIR8MnhGBoWU|D~$aB9)76pG4kjoWdHp(V?Th` zw`*+Vh06SH+l)TMQ+7!3l)ym7S-@L*dur&d)lXJGorw4e_b0^v(BXwct|Yw}(M)`E zgQ}mvL$`3hf&Ju*2atXHF2Y{DPXZ4a0?%4y-jcIFg?N^~M~ z^$&RHALgNm|JL<$l6@~}uf~7C>vb!IwNTbu+6~Mhp2KOz`Ukvzy(KoXg);w*X`Dwq zuQ-hL4|r=_oZ9zutN*P2tIho#`i1pC=-0|Gy`&eVDaN`3yk5T?Y=EMp`(E-BkLOBb z9R(hKU|tMA_S`aw?3ed3_5*m;kC#&Q>zUbC+(bNg%J@&6&jXM90oI@4$JArDl6}`9 z#`ORm_E;Z)eY=gNWIydXV|(CXkN6t)2iH#}`!~lL+XD}K?B9WX)ZNp_e!uKbwf?{h z`_TO{d4pzf!<6~bbofI2S_hQ4mZ{-;9(E_ zgI?WQF^lY5NWD?@3V7(%a%F#^XVV9%)B3XJ&*SN ztjap>sGW<6XG*%U4g(KA5KqI8s~>%q?8{xkUTqiP^?rozkMK@dNj&%F8S@yoi-X;N zv3@=H@UZ8J=OK@AeSk-O_Ftr*2lVc=j_lVsjr{;#txr#7-}%t**AvgMEMp$vkGJpq z(9#XW(`LNjDM7(jTy4eG*7x`RYrnr2`W3G{4{LS*X3~qH`Np~fJY)ns&{ydC!>L<{ z=b|wB1-#0$PTA*ub?bMC=R>!#KftT|=iJKg7p(Yx8}WQ9bzQyB0I&c3f)ti_xqiRp z8#{Io&t|W&Kfvqj6}o@!tEYAmPnT@Lqs~7N4~X`4Fs#3zeUsLFLiVY$eN}tl(LWoN z{dY&E?DACk;1Sa1b+N7=u6^x?{eez0AAle4)&ZfpPO{%*TnOtoKLCs z0Uq^9Ql3AgzjJ`>e;8`)2k`PaMk3nv0c9QUf9^g=Jl;{p{Q^AfTXoal|0~iDk$pdT z|5xh)JnYfWXwUW?kC1(7rg8be!~T6G9$RUUmh>SMq*tHdUeN&O45rS^8T#q1@N_kLtTn0eSZ?u72wD}AOF z@woa4-BEdz{d&Q9?h<9Q|=oHe6M<)oqP~#ln5$BXE&j)`x zZ3x-FEca2XaWU{B+QRp3Ias{>9zyu>#jb0KXYB}MKY)iH&nnM58$C6G?EjMXsvp4X z_m70mH@3S*5zp!{@gwFN>hrz1%DT%N$?3$?SH{I^y8^5Fa7&@PEXONl2V%)Tw)_2WE+ zRV>i&ADhOe63;nuzm-}q;MIBCHOhSH!>wh+)4a&Ioq^ZSmmCb&GwOBbe@`c#FQyoe zH{dZ|ux|kN(Sv7_{e5>E+XFA`L+5L)de0*J>!%6(5_E)oo@~w6toX}{zy7W9R|;FH z=;sTA=8#^zCG|?xPvG_X85{YRvVV3}{9NM6lj~Kg?f_rcbzaNP(k)B3PCea1T#Nmu zh;z2LnMZn&JKNr^I724c2S?NO!|5?@ubQ9uBxsBuk!py`M$E- z-damM9p(DJ$^*Rqc}5C*PnjQ2er!GQREDV+@W<H2-Rao6u6o-Sc{fX6ryzY-ySPi&L? z?Hitco&6E!S z%*XB>(}y1J5nm+`lEBUV&uubTY6QUjL4mh60q_V1#; zV}BFnz290mthPJw`gTuY2}_^S@$BuioBDfYhA|KDY5#eG>_q#evN4Z*uH(67u;4j% zgBS^7d}*9=o(#O>%DrRJUpcgY^zbP+xxCT6qZ5?RmwodVo#(%};n3c8j9W2ab0@oOTGQJK3kvcB{1bJ8 z-RJU;C$o@0oWt*O7WlJ6vTA&SbJ%if3^xQ4hs!Xl#wR(4bBZ(H6}Ysh#zqQ|bJz-$ zleIkL9K6M2vNH=aW4J2S-W|>nQv+SoU}0YlEhjE)Zp|`?BbHt@GcZ7Lj)d4+4f7Bd z&SADL{5D)BnUV6td14yEy2|@B*HQj*_MVj~KAoJu&=s>|w)uSF_&G8?SEemw+ES*i zWO|-V&zEUynYNMX1u|_b({?g#FVhY(jg~1d-J!i>ZE=ZaTB=DEj~N>BCEnJ{G)IEX zRCUOYBwKIOek9v2G0hRDb&i?q$Dw%;7jKI*)2hUgU~-TsEg>&i`h9Gk)j;MWm7#QS zjv0)NCGIpO#c$do6~A1F-`?i)xDzwhWwCWmP;bNociP(%mqYt&_B!OhF2>u5b2)vr zzjtY7!589`Oiy-(yTIcq%Bb6YfcKL_duRDwnR&hfx3@4@M6EvXUvhJ3Z+BK8W4x=- znOiICDll&&hxXyWuo8t8U*JlGTF|&UVQlk_2ZoA!~R;hrybV$ z<%{BOtmPx?wjalk*znzS_ z)t7!V8z8b=^$fyX$q`Oy1x4a}Rzm;eYU)@kHF; z;*xqLCzx2GZP+1h~%2Rl*h=4q0d3VmtuXH9irR2ib zS(8kigGOWw88)bU0Tb6lGTo=$SJ=dZ{rzQzmK>cnVX^i|x&3FAAlM?)tuoyv(+_3( zkxW09>2{g!km*jD?vm*zGW}GhyJh;BOsi$em8qw206#0nDW-=cs@)57+%Y3BBM;yzrWk``f zgygy~ym`OgRP&t0U!E7pG9GS|5f@Lytbt(!wqZ16UHJNNUS*{vNkyK#q+oh4!|Mr#_sfC_ zfy~;{twP(N8P+{i1}J=Xtu}+;~daaejB= z#QeOg$#0?@C##bAD5uJ@Tbyyf?w$UP%Zsq zn1lUVCZ0q8h9#mr(e8|R5bk2o7x>ydHWp+{l8-3ThFNA^vZ&P?UR=o_3`2yj`tFuIE7i!3UtKww4oPVY0AJ)j0@|6Jcev8}gjTQaFvKMl`<~eAWN#Xkk zPo9@|3cZL3{+Qz!U#owrRenW`DBt-P!m|otAIcvk%l|2--@^9QQ%L{4nK?2`n~sG z6Xnq0V^px-?*V_Rek1RYsL$GV@%H_Md=9@=Z58Eh5z!XX?*U(%hmSLx3&nT=o`atW zo)xlT>;3^g-1y=+9I7ub=lv|ToR1EPa(1K$h4pfP4_8i~BcbKQFgqLnw%T9adzF{s zc1ml}0_1F=W;_5M?Po!2RX*=~A@BWBwqNOA^iRO!v1mV8Y`7NJuAravShz?yR`1t= z*WtJHQEdC^&O`+wP-{A*1kuYivCL)2D=(D(XJ6M z>!#dAu3w?fquPI8NxYp83SMp( zzu+(6kF64NfAQ8mpQ^7tkAFFv%Kh^zEB55D>$JF9ZktcM!D_*)_m{xyab>QzpIzuu z{gu~w5%J&fyWrRRG2l8N3F|=A5Ads=7J9z$f=I2_ z*U#UIaSQRs9$ZR#yx+n+u7R&R{zB(>&3=5Cc$>F3=dE8{``KqQKB4ao*FQ$QDXD^2 zuP5Lmjpxra|IEH+Ih8kkkhvYe*X?+~voAb7Elg#HEi z4YFT4)BOzM33gVsTu=IJDmS+i_`2;3jlUzGZY17wdRyW3vn5*lygy?z@phMSt@ks) zM;3pfc1&8mmCAc;s1-Ycto_IBl{oNr|Co4VM+sg%u7S5YuAu%<-%~!^N#*)|R_qCF zFRg>SiMPQdbKd&3mr(!5Uit;`7Tsaa3;RwtJ|A88HSr#uer9?9HtSpB?K9UJ?@Dc7 z?4!hg6YuPK=Dfh4t{=YL{3G!`zSx{MQvViC+Anw?IkUWb`Wz(Qj?Y-*ZP-7V_6g7~ zEIz#EzK@>#ym@)n z=3(>Mer-G(6xWE#yH>V)eLMsHe>I-9c|Ml(nO$lvJ`neak2`vwN4#C8TH!5V$L0Pw zco%GKPP{+M^|QVY0erZ92=jG~pEYg&hca)wW7-8&P7~SA^*97RvT@VTDztI>?}@Fc zymrgX;}H1B$_uiYa{UEA_N#6~GcG>HGc(J z!)kTDmF7sHa>w3cUQfVVE4P46d`aEE7*(E5vIPY zUt5RpJjQkC1^mQ$@^fZ|6?)p))E`tm8(%w^cyArbOBSCw(;I0o@ItRp`QZ2=B>(H1 zV#idkFIC>j#vfD5U$FN&l7DKQ*rC$>1U%_f>yOYUhmHJ6AaE`?UCY@ueGyf6f^3&4?Nf=`Y|LX#DWsA*ROb$WEEWH)_4$)BOj$ zkaw_EmvvF%;=qepB!AUGA+N_P@Ty%7_PG`Z4-6SYJP*f-Z*=H9z^ifZVBQX@K3!Q3 z@!XSP&I7!zPuxW+&-qha#M5%5IS=qEkDaA8RQm)z=yAk`!h-X`Ka~|Mr7awfHzbAq?PSN_^(Z5b6p4ShU`x$u1zj|D?r~I5d zNxt)Xb9vx}d=^X8{Cn>I-$nA}PYQXxe+2&h$)a3atguJ*kCPQlRrTtddJpk#c-g$( zfCmp!p{J8&X#U>td?oQM_{N+Mcv}lI{T%G|3)J}Bn>w5LmbNgjKj2lp?CeghAO7yw zdBl_E5IlN20bbbUU>&r0ym#XwlJ7slTo2%NJ=SUc;RBC7NIbo7FxLZk=&`ev>W}*; zFC%$&ow**s>w0W?L#_9;TOK8zT?5Va0AA=}XX7+Gb~b&2eZ(H2nfUy-K_##e!GwUx7CtmmMrw8&{{TeS`RJEHbwjcvUY4 zE7IaI=6VAUUhu?f<9O_$jl}atzBv!@Do;*G{H@!xnRt#)5Ip+4PLDtE=_%UlC9Bo; zo?W$#_{aGKzux}=4?8<+{>lw(C;93y@d7;b5j;-zA2}YO-ESWJ3GuWk6nf}!3_N&{ z3ceiH_Y2jo<6WwWZ_qe%zX4yr`LLZ;YV*dl@4qDe*lcsZ0S~_q(E7=uXZDc%H)GA^ zfrtDYO@48~UXmX!`xCw1fEV%(w!fts=l1?TkbL?SQNBKp0$%mAgALcl^%<>xA)c<2 z%z1#f*=x_E9PFqz?gYL$Ks*~Kn)3j!>w|d%;^?DQhl!_67=3_Od7Lc%OErErF8YIb z-YOIN=h0yrV`BOK@Mu}@dc6T}bJq5^ zon4^KThINa5%KH@2z~VU0Uq%;Oq=)I{g0+3pEB889(ed;pw>U$G`AVar_1tnJAl{y zfq5P3`@0)k5>MZ9bH4(w^4M8oBXzyB^P)K7`Juwx9^l~*%*&z2oxfd7@+Fn#^1usu zCv$1@?cEzLA^E(!g}febz{3ut@UKr>kM3F;Pkd$49^Jpdt309e_KUooh^OZ?bG`KT zr1N2&06$$cybJODF8dwbPT*}@4%50z=R2m28{N+DMtmDf&GiF5+`3}FHgEaugCyd! zPZfN+-+)*3va_qS_VL239>jCx9>JrxFW?am=qKSPpC^sv3l^B$1w8aX3O-kqC57?lb2D9_w)1lHY2rdmU`w8!F$5glmXz&Ryny0$$f^u21FX|fkBf4K6VDE*j~)lW zi+Z!OW!m~Quk{F$KP=meE)P8VfuA)yIv&a-`Sa(R*Awu%KU%yhzP|}OZvFQt;+c4_ zIS=qU55^<#oKt-p@$8r5s_s|dRUSLTcmf{prd;Cb?v4#s(jugP$8{{pY`%+lij$@PBXNz66p z0bb`BsquWXBtSgvgXTQIt2|k(ZME9I8&3}s&rmt9)7QPgWBm+1q^LjaqrqQ`3a1ip zr}08Rz5aoRzkl4J>UD>+g5)#9><<91>fvD6e}x{|y=M?l*D(7Pz^gpYkoowbJ@*pN zKkULDJ)ZRWxXR~XeYJV{(EDZ)Uz<$x`UBq9rS?2AbpK;a&w0etU#`pa_NuQx;qRr| z{CA>l5%CO~X|50Ox?N~T@b}EG7ZcAyInUDd0p4~%%pagnQ1egwcb5{+mN4@p;B7~0 z^JK9D0d*XlUG)g@tds3Tk8j|y{|ft%3jMMe#$VW(JojkstdjAGI{PVQd-tzufL-Kp&zLD-9;B`H2)7C}T?tG7U?hT^{ z@Th0ZYvJDs&u%37ufmMezzcaN!+a0&N6R;p{2aNDrq>VfumdUhus#9b`L}N)zAkcn z)cJt7m7BMx4DEA>xox)-U!yGZehqk4FFRYJwWqAL$d2gA4ty?T$>BkT&ZUje-89|yyH50MdM`ty@-d4YtlkgP z?&oXw+vPp>t*f>Cc!$cDFYkdblK1;%2QcP3fzOiqy)sPP2c$!~IK}-Kk+2!^{t}sA zHBw~Eh!bfik4TMU1?WnZ_jY+7koUr$j0Ls(sq%id#K(>oZ-AGNmBdz&&-ic2zgYg{ zHI<)K{&iXBfpb3DnwQ?Us{BKK&d2CNcS*6kD6ufNsHk+Zx77HV`)OY>vW(w;n31`9 z*}P`&y7K=low{su-0Umb@-nQRFTfQ-87p5{yz8aqt9MMvd^kU^$BybY9WR?oWrY7N zu<2O=W=5inOQ(NOWy^bZ&F8QF->r||ckrB&t=lWeSMnSHGuiq+Lm4+rIq!ineec?} zX|1zOr-xp9xb5BV>^+4Yp1fMWDGpbj-N=x?<4(A~XM%HBrVPSx0w&KnN3X>6#N>qZ zw1jc)Vt0whWdf`75jawea~UGNIEOg;AyNj$kMN)u=jwm`!1xi9%iYECY4JVNyZ6kD zpX3P?#E;0$cl(Ot2bbjf3p}pU@MRH4S)jmO9PqddvTogbc1ut0mK(7{xO&4) zfyCi9&8qi9&f%KuDsq?hE~~eY2IL$GKJDRY-f|A^@x=VxKyISf!;d>TU4=MD;wjEe zF(7qbb|W_>nXWo*j3}H1g)^UUwiC{9!dXo?lL==p;fy7mrGzt+a5fUoK;nMob|fdH z^fZ!)uUJFUZoCYhh&i~R-FS6R2zN3PN1Xby8<5llOXiRsy%NNEU#Bk79KrQSPq@lF zvz|{mM~dN8D_$jz?g=SYyhJK#l{&(;)>BytQ(MGo{ALvEJ*Qv3#3gX zAJQ`w>ykF&1omPqza+LDaoLT(R@)(;7jXY@E|>pwM;$|`*2TY_Y&TxH-u!Hj&+9GA zj%*y@^}~7cO5C}HexIi}VBAD-@o#V2jh82{G<&Q&;3|lKgNtD9#Cd%F2uTbv^m&E; z5?`K$xeNbRwB2}lOG}Kukzf4Vns(#m_f2wJVM048^^FZoGEQ-eRHGQFk8$>$%Wa|F zn@XNCi+<*I@e`V0pWS$mIvi+B)r{MSFS{Yxqq|)2N65lCtd5^%whbvR4V09*0v=y+ zM4EBEqSdx&sW+>0mT0cGvte7b)SK0LVl>y=*|05I>dorhJeuq6Y}ghp^=5TF6wUQ^ zHf)QQdb7F)jplkg8@5GFy;)rMM|HiO1>2&g-Yo95M0LGcv<>S@zR`j8TV}CmtjC=n z(fWo9*^O7v6l)7O&S7=^7R|O{uc- pzKYaEgLJ<^^}=l8wNcOE)gP43zEk*luRGg2?m_P9*n>^tuqK|j>1GNUi~ z&8PQ2+V@l7^tMGR;C7joWLlP~A=8RXO_^3@YRR-F({VOKM4Ca z$<^YCba0fE>pk-KBX|9=ArB8r!g}t=(ytTgMByxXe$Efyr{m#iC%tgjuit+AZTWLI zk?a=sh+hV_4agkUZz6ybISSbXUEEXPC1|b z?>L#yDd)3a9xwBIDcOhp^#qyEDPPZiEZw%iDd(d-k0?wQz-6S*tR51_H{B~uN$0mr zTW-s0?3gwh4R5~b-B$9gIbVV*36(e3E3Z7ZEXpe@L~y7z?26WGS7#cQr;YEJ*fCU` z*j1deT9#{@8i8q5*PgXpPis~+*RZQYJEq&gQhH~E>-2C7j z?IL0%Zb;5aiMN^ouTRe5jmWvOi{u>2fQvSO-E_^JBPN1|^-FSgdV19-2+zrY$8tIi z!>_g7rZzV*t{rIFiSjD`r03ZI;aZwivvDsvp5Jz@RQUQu`R(1BZCaYy zZZ_L34Udb)j9*9eaCIj64;H%dXz*gfpUk268Ly(%+n%q@)lCo{b{7JTXLwN>Ae;~% zE0T{D%;{|#w!h`qF_QZ11e!ZBJia@Y5#mGf|CLKfA8Q%-!1|7Xbez6R&VIxd&e)TP zjtEz&_Z^*ZJ3-0~$#0LCT4l!bAmqbg(Xs8h(HO}?_~lCpU(bLqP*em_3(c4qfF+0U%OBZn{mbSx zQv5J};R?df5(lE~Pu?F9gQDeU{2cnXTh5FRy`CA*(i~5uG42%VC=O=mua z0kXdmca|mo8u=Z6=kPy7cDNi)30H~#1J`c0-9-c}xt7_UjS(K03H!g~V~9DWG?n~@ zYt}_yM>?YDa6voNXwE>7x?KQ1f~7LPqW{iyq=z*gVQm1Pu2bmtD_13MUj#~p%l>87 z4OeS8QGkS}*thdBk~2&EV7#Vb+AY8B)ipRO)$M8&GU+j-=M{;cMbC^cZak6@{+2L*rW=KS{WA2$~(&2|lSkfQx4+>#x?+kVM;nWiTuMr^>3mRoJN zB(5H$Ol5!BTv}$eUXoKoC~Z8hqVHviJDCBO?M?kP52mErn)oic#(`*`qxz=!edpHA z@_X=f;q{Of8`hLz&KGGc9mPqAkF`zX1IEm4+ZC+1tzaDt2i#Enac;}5k8ZaKr{zkX z-4uZj0)g8KT9@mc66ZU&XUFCE15qe$tQ3-SP2%N=^OeSo<+s~@U90#_f1P^uz{?_Ub8o&y)>BcyHN5TcCeAc~+GZC9m!FXq5yd8eEim}73@Uh@5B zsctnKON6$5csU+W{A)GHVJdm49$^)t=wZN|Z&l3t#^5S;UY59e23*!N&N~^6sWweJ;Zd<- z`%&&4w)12)fBZ0o*wBwD;>;q3CVpV7R7_=XXGHL`(|jF6p`e$1-GUkY-Xs=iBo zTc(IF0zX(vgngGkPx5wxl&SDoUbSAie&hr$$+=MI&fgXm2zN2Z{kZP<&-P$yZC;E6 zt8~2HSP&%*)$@fvB7XD?dI+{Nvqo#GJyjRg9oH&AyE7(cI&o|oEIopcfk!rr57%kX zaLsA(Y|nTCA4oHTEX=~vH#?=WPgXBR3OGv*}uR2H7rjZ=i*d-pBkhd6#bVE5&rUt z0n$|XSRa+XINybel0MW9E&Mg%4q;AjONGn&SFnBP|D?iQ`xfC2W|&W+`p)$Y()d~O zki3I`EA@O@PgpogB`@l^X_#W#w0jUGE&G=e|Miy%H_Lny$qNBZ3(8!z%woM7xelI% zBl#6R)+8T`OhIWX`Ptr1`+((|hNFp42RGN91}x#hW#ZAw00`nkX@`bhA$=_5@VoWf zp>dp9Zy9D0B2xli%J{VWD)BXN*!bE#GRG`clE;OsKO%m*PqJmOSAw3RltcDX;-j-l ze5?{ae{aZd)Hupk? zf>io|`KgNCsx|#m)Aqz%Y4Uh^RpNCr;H^{tux+yD47wEndk&kG6B@PVyoba=ZPY7SszjjTV^R1RgLnW_WE7EBZ;$l=x3f0FU$%h`~IF~ej3tL9^G%`w)w4HX`rtZgDb@Y1kb>DI?B9z$~LK@FwScTny*zvP?FOm!VU zQ|!DfahEfU2ZOk++B!@6$<9hW(@!LMv(z)h?R%a8z^hixHKt0K5mm!Tuf&HUza#lr z$sm7ZJwt3axqdC4oKJqx0YAs#ml}4H){Dj9Dtcd)xaEv~2I?ITR5C^H;<24zxqKGV z`{F5^p{wFaSonO^KE>j2d$6}e?{V#GvG(ocf9u~ z;>|kviXSYVL4L538y{x@xowAIoP`y91PhjDA%7frU-tSJ)I&cXv<^Rp72-qjhegRp z)^;G^k4ClX`ty#MkH=@|+cE1Q6niWmMm;nuuFRw7K18@{L5-x=Z_kh5-xvgFgmxov z%ftC_d+jL~C>m^Y{!c55B+ zCBEX z8RRsze+TfTr2#)!8q2Nfd5q-FYG=5qsd>JLRX%uFG@jD_ElNBm!+bp2wc^^}hX^;z{aRsfUX`G^HJt93Nu1?LZpFWsbiyxZSTBJ1J}gXy z9q|x9T3*G^CNGnCLCOsFhByL$S`)MCKNPRaR}gL?L%$N`guOe@+w-$HHHX`a!qtSI zHIDS~L2fj(6g=Do5{eDUzbNtZ$4{tdjrLT@oA)4zQ$VPGIeSTdeKYKS&2?8GFyIGE z&fxVoo^(E6{dgt+I4`iF=7zpgya1k4xQK%Jh&0q}u zKJoj}6qN>h*Hx?*{Dlur=d_z}SG8N-vA4e2`ql4kFW~>`ZYwim)@;GEt8I50p7WU2oS90)HcRjvBRp1D zH>Tk)(X>8J=Bz3VaWMyr>Ehq<9pY;{{B}g)yuwK@O#B6o<535=d#_VI@`4-jf3SOf zVd3u6o&AupDBGbyq$&vhQKyd_af&!U=k^OH@Sw~8j0b-?1WP30uT#M~umGqvGgq8y z&1j9PJb|3z_~;I=yJjz{ntSle{s%x6D?( z_H8{r?tCD~hyFn;ptrwyG{^_?yAAZa;NC0r#ri$>6{6!-oay#=f^-xAOLY9d&rDbT zHR+$ryK?#`LO<`8qLIBiJ^2@Xve&XKX_W}k-_Tyoqsd-RJXd}Vb69?Ojw8-qcBP3% z8W#mIANN1}Q^nsE{LY67fAYP8zR)4b8UN!F|7Yb7J(3#V4C606_McrpLA~Gby?X+B zKWFr3BL61hqQLq*_5Jq+=sI6s6LeqhqkD1S<^bJ44ZkJm{xX5CcIUP>#(}33=+?%L z5_I<`(7k8)SV4DB0^MJ|euALuB+wP_KS|JG6~T@5(l75D$DShS?jo{Sy7IH{6?C6T zpc}gCOhK0<$5&4LT|swmLV3UVyR!w|XA|gNJ$Rm=JD5QC%H#!tF3BGMFg7IU9!Mzf ztap#5qC5L1yHeqv`t1t^U6LH%d;StZm!y}y_g$8X?wi+KA?WT)koWFm_ogcE#()0< zL6;=QWzX$PMR)C;A5DdO{xvtJqI=`q+f&h9{Zk_q-D6*~1YMH;Uiue1Re9b?tyFZ| zce#QtslL5&#jK!9s=wd(&Ag!NB>2H=fBGpwmlR*-&$}xX-CN(dCl&6|dk+e_BstFd z?Sq0YNiV

gNSrlHFcA@C8AaRNr3R^O&GZ^835aeL~PB#i7-=o=R2T7hZZg6@T-; z|E!=(lJ`^Imjqo>e17=QAwicUhyKgw1l>c@uihb2>ThqCeN;?;e&W^pv0df<$9GPk z^UDSO(S<9gr~sa>dAUyKlwT;EVF%C4edo{goxds1kEY8CYkm2ncLd9uljls|k>~uF zrz2-zJ_h|T+`cPUAo?^gDO}{z@3&G?cwC;JA@jc~|C#k3xeRaOK_Z0Vkl3!2=ej&U zD9=fsg~CI9=L_^d&bja}A&9*UZ7q1;FPF{24^>L)JzhfEwuQZ7zj>+IMhDTxu zQv8f%&@eFtaSRF81Kf4kFTM8i!F}_euv&w=2FE5w#%>%uVEgsKeFnTpqcwO1Jib%6 z&Cp6{xKM67q?f{*A7CcWHleT!M#e6f7`kAH^V6hqVHrCT=3wRP#6Y}~BzkiJ1Gsf=|ey^9c zNDV)~r;Fdm#qZtX_iOQcwD^5l{9Y`)T8Dk8I6Nj*nkm8li4HrIlj(ZC2+J5L?&Q-< zDV8x>%q5PHWxPo57|X;2T!ef~6nAI6TwI4`41-ehXjPOkQXI~sRZ+%hPA36b)Qvos z>NNhoO=fZ`yQtkec-)8N6z3Rm)JFMv|*>DGPrj)H6K>J`h6?pI0w9|&?rQjsPb_`sj!){l+ zQqA(sdWJIa67D-;Ic+CnA#fSTylKa6S90__j)$^z*x^w|_|Cqa0GH~pi^s~d7bDtO zEC-${^YOK5S&P~R7h*ZMg5Szv_wBNrnVfpYdWUxpI1cD=%r?9nx}9*EdDG#_og;fQ zUym2+u;*<*qh!0P<@xT6>D%ql0Wk}WM;&bE6?%a*f!!}}iCmf#oPXWWeTZFqd=#AuEwO5`c; z{Rw_9fY)bXKSgfXl#97p{sB{-vE&q%2_1=Rg~WDAkCOX4O=GSEFU2zL@S~SK;&qO6 M$jf$@BUtGF0bLC{FaQ7m literal 0 HcmV?d00001