From e274d5f6a9c549a8c404928f527b8243c8d05e94 Mon Sep 17 00:00:00 2001 From: "Moe Charm (CI)" Date: Tue, 9 Dec 2025 19:34:54 +0900 Subject: [PATCH] pool v1 flatten: break down free fallback causes and normalize mid_desc keys --- CURRENT_TASK.md | 255 +++++++++++++++++- core/box/pool_api.inc.h | 187 ++++++++++++- core/box/pool_mid_desc.inc.h | 9 +- .../MID_LARGE_CPU_HOTPATH_ANALYSIS.md | 29 ++ 4 files changed, 474 insertions(+), 6 deletions(-) diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 7df92813..a59e9619 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -1,5 +1,125 @@ ## HAKMEM 状況メモ (2025-12-05 更新 / C7 Warm/TLS Bind 反映) +### Phase80: mid/smallmid Pool v1 flatten(C6-heavy) +- 目的: mid/smallmid の pool v1 ホットパスを薄くし、C6-heavy ベンチで +5〜10% 程度の底上げを狙う。 +- 実装: `core/hakmem_pool.c` に v1 専用のフラット化経路(`hak_pool_try_alloc_v1_flat` / `hak_pool_free_v1_flat`)を追加し、TLS ring/lo hit 時は即 return・その他は従来の `_v1_impl` へフォールバックする Box に分離。ENV `HAKMEM_POOL_V1_FLATTEN_ENABLED`(デフォルト0)と `HAKMEM_POOL_V1_FLATTEN_STATS` でオンオフと統計を制御。 +- A/B(C6-heavy, ws=400, iters=1M, `HAKMEM_BENCH_MIN_SIZE=257` / `MAX_SIZE=768`, `POOL_V2_ENABLED=0`, Tiny/Small v2/v3 は従来どおり): + - flatten OFF (`POOL_V1_FLATTEN_ENABLED=0`): Throughput ≈ **23.12M ops/s**、`[POOL_V1_FLAT] alloc_tls_hit=0 alloc_fb=0 free_tls_hit=0 free_fb=0`。 + - flatten ON (`POOL_V1_FLATTEN_ENABLED=1`): Throughput ≈ **25.50M ops/s**(約 +10%)、`alloc_tls_hit=499,870 alloc_fb=230 free_tls_hit=460,450 free_fb=39,649`。 +- 所感: 自スレッド TLS fast path を太らせるだけで目標どおり +10% 程度の改善が得られた。まだ free_fb がそこそこ残っているため、次に詰めるなら page_of / 自スレ判定の精度を上げて free_fb を削るフェーズ(Pool v1 flatten Phase2)を検討する。運用デフォルトは引き続き `POOL_V1_FLATTEN_ENABLED=0`(安全側)とし、bench/実験時のみ opt-in。 + +### Phase81: Pool v1 flatten Phase2(free_fb 内訳の可視化) +- 変更: flatten stats に free fallback の理由別カウンタを追加(page_null / not_mine / other)。`hak_pool_free_v1_flat` で mid_desc 取得失敗 → page_null、owner 不一致等 → not_mine、その他 → other として集計。 +- ベンチ(C6-heavy, 1M/400, Release, Tiny/Pool v2 OFF, small v3 OFF, `POOL_V1_FLATTEN_ENABLED=1`): + - flatten OFF: **23.68M ops/s**(stats 0)。 + - flatten ON : **25.90M ops/s**(約 +9.4%)、`alloc_tls_hit=499,870 alloc_fb=230 free_tls_hit=460,060 free_fb=40,039 page_null=40,039 not_mine=0 other=0`。 +- 所感: free fallback はほぼすべて mid_desc 取得失敗(page_null)によるもの。owner mismatch は 0。次は page_of/mid_desc 判定を精度アップさせ、free_fb をさらに削る余地がある。デフォルトは引き続き flatten OFF(安全側)。 + +### Phase82: mid_desc マスク整合(free_fb 削減の第一歩) +- 変更: `mid_desc_register/lookup/adopt` が扱うページアドレスを `POOL_PAGE_SIZE` で正規化し、mmap の 64KiB 非アラインでも lookup が一致するように修正。flatten stats は page_null/not_mine/other もダンプするよう拡張済み。 +- ベンチ(C6-heavy, 1M/400, Release, tiny/pool v2 OFF, LEGACY tiny, flatten ON): + - flatten OFF: **23.68M ops/s**(参考)。 + - flatten ON : **26.70M ops/s**(約 +13% vs OFF)、`alloc_tls_hit=499,871 alloc_fb=229 free_tls_hit=489,147 free_fb=10,952 page_null=3,476 not_mine=7,476 other=0`。 +- 所感: page_null が大幅減少、not_mine が顕在化。owner 判定を少し緩め/精度アップする余地はあるが、デフォルトは引き続き flatten OFF(実験のみ ON)。 + +### PhaseA/B (SmallObject HotBox v3 C7-only 通電) +- 追加: `HAKMEM_SMALL_HEAP_V3_ENABLED/CLASSES` gate、route `TINY_ROUTE_SMALL_HEAP_V3`、front の v3 経路(fallback 付き)。 +- 型/IF: `core/box/smallobject_hotbox_v3_box.h` に so_page/class/ctx+stats、TLS 初期化を実装。`smallobject_cold_iface_v1.h` で v1 Tiny への Cold IF ラッパ(C7 専用)を用意。 +- Hot path: `core/smallobject_hotbox_v3.c` で so_alloc/so_free を実装(current/partial freelist を持つ)。C7 で refill は Tiny v1 からページを借り、freelist を v3 で carve。retire 時に v1 へ返却。fail 時は v1 にフォールバック。 +- デフォルト: ENV 未指定時は C7-only で v3 ON(`HAKMEM_SMALL_HEAP_V3_ENABLED` 未設定かつ CLASSES 未設定で class7 に v3 を適用)。`HAKMEM_SMALL_HEAP_V3_ENABLED=0` または CLASSES から bit7 を外せばいつでも v1 経路に戻せるようにしている。 + +### Phase65-c7-v3-HEAP_STATS(C7-only v3 A/B 追加確認) +- 短尺 20k/ws=64: + - v3 OFF: **40.91M ops/s**, `HEAP_STATS[7] fast=11015 slow=1`。 + - v3 ON (`CLASSES=0x80`): **56.43M ops/s**、`SMALL_HEAP_V3_STATS` で `alloc_refill=49 fb_v1=0 page_of_fail=0`(短尺ウォームアップ由来の refill)。segv/assert なし。 +- 長尺 1M/ws=400: + - v3 OFF: **38.29M ops/s**, `HEAP_STATS[7] fast=550099 slow=1`。 + - v3 ON: **50.25M ops/s**、`alloc_refill=5077 fb_v1=0 page_of_fail=0`。slow は v1 と同等レンジ。 +- Mixed 16–1024B 1M/ws=400(参考): + - v3 OFF: **42.35M ops/s**, `HEAP_STATS[7] fast=283169 slow=1`。 + - v3 ON: **49.60M ops/s**, `alloc_refill=2446 fb_v1=0 page_of_fail=0`。 +- 結論: HEAP_STATS 的にも slow≈1 を維持しつつ、v3 ON は C7-only/Mixed で大きくプラス。デフォルトでは C7-only v3 を ON(ENV 未指定で ENABLED=1, CLASSES=0x80 相当)としつつ、混乱や回帰時に備えて `SMALL_HEAP_V3_ENABLED=0` / クラスマスクでいつでも v1 に戻せるようにしている。*** + +### Phase63: C6 v2 A/B(bench専用マスクでの初回計測) +- C6-heavy (min=257/max=768, ws=400, iters=1M, PROFILE=C7_SAFE, C7_HOT=1, v2 stats ON) + - v2 OFF (`HOTHEAP_V2=0`): **42.15M ops/s**, HEAP_STATS[7] fast=283169 slow=1。 + - v2 ON (`HOTHEAP_V2=1`, classes=0x40): **29.69M ops/s**(大幅回帰)。HEAP_STATS[6] fast=266914 slow=16。v2 stats cls6 route_hits=0 / free_fb_v1=266930 で実質 v1 経路に落ちており、v2 専用処理が活きていない。 + - 所感: C6 v2 を有効にすると現状大きく劣化。C6 マスクは研究用のまま(本線は C7 v1/v2 のみ)。 +- Mixed 16–1024B (ws=400, iters=1M, PROFILE=C7_SAFE, C7 v2 ON, v2 stats ON) + - C7 v2 only (`classes=0x80`): **45.07M ops/s**、HEAP_STATS[7] fast=283170 slow=2276、v2 alloc_lease/refill=2276。 + - C6+C7 v2 (`classes=0xC0`): **35.65M ops/s**(大幅回帰)。HEAP_STATS[6] fast=137306 slow=1、cls7 slow=2276。v2 stats cls6 route_hits=0(C6 依然 v1)、cls7 refills=2276。 + - 所感: C7 v2 は long-run で安定していたが、今回の 1M/400 では refill が 2,276 件まで増えスルーが低下。C6 を v2 に乗せる構成は混在でも回帰が大きく、当面研究箱のままに固定。 + +### Phase64: C6 v2 route 修正 / C7 v2 refill 再トリアージ +- front の route switch を汎用化し、class6 でも `TINY_ROUTE_HOTHEAP_V2` を直接呼ぶよう修正。v2 stats に route 値を追加。 +- C6-heavy v2 ON (classes=0x40, ws=400, iters=100k): route_hits=26660, alloc_refill=1, fallback_v1=0、throughput **35.52M ops/s**(v1よりは低めだが v2 パスが有効に)。 +- C7-only 20k/ws=64 v2 ON: HEAP_STATS[7] slow=48、v2 alloc_refill=48(v2 OFF は slow=1)。Mixed 20k/ws=256 v2 ON でも alloc_refill=42。短尺では refill 多発が残っており原因再調査中。 +- Next: C7-only 長尺でも slow/refill が増えているかを再確認し、refill_slow/partial ポリシーを見直す。C6 v2 は route/gate が通るようになったので、性能 A/B を改めて計測(未実施)。デフォルトは引き続き v2 OFF。 + +### Phase71: PoolHotBox v2 初回 A/B(Cold IF=v1)→ 失敗 +- C6-heavy (min=2048/max=8192, ws=400, iters=1M, PROFILE=C7_SAFE, Tiny v2 OFF) + - v2 OFF (`POOL_V2_ENABLED=0`): **30.57M ops/s**。 + - v2 ON (`POOL_V2_ENABLED=1 POOL_V2_CLASSES=0x7F/0x1`): 実行直後に **SIGABRT**(gdb で `hak_pool_free_v2_impl` → `pool_hotbox_v2_page_of` の fail-fast で abort)。10k 短尺でも同様に abort し、`POOL_V2_STATS` は出力されず。 +- 所感: v2 alloc→v1 free の混線か page_of 範囲判定で落ちており、構造A/B がまだ通らない。デフォルトは v1 のまま維持。次ステップでは route/gate と page_of 整合を確認し、free_fb_v1 が跳ねない形に直してから再A/B する。 + +### Phase65/66: v2 refill 可視化(C7短尺)と C6 v2 調査の前段 +- C7-only (ws=64, iters=20k, v2 ON, stats ON): HEAP_STATS[7] fast=11016 slow=48。新設の refill stats で `refill_with_current=0 / refill_with_partial=0` → current/partial が空の状態で 48 回 refill 発生していることを確認(retire 0)。 +- v2 OFF 同条件: slow=1(baseline)。短尺の refill 多発は依然再現するが、current/partial を失ってから refill しているパターンに絞れた。 +- C6 v2 の性能トリアージは未着手(route は修正済み)。次ステップで C6-heavy / Mixed A/B を再取得し、route_hits>0 でのスループット/slow を確認する。 +- Phase65 後半(長尺本命プロファイル) + - C7-only 1M/ws=400: v2 OFF **38.24M ops/s**, v2 ON **38.68M ops/s**(HEAP_STATS[7] fast=550099 slow=1、refill=1)。 + - Mixed 16–1024B 1M/ws=400: v2 OFF **41.78M ops/s**, v2 ON **41.94M ops/s**(HEAP_STATS[7] fast=283169 slow=1)。refill は 1 件に収束し、fail/fallback なし。 + - 結論: 短尺の refill≒50 はウォームアップ由来。本命プロファイルでは v2 ON/OFF で slow≈1 に張り付き、性能も ±5% 以内(むしろ微プラス)。 + - 運用方針: デフォルト構成では `HAKMEM_TINY_HOTHEAP_V2=0` を維持しつつ、C7-only の bench/pro プロファイルでは `HAKMEM_TINY_HOTHEAP_V2=1 HAKMEM_TINY_HOTHEAP_CLASSES=0x80` を opt-in 推奨とする(Mixed/本番では明示しない限り OFF)。 + +### Phase59: C7 HotHeap v2 Mixed A/B(C7-only 研究箱の現状) +- Mixed 16–1024B(ws=256, iters=20k, `PROFILE=C7_SAFE`, v2 C7-only, v2 stats ON)で v2 ON/OFF を比較: + - v2 OFF (`HAKMEM_TINY_HOTHEAP_V2=0`): **45.11M ops/s**, HEAP_STATS[7] fast=5691 slow=1。 +- v2 ON (`HAKMEM_TINY_HOTHEAP_V2=1`, classes=0x80): **46.21M ops/s**(約 +2.4%)、HEAP_STATS[7] fast=5692 slow=45。 + - HOTHEAP_V2_C7_STATS: route_hits=5692, alloc_fast=5692, alloc_lease/refill=45, cold_refill_fail=0, page_retired=4, fallback_v1=0。 + - C7_PAGE_STATS: prepare_calls=45 → refill が多く current/partial を握り切れていない。 +- 方針: v2 は Mixed でも微プラスだが slow_prepare が増えている。refill 後のページを温存する/partial を活用するポリシー調整で slow≈1 を目指す。デフォルトは引き続き v2 OFF(C7_SAFE v1 本線)。 + +### Phase60: C7 v2 空ページ保持ポリシー導入(partial 温存+追加 stats) +- 変更: `tiny_hotheap_class_v2` に `max_partial_pages`(C7 デフォルト 2)と `partial_count` を追加し、free で `used==0` のページは retire せず partial に温存。上限超のみ retire。partial push/pop/peak と retire_v2 を v2 stats に追加。 +- ベンチ: + - C7-only (ws=64, iters=20k, PROFILE=C7_SAFE): + - v2 OFF: 41.94M ops/s, HEAP_STATS[7] fast=11015 slow=1。 + - v2 ON: 50.43M ops/s, HEAP_STATS[7] fast=11016 slow=48。HOTHEAP_V2_C7_STATS: alloc_lease=48 (=refill), partial_push/pop/peak=0, retire_v2=0。 + - Mixed 16–1024B (ws=256, iters=20k, PROFILE=C7_SAFE): + - v2 OFF: 42.82M ops/s, HEAP_STATS[7] fast=5691 slow=1。 + - v2 ON: 47.71M ops/s, HEAP_STATS[7] fast=5692 slow=42。HOTHEAP_V2_C7_STATS: alloc_lease=42 (=refill), partial_push/pop/peak=0, retire_v2=0。 +- 所感: slow_prepare は refill 回数と一致し、空ページがほぼ出ないため partial/retire はまだ発火していない。v2 は C7-only/Mixed ともプラスだが、refill=40〜50 が残る。ページ容量/lease 戦略や空ページを作る負荷での検証が次課題。デフォルトは引き続き v2 OFF(研究箱扱い)。 + +### Phase61: C7 v2 長尺 Mixed (ws=400, iters=1M) 安定性チェック +- プロファイル: Mixed 16–1024B, ws=400, iters=1M, PROFILE=C7_SAFE, C7_HOT=1, LARSON_FIX=1, v2 classes=0x80, STATS_BOX ON, STATS_BATCH=0。 +- ベンチ: + - v2 OFF: **41.58M ops/s**, HEAP_STATS[7] fast=283169 slow=1。fail/fallback=0。 + - v2 ON: **42.41M ops/s**(約 +2%)、HEAP_STATS[7] fast=283169 slow=1。v2 statsは特記なし(fail/fallbackなし)。 +- 所感: 長尺でも v2 ON で回帰なく微プラスを維持。slow=1 に張り付き、短尺で見えた refill 多発は再現せず。引き続きデフォルトは v2 OFF のまま研究箱扱い。 + +### Phase61': C7 v2 短尺 Mixed 再確認(ws=256, iters=20k) +- プロファイル: Mixed 16–1024B, ws=256, iters=20k, PROFILE=C7_SAFE, C7_HOT=1, LARSON_FIX=1, v2 classes=0x80。 +- ベンチ: + - v2 OFF: **43.27M ops/s**, HEAP_STATS[7] fast=5691 slow=1。 + - v2 ON: **44.57M ops/s**(約 +3%)、HEAP_STATS[7] fast=5691 slow=1。 +- 所感: 短尺でも slow_prepare は v2 OFF/ON ともに 1 件に収まり、fail/fallback も 0 で安定。Phase59 時点で見えていた「slow≈refill で 45 程度」という状態から改善され、C7 v2 は C7-only / Mixed / 短尺・長尺いずれでも v1 C7_SAFE を上回る構造になった。運用デフォルトは引き続き v2 OFF だが、bench/研究プロファイルでは C7 v2 を本命候補として扱える状態。 + +### Phase68: mid/smallmid・pool 方面への次ターゲット整理 +- 現状: mid/smallmid (257–768B メイン) のベースラインは **HAKMEM ≈28–29M ops/s** に対し mimalloc ≈54M / system ≈15M。Tiny 16–1024B は ~41–42M と比べ、mid/pool 側が大きく劣る。 +- ホットスポット: perf では `hak_pool_try_alloc/free`, `memset`, `mid_desc_lookup` が主因。pf/sys は小さく、CPU 側命令数削減がボトルネック。 +- 目標: mid/smallmid で +5〜10%(28–29M → 30–32M)をまず達成すること。 +- 方針: Tiny v2/C6 v2 は研究箱のまま固定し、pool/smallmid の Hot Box 化設計に着手(新規 POOL_V2_BOX_DESIGN を作成)。運用デフォルトは変えず、実装は段階的に A/B できるようゲート前提で進める。 + +### Phase62: C6 v2 実験箱の足場を追加(コード実装のみ、デフォルト OFF) +- 変更: + - TinyHotHeap v2 を C6 でも動くように拡張(Hot Box ロジックを class_idx パラメータ化、stats をクラス配列化、TLS 初期化で C6 も partial 保持 2 枚に設定)。 + - Route/Front は既存の 1 LUT + 1 switch をそのまま利用し、`HAKMEM_TINY_HOTHEAP_V2=1 HAKMEM_TINY_HOTHEAP_CLASSES=0x40` で C6 v2 を opt-in。 + - Cold IF は v1 TinyHeap をラップする既存実装を流用(refill/retire のみ触る)。fallback 記録/カウンタもクラス別に整備。 +- 状態: + - デフォルトは v2 OFF(C7 SAFE v1 が本線)。C6 v2 は bench/研究専用の opt-in。ベンチ未実施(次フェーズで C6-heavy / Mixed A/B を取得予定)。 + - C7 v2 の安定性・性能は維持(C6 追加による挙動変化はなし)。 + ### 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 に落とし、境界を @@ -13,6 +133,29 @@ - 実装ガイド: `docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md`(本フェーズで骨子を追加、実装担当 AI/開発者向けの指示書)。 - 詳細設計: `docs/analysis/TINY_HEAP_V2_DESIGN.md`(Phase36+ セクションに A/B/C 案と Box 構造を集約)。 +### Phase 36+ (ChatGPT Pro フィードバック統合 / TinyHeap v2 ロードマップ再定義) +- 状況整理: + - Mixed 16–1024B: HAKMEM ≈41M ops/s / mimalloc ≈113M / system ≈92M → HAKMEM は mimalloc の ~36%、system の ~45%。 + - mid/smallmid: HAKMEM ≈28–29M / mimalloc ≈54M / system ≈15M → mid/pool は mimalloc の ~50%、system の ~2×。 + - Superslab/OS: SS_OS_STATS では 1M ops あたり `alloc≈2 free≈3 madvise≈2` 程度で、OS 呼び出しは支配的ではない。WarmPool (C7) は hit≈99%。 + - pf: ≈6.6k/1M ops はほぼ first-write 起因と推定され、HugePage/ヘッダ軽量化実験でも大きく減らせていない。 +- ChatGPT Pro からの提案(要約): + - 次に大きく変えるべきは TinyHeap v2 の Hot 層であり、「v1 の上に乗るラッパ」ではなく **v1 と并列の Hot Box** として再設計する。 + - Superslab/Tier/Guard/Stats/Learning は v1/v2 共通の Cold Box とし、Hot→Cold の境界は共通インタフェース(`TinyColdIface` 的なもの)に集約する。 + - TinyHeap v2 は C5–C7 をカバーしつつ、rollout は C7-only → C6 → C5 の順に段階的に行う。v1 は常に fallback/safe path として残し、PolicySnapshot で `tiny_heap_version[class]` を切り替える。 + - mid/smallmid/pool v2 は第2波の最適化対象とし、pf/first-touch/HugePage は v3 以降(最後の 5〜10% を詰めるテーマ)に回すのが妥当。 +- ドキュメント反映: + - `docs/analysis/TINY_HEAP_V2_DESIGN.md` に「ChatGPT Pro からのフィードバックと v2 ロードマップ」セクションを追加し、 + - v1/v2 并列 Hot Box 構造(Front/Gate → TinyHeapBox v1 or TinyHotHeapBox v2 → 共通の Cold Box) + - v1/v2 共通の Cold インタフェース(TinyColdIface)導入方針 + - C7-only → C5–C7 への段階的 rollout 戦略 + - v2 世代では Superslab/Segment/Tier/Guard/Remote の構造は変えず、v3 世代で SmallObjectHeap 全体を再構成する + を明文化。 +- 今後のロードマップ(v2 世代の位置づけ): + - v2 では Tiny front/route Box(済)+ TinyHotHeap v2 の Hot Box 再設計に集中し、Cold Box 側はほぼ据え置きとする。 + - mid/smallmid/pool v2 は構造スケッチと A/B ゲートまでに留め、本線は pool v1 のまま。 + - pf/first-touch/HugePage は研究用モード(Mode A/B)として設計・実装を持ちつつ、運用デフォルトには含めない。 + ### 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)。 @@ -99,6 +242,7 @@ - C7_SAFE (C6 OFF, `PROFILE=C7_SAFE`): **40.96M ops/s**(cls7 fast=5691 / slow=1)。 - C6+C7 SAFE (`HEAP_CLASSES=0xC0` / C6+7 HOT / meta_mode=1): **27.21M ops/s**(cls6 fast=1388 / slow=1366、cls7 fast=5664 / slow=19)。 - 結論: C6 TinyHeap は mode0/1 いずれも C6-heavy/Mixed で大幅マイナス。C6 meta_mode=1 は slow_prepare が増え性能も悪化。C6 は引き続き bench/実験専用マスク(0x40/0xC0)とし、通常は LEGACY または C7_SAFE プロファイルを推奨。 + - 現状の扱い: C6 v2/TinyHeap は構造レベルでは通電しているが perf 未達のため「研究箱」に固定し、`HAKMEM_TINY_HOTHEAP_CLASSES=0x40/0xC0` は常に opt-in(実験時のみ ON)とする。本線の TinyHeap は C7 SAFE(v1/v2)のみ。 ### Phase 20: C6 Hot front の箱追加(C7 対称の直線パス) - 新規ドキュメント: `docs/analysis/C6_HOTBOX_DESIGN.md` を追加し、C6 を TinyHeap でホット化する箱の目的と境界を定義(SAFE のみ、ULTRA なし)。C6 TinyHeap は当面 bench/実験扱いと明記。 @@ -600,6 +744,32 @@ - v2 ON: **24.73M ops/s** - 所感: 回帰を避けるため標準は v1 を維持しつつ、どの変更が悪かったかをスイッチ単位でA/Bできるようにした。次は各スイッチON/OFFでの差分取り、必要なら v2 を研究箱のまま凍結。 +### Phase69: PoolHotBox v2 初回 A/B(Cold IF まだダミー) +- 状況: HotBox v2 を posix_memalign/free ベースの Cold IF で通電。Pool v2 は研究箱のまま(デフォルト OFF)。 +- ベンチ (Release, min=2048/max=8192, ws=400, iters=100k, C7_SAFE, Tiny v2 OFF): + - v2 OFF: **28.70M ops/s** + - v2 ON (classes=0x7F, Cold IF=posix_memalign): **2.15M ops/s** + - Stats: `alloc_refill=1630` (cls0), `alloc_refill_fail=0`, `free_fb_v1=50437` → page_of/class 判定が合わず v1 free に大量フォールバック。 +- 次の一手: Cold IF を v1 pool/Superslab 経路に差し替え、free_fb_v1 の主因(page_of or class 判定)を潰した上で再A/B。研究箱のまま進め、標準は v1 を維持。 + +### Phase70: PoolHotBox v2 Cold IF 切替(進行中) +- 変更: Cold IF を posix_memalign から v1 ベースの mmap/POOL_PAGE_SIZE + mid_desc_register に切り替え、retire も munmap に統一。page_of で class/base 範囲を厳密チェックし、ミスマッチ時は Fail-Fast(v2 内で v1 fallback しない)に変更。free_fb_v1 は front 側のみでカウントする前提に整理。 +- ビルド: `make bench_random_mixed_hakmem -j4` 成功(警告のみ)。 +- 次アクション: C6-heavy で v2 OFF/ON を再A/B(POOL_V2_ENABLED=1, CLASSES=C6ビット, POOL_V2_STATS=1)。free_fb_v1 が 0 近傍か、refill/fail が妥当かを確認して Phase69 の回帰を再評価する。 + +### Phase72: PoolHotBox v2 page_of O(1) 化と凍結判断 +- 実装・挙動整理: + - PoolHotBox v2 の page_of を 64KiB アラインマスク+ページ先頭ヘッダに埋めた self ポインタで O(1) 化し、retire/初期化時にヘッダを必ずセット/クリアするように変更。ヘッダ領域を除いた容量から freelist を carve するよう capacity 計算も揃えた。 + - Cold IF は v1 pool/Superslab ベースの mmap + mid_desc_register / munmap に統一し、HotBox v2 からは geometry/token だけを受け取る構造に整理。 + - C6-heavy 短〜中尺(10k〜100k/ws=400, v2 ON, POOL_V2_STATS=1)では page_of_fail_x=0 / free_fb_v1=0 / alloc_refill 十数〜数十回で完走し、構造バグや大量 fallback は解消された。 +- 1M 長尺 C6-heavy での結果: + - v2 OFF: ≈27〜30M ops/s で安定完走。 + - v2 ON: 120s タイムアウトで完走せず(ハング/極端な遅さ)。page_of_fail は短尺では 0 だが、長尺では v1 比で極端な回帰となる。 +- 結論と方針: + - PoolHotBox v2 の箱構造と Cold IF は一通り通電し、研究用には十分な観測性が得られたものの、C6-heavy 長尺での性能回帰が大きく、現フェーズ(Tiny v2 + mid/pool v2)のスコープでは本線候補に持ち上げるのは難しい。 + - 本フェーズでは PoolHotBox v2 を **研究箱として凍結** し、標準構成は引き続き `HAKMEM_POOL_V2_ENABLED=0`(v1 pool)とする。 + - 将来の v3 テーマとして mid/smallmid/pool の Hot/Cold 再設計を行う際に、本フェーズの実装・perf/abort ログを出発点とする。*** + ### 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。 - 研究箱: @@ -608,5 +778,86 @@ - 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 のさらなるフラット化。 +- first-touch/page-fault 系の本格対応(HugePage/ヘッダ light の昇格可否検証)。 +- mid/smallmid の pool/フロント最適化、または mid/large route のさらなるフラット化。 + +### Phase65-c7-v3 short/long A/B(C7-only, Tiny v2/pool v2 OFF) +- 短尺 (20k, ws=64): v3 OFF `41.26M` ops/s → v3 ON `57.55M` ops/s、`alloc_refill=49, fb_v1=0, page_of_fail=0`(HEAP_STATS slow 未取得だがクラッシュなし)。 +- 長尺 (1M, ws=400): v3 OFF `38.26M` ops/s → v3 ON `50.24M` ops/s、`alloc_refill=5077, fb_v1=0`、クラッシュ/アサートなし。 + +### Phase65-c7-v3 mixed sanity(16–1024B, ws=400, iters=1M, Tiny v2/pool v2 OFF) +- v3 OFF `41.56M` ops/s → v3 ON `49.40M` ops/s、`alloc_refill=2446, fb_v1=0`、異常なし。 +- v3 は依然デフォルト OFF(HAKMEM_SMALL_HEAP_V3_ENABLED=0)。C7-only の研究/bench では `ENABLED=1, CLASSES=0x80` で opt-in 可能。 + +### Phase73: Tiny front v3 スナップショット化(Mixed 16–1024B, C7 v3 ON) +- 追加: `HAKMEM_TINY_FRONT_V3_ENABLED`(デフォルトOFF)と `HAKMEM_TINY_FRONT_V3_STATS` ENV を追加し、front v3 有効時に + - unified_cache_on + - tiny_guard_on + - header_mode + を 1 回だけキャッシュする `TinyFrontV3Snapshot` を導入。ホットパスでは snapshot を読むだけにして guard/UC 判定の呼び出しを整理。 +- コード影響: malloc/free 両方が snapshot を読んで UC/guard 判定をキャッシュ経由で処理(front v3 OFF のときは従来通り)。 +- Mixed 16–1024B (ws=400, iters=1M, C7 v3 ON, Tiny/Pool v2 OFF) で A/B: HEAP_STATS slow=1 のまま挙動変化なし(perf は後続フラット化で狙う)。 + +### Phase74: Tiny front v3 size→class LUT 化(挙動維持のまま前段を軽量化) +- ENV: `HAKMEM_TINY_FRONT_V3_LUT_ENABLED`(デフォルト OFF、front v3 ON 時だけ有効)。ON 時は Tiny 前段が size→class→route を LUT 1 ルックアップで取得し、従来の `hak_tiny_size_to_class` + route 読みを置き換える。 +- LUT は起動時に既存の size→class 変換と route スナップショットを写経して構築するため挙動は不変。LUT/Front v3 が無効なときは自動で旧経路にフォールバック。 +- A/B (Mixed 16–1024B, ws=400, iters=1M, C7 v3 ON, Tiny/Pool v2 OFF, front v3 ON): + - LUT=0: 44.820M ops/s + - LUT=1: 45.231M ops/s(約 +0.9%) + - C7_PAGE_STATS: prepare_calls=2446(HEAP_STATS は TinyHeap v1 経路外のため未出力) +- 次: size→class→route 前段のさらなるフラット化(Phase2-B 相当)で 5〜10% を狙う。 + +### Phase75: Tiny front v3 route fast path(LUT→1 switch) +- ENV: `HAKMEM_TINY_FRONT_V3_ROUTE_FAST_ENABLED`(デフォルト OFF)。front v3 + LUT が ON のときだけ、LUT に写した route_kind を直接使い `tiny_route_for_class` 呼び出しを省略する。 +- 実装: malloc_tiny_fast の Tiny 前段で size→class→route を LUT 2 バイト load だけで決定し、直後の 1 switch で v3/v2/v1/legacy に分岐。route_fast=0 では Phase74 と同じ経路に自動フォールバック。 +- A/B (Mixed 16–1024B, ws=400, iters=1M, C7 v3 ON, Tiny/Pool v2 OFF, front v3 ON, LUT ON): + - route_fast=0: 45.066M ops/s + - route_fast=1: 44.821M ops/s(約 -0.5%) + - C7_PAGE_STATS: prepare_calls=2446、回帰なし(HEAP_STATS は TinyHeap v1 経路外)。 +- 所感: 微小マイナスのためデフォルトは ROUTE_FAST=0 のまま。次は size→class 前段・header/guard 判定の整理など、別軸のフラット化を検討。 + +### Phase76: Tiny front v3 Header v3 (C7-only軽量ヘッダ) +- ENV: `HAKMEM_TINY_HEADER_V3_ENABLED`(デフォルト0), `HAKMEM_TINY_HEADER_V3_SKIP_C7`(デフォルト0)。有効時は C7 v3 alloc だけ tiny_region_id_write_header を通さず 1byte を軽く書いて返す。 +- snapshot に header_v3_enabled/skip_c7 を追加(他クラスや v1/v2/pool には無影響、いつでも ENV で OFF)。 +- A/B (Mixed 16–1024B, ws=400, iters=1M, C7 v3 ON, front v3 ON, LUT ON, route_fast=0, Tiny/Pool v2 OFF): + - header_v3=0: 44.29M ops/s, C7_PAGE_STATS prepare_calls=2446 + - header_v3=1 + SKIP_C7=1: 43.68M ops/s(約 -1.4%)、prepare_calls=2446、fallback_v1/page_of_fail=0 +- 所感: ヘッダ簡略だけでは perf 改善せず。free 側のヘッダ依存を下げる / header_light 再設計を別フェーズで検討。デフォルトは HEADER_V3=0 のまま。 + +### Phase58: TinyColdIface 導入と C7 v2 Cold 境界接続(WIP / v2 は引き続き研究箱) +- 変更内容(コード側の状態メモ): + - `core/box/tiny_cold_iface_v1.h` に `TinyColdIface` を追加し、v1 TinyHeap の `tiny_heap_prepare_page()` / `tiny_heap_page_becomes_empty()` を + - `refill_page(cold_ctx, class_idx)` + - `retire_page(cold_ctx, class_idx, page)` + の 2 関数にラップ。Hot Box(v2 など)はこの IF 経由でのみ Superslab/Tier/Stats に触れる方針を具体化した。 + - `core/hakmem_tiny.c` の C7 v2 実装を更新し、`tiny_hotheap_v2_refill_slow()` / `tiny_hotheap_v2_page_retire_slow()` が `TinyColdIface` 経由で v1 TinyHeap に + ページ借用・返却を行うよう変更。refill では: + - `cold.refill_page(tiny_heap_ctx_for_thread(), 7)` で `tiny_heap_page_t` を 1 枚取得 + - v2 側の `tiny_hotheap_page_v2` ノードを確保し、`base/capacity/slab_idx/meta` などをコピー + - v1 ページに freelist が無い場合でも、v2 が `used=0` にリセットして `tiny_hotheap_v2_build_freelist()` で自前 freelist を構築する + - freelist がある場合は `lease_page->meta->freelist` を v2 側 freelist で更新 + - `current_page` に必ず freelist/capacity を持つページが入るように Fail-Fast チェックを追加 + - TLS 初期化 (`tiny_hotheap_v2_tls_get()`) を整理し、クラスごとに `storage_page` を `tiny_hotheap_v2_page_reset()` でゼロ化しつつ `stride=tiny_stride_for_class(i)` を事前設定。 + v2 側の page 構造は常に Hot Box 内で初期化される。 + - v2 統計を強化し、`cold_refill_fail` / `cold_retire_calls` など Cold IF 周辺のカウンタを `HAKMEM_TINY_HOTHEAP_V2_STATS=1` でダンプ。 +- 現状の挙動 / ベンチ: + - C7-only (ws=64, iters=20k, `PROFILE=C7_SAFE`, v2 OFF) は依然として **≈42M ops/s**, `HEAP_STATS[7] fast=11015 slow=1` を維持(v1 C7_SAFE は安定)。 + - v2 ON では、Cold IF 経由の refill/carve までは進むものの、ウォームアップ直後の `tiny_hotheap_v2_try_pop()` 付近で SIGSEGV が再現し、ベンチ完走前に落ちる。 + - 既存ログでは `route_hits` / `alloc_calls` は増えるが、`cold_refill_fail` は 0 近傍まで減少しており、「refill が常に失敗する」状態は解消。 + - SEGV は v2 `current_page` / `lease_page` と v1 TinyHeap の `tiny_heap_page_pop()` / `mark_full` の相互作用に起因している可能性が高い。 +- 問題の整理(Box Theory 観点): + - `tiny_hotheap_v2_try_pop()` はまだ v1 TinyHeap の `tiny_heap_page_pop()` / `tiny_heap_page_mark_full()` を直接呼び出し、 + - `v2page->lease_page` の `free_list/used` を v1 関数で更新しつつ + - v2 側の `candidate->freelist/used` にもコピーする + という二重管理になっている。 + - 本来の設計では「Hot Box (v2) は自分の `tiny_hotheap_page_v2.freelist` だけを pop/push し、Cold Box とは `TinyColdIface` の refill/retire 境界でのみ接続する」べきであり、 + v1 TinyHeap の page/pop ロジックに依存している現状は箱の境界が曖昧になっている。 + - その結果として、v1 側の meta/used/freelist と v2 側の freelist/capacity がずれた状態で deref され、SEGV や不整合の温床になっていると考えられる。 +- 次の一手(実装担当 AI 向け指示書の要点 / 詳細は `docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md` に追記済み想定): + 1. `tiny_hotheap_v2_try_pop()` から v1 TinyHeap 依存を排除し、**v2 の freelist だけで pop する直線パス**に書き換える。 + - refill 時に `TinyColdIface.refill_page()` から得た `tiny_heap_page_t` の `base/capacity` だけを信頼し、`tiny_hotheap_page_v2` 内で freelist を完全に自前構築する。 + - Hot path では `lease_page` を「Cold に返すための token」として保持するだけにし、`tiny_heap_page_pop()` / `mark_full()` など v1 の API は呼ばない。 + 2. `tiny_hotheap_v2_page_retire_slow()` では、page が空になったタイミングでのみ `TinyColdIface.retire_page(cold_ctx, class_idx, lease_page)` を呼ぶようにし、 + v2 内部のリスト (`current_page` / `partial_pages` / `full_pages`) から unlink したら Hot 側の state を全て破棄する。 + 3. `TinyColdIface` を **「refill/retire だけの境界」**として明確化し、Hot Box から Cold Box への侵入(meta/used/freelist の直接操作)をこれ以上増やさない。 + 4. C7-only で v2 ON/OFF を A/B しつつ、`cold_refill_fail` が 0 に張り付いていること、`alloc_fast` ≈ v1 の `fast` 件数に近づいていることを確認する(性能よりもまず安定性・境界の分離を優先)。 diff --git a/core/box/pool_api.inc.h b/core/box/pool_api.inc.h index c2ed511d..8571518d 100644 --- a/core/box/pool_api.inc.h +++ b/core/box/pool_api.inc.h @@ -3,6 +3,7 @@ #define POOL_API_INC_H #include "pagefault_telemetry_box.h" // Box PageFaultTelemetry (PF_BUCKET_MID) +#include "box/pool_hotbox_v2_box.h" // Pool v2 is experimental. Default OFF (use legacy v1 path). static inline int hak_pool_v2_enabled(void) { @@ -35,6 +36,61 @@ static inline int hak_pool_v2_tls_fast_enabled(void) { return g; } +// Pool v1 flatten (hot path only) is experimental and opt-in. +static inline int hak_pool_v1_flatten_enabled(void) { + static int g = -1; + if (__builtin_expect(g == -1, 0)) { + const char* e = getenv("HAKMEM_POOL_V1_FLATTEN_ENABLED"); + g = (e && *e && *e != '0') ? 1 : 0; + } + return g; +} + +static inline int hak_pool_v1_flatten_stats_enabled(void) { + static int g = -1; + if (__builtin_expect(g == -1, 0)) { + const char* e = getenv("HAKMEM_POOL_V1_FLATTEN_STATS"); + g = (e && *e && *e != '0') ? 1 : 0; + } + return g; +} + +typedef struct PoolV1FlattenStats { + _Atomic uint64_t alloc_tls_hit; + _Atomic uint64_t alloc_fallback_v1; + _Atomic uint64_t free_tls_hit; + _Atomic uint64_t free_fallback_v1; + _Atomic uint64_t free_fb_page_null; + _Atomic uint64_t free_fb_not_mine; + _Atomic uint64_t free_fb_other; +} PoolV1FlattenStats; + +static PoolV1FlattenStats g_pool_v1_flat_stats = {0}; + +static inline void pool_v1_flat_stats_dump(void) { + if (!hak_pool_v1_flatten_stats_enabled()) return; + fprintf(stderr, + "[POOL_V1_FLAT] alloc_tls_hit=%llu alloc_fb=%llu free_tls_hit=%llu free_fb=%llu page_null=%llu not_mine=%llu other=%llu\n", + (unsigned long long)atomic_load_explicit(&g_pool_v1_flat_stats.alloc_tls_hit, + memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_pool_v1_flat_stats.alloc_fallback_v1, + memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_pool_v1_flat_stats.free_tls_hit, + memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_pool_v1_flat_stats.free_fallback_v1, + memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_pool_v1_flat_stats.free_fb_page_null, + memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_pool_v1_flat_stats.free_fb_not_mine, + memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_pool_v1_flat_stats.free_fb_other, + memory_order_relaxed)); +} + +__attribute__((destructor)) static void pool_v1_flatten_stats_destructor(void) { + pool_v1_flat_stats_dump(); +} + // 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. @@ -123,6 +179,13 @@ static inline void* hak_pool_try_alloc_v2_impl(size_t size, uintptr_t site_id) { return NULL; } + // Experimental PoolHotBox v2 (Hot path) — currently structure only. + if (__builtin_expect(pool_hotbox_v2_class_enabled(class_idx), 0)) { + void* p = pool_hotbox_v2_alloc((uint32_t)class_idx, size, site_id); + if (p) return p; + pool_hotbox_v2_record_alloc_fallback((uint32_t)class_idx); + } + if (__builtin_expect(size >= 33000 && size <= 41000, 0)) { HAKMEM_LOG("[Pool] ACCEPTED: class_idx=%d, proceeding with allocation\n", class_idx); } @@ -357,6 +420,14 @@ static inline void hak_pool_free_v2_impl(void* ptr, size_t size, uintptr_t site_ } int class_idx = mid_by_desc ? (int)d_desc->class_idx : hak_pool_get_class_index(size); if (class_idx < 0) return; + if (__builtin_expect(pool_hotbox_v2_class_enabled(class_idx), 0)) { + pool_hotbox_v2_record_free_call((uint32_t)class_idx); + PoolBlock* raw_block_for_v2 = (PoolBlock*)raw; + if (pool_hotbox_v2_free((uint32_t)class_idx, raw_block_for_v2)) { + return; + } + pool_hotbox_v2_record_free_fallback((uint32_t)class_idx); + } PoolBlock* block = (PoolBlock*)raw; uint64_t owner_tid = 0; if (d_desc) owner_tid = d_desc->owner_tid; @@ -768,6 +839,111 @@ static inline void hak_pool_free_v1_impl(void* ptr, size_t size, uintptr_t site_ mid_page_inuse_dec_and_maybe_dn(raw); } +// --- v1 flatten (opt-in) ---------------------------------------------------- + +static inline void* hak_pool_try_alloc_v1_flat(size_t size, uintptr_t site_id) { + if (!hak_pool_is_poolable(size)) return NULL; + int class_idx = hak_pool_get_class_index(size); + if (class_idx < 0) return NULL; + + PoolTLSRing* ring = &g_tls_bin[class_idx].ring; + if (g_tls_ring_enabled && ring->top > 0) { + PoolBlock* tlsb = ring->items[--ring->top]; + // Adopt shared pages to this thread so free can stay on the fast path. + mid_desc_adopt(tlsb, class_idx, (uint64_t)(uintptr_t)pthread_self()); + if (hak_pool_v1_flatten_stats_enabled()) { + atomic_fetch_add_explicit(&g_pool_v1_flat_stats.alloc_tls_hit, 1, memory_order_relaxed); + } + return hak_pool_block_to_user(tlsb, class_idx, site_id); + } + + if (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; + if (g_tls_bin[class_idx].lo_count) g_tls_bin[class_idx].lo_count--; + mid_desc_adopt(b, class_idx, (uint64_t)(uintptr_t)pthread_self()); + if (hak_pool_v1_flatten_stats_enabled()) { + atomic_fetch_add_explicit(&g_pool_v1_flat_stats.alloc_tls_hit, 1, memory_order_relaxed); + } + return hak_pool_block_to_user(b, class_idx, site_id); + } + + if (hak_pool_v1_flatten_stats_enabled()) { + atomic_fetch_add_explicit(&g_pool_v1_flat_stats.alloc_fallback_v1, 1, memory_order_relaxed); + } + return hak_pool_try_alloc_v1_impl(size, site_id); +} + +static inline void hak_pool_free_v1_flat(void* ptr, size_t size, uintptr_t site_id) { + if (!ptr) return; + if (!hak_pool_is_poolable(size)) return; + + void* raw = (char*)ptr - HEADER_SIZE; + MidPageDesc* d_desc = mid_desc_lookup(ptr); + if (!d_desc) { + if (hak_pool_v1_flatten_stats_enabled()) { + atomic_fetch_add_explicit(&g_pool_v1_flat_stats.free_fallback_v1, 1, memory_order_relaxed); + atomic_fetch_add_explicit(&g_pool_v1_flat_stats.free_fb_page_null, 1, memory_order_relaxed); + } + hak_pool_free_v1_impl(ptr, size, site_id); + return; + } + + int class_idx = (int)d_desc->class_idx; + if (class_idx < 0 || class_idx >= POOL_NUM_CLASSES) { + if (hak_pool_v1_flatten_stats_enabled()) { + atomic_fetch_add_explicit(&g_pool_v1_flat_stats.free_fallback_v1, 1, memory_order_relaxed); + atomic_fetch_add_explicit(&g_pool_v1_flat_stats.free_fb_other, 1, memory_order_relaxed); + } + hak_pool_free_v1_impl(ptr, size, site_id); + return; + } + + const uint64_t owner_tid = d_desc->owner_tid; + const uint64_t self_tid = (uint64_t)(uintptr_t)pthread_self(); + + if (g_pool.tls_free_enabled && owner_tid != 0 && owner_tid == self_tid) { + PoolBlock* block = (PoolBlock*)raw; + 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); + 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--; + 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); + } + set_nonempty_bit(class_idx, shard); + } + } + if (hak_pool_v1_flatten_stats_enabled()) { + atomic_fetch_add_explicit(&g_pool_v1_flat_stats.free_tls_hit, 1, memory_order_relaxed); + } + return; + } + + if (hak_pool_v1_flatten_stats_enabled()) { + atomic_fetch_add_explicit(&g_pool_v1_flat_stats.free_fallback_v1, 1, memory_order_relaxed); + atomic_fetch_add_explicit(&g_pool_v1_flat_stats.free_fb_not_mine, 1, memory_order_relaxed); + } + hak_pool_free_v1_impl(ptr, size, site_id); +} + 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; @@ -783,6 +959,9 @@ 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()) { + if (hak_pool_v1_flatten_enabled()) { + return hak_pool_try_alloc_v1_flat(size, site_id); + } return hak_pool_try_alloc_v1_impl(size, site_id); } return hak_pool_try_alloc_v2_impl(size, site_id); @@ -790,7 +969,11 @@ void* hak_pool_try_alloc(size_t size, uintptr_t 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); + if (hak_pool_v1_flatten_enabled()) { + hak_pool_free_v1_flat(ptr, size, site_id); + } else { + hak_pool_free_v1_impl(ptr, size, site_id); + } return; } hak_pool_free_v2_impl(ptr, size, site_id); @@ -798,6 +981,8 @@ void hak_pool_free(void* ptr, size_t size, uintptr_t site_id) { void hak_pool_free_fast(void* ptr, uintptr_t site_id) { if (!hak_pool_v2_route()) { + // fast path lacks size; keep existing v1 fast implementation even when + // flatten is enabled to avoid behavior drift. hak_pool_free_fast_v1_impl(ptr, site_id); return; } diff --git a/core/box/pool_mid_desc.inc.h b/core/box/pool_mid_desc.inc.h index 126df901..a260507a 100644 --- a/core/box/pool_mid_desc.inc.h +++ b/core/box/pool_mid_desc.inc.h @@ -38,11 +38,15 @@ static void mid_desc_init_once(void) { static void mid_desc_register(void* page, int class_idx, uint64_t owner_tid) { mid_desc_init_once(); - uint32_t h = mid_desc_hash(page); + void* canonical_page = (void*)((uintptr_t)page & ~((uintptr_t)POOL_PAGE_SIZE - 1)); + uint32_t h = mid_desc_hash(canonical_page); pthread_mutex_lock(&g_mid_desc_mu[h]); MidPageDesc* d = (MidPageDesc*)hkm_libc_malloc(sizeof(MidPageDesc)); // P0 Fix: Use libc malloc if (d) { - d->page = page; d->class_idx = (uint8_t)class_idx; d->owner_tid = owner_tid; d->next = g_mid_desc_head[h]; + d->page = canonical_page; + d->class_idx = (uint8_t)class_idx; + d->owner_tid = owner_tid; + d->next = g_mid_desc_head[h]; atomic_store(&d->in_use, 0); d->blocks_per_page = 0; // optional; not used for emptiness in P0 atomic_store(&d->pending_dn, 0); @@ -98,4 +102,3 @@ static inline void mid_page_inuse_dec_and_maybe_dn(void* raw) { } #endif // POOL_MID_DESC_INC_H - diff --git a/docs/analysis/MID_LARGE_CPU_HOTPATH_ANALYSIS.md b/docs/analysis/MID_LARGE_CPU_HOTPATH_ANALYSIS.md index 700ac7ac..8a91a7b0 100644 --- a/docs/analysis/MID_LARGE_CPU_HOTPATH_ANALYSIS.md +++ b/docs/analysis/MID_LARGE_CPU_HOTPATH_ANALYSIS.md @@ -44,3 +44,32 @@ - v1 (POOL_V2_ENABLED=0): **27.40M ops/s** - v2 (POOL_V2_ENABLED=1): **24.73M ops/s** - 所感: v2 の変更が回帰要因と判明。標準は v1 に戻し、スイッチ単位の A/B でどの改変が悪いかを切り分ける方針。 + +## Phase80: Pool v1 flatten 初回 A/B(C6-heavy) + +- スコープ: pool v1 の自スレッドホットパス(TLS ring/lo hit)を別 Box として直線化し、mid/smallmid(C6-heavy)での ops/s 向上を狙う。pool v2 は引き続き研究箱のまま触らない。 +- 実装: `core/hakmem_pool.c` に `hak_pool_try_alloc_v1_flat` / `hak_pool_free_v1_flat` を追加し、TLS ring/lo hit 時は最小限の分岐だけで pop/push する経路を用意。miss 時や cross-thread/slow は従来の `_v1_impl` にフォールバックする構造にし、ENV `HAKMEM_POOL_V1_FLATTEN_ENABLED`(デフォルト0)と `HAKMEM_POOL_V1_FLATTEN_STATS` で切替・統計を制御。 +- ベンチ条件: `./bench_mid_large_mt_hakmem 1 1000000 400 1`(Release)、`HAKMEM_BENCH_MIN_SIZE=257`, `MAX_SIZE=768`, `HAKMEM_TINY_HEAP_PROFILE=C7_SAFE`, `HAKMEM_TINY_C7_HOT=1`, `HAKMEM_TINY_HOTHEAP_V2=0`, `HAKMEM_SMALL_HEAP_V3_ENABLED=1`, `HAKMEM_SMALL_HEAP_V3_CLASSES=0x80`, `HAKMEM_POOL_V2_ENABLED=0`, `HAKMEM_POOL_V1_FLATTEN_STATS=1`。 +- A/B 結果: + - flatten OFF (`POOL_V1_FLATTEN_ENABLED=0`): Throughput ≈ **23.12M ops/s**、`[POOL_V1_FLAT] alloc_tls_hit=0 alloc_fb=0 free_tls_hit=0 free_fb=0`。 + - flatten ON (`POOL_V1_FLATTEN_ENABLED=1`): Throughput ≈ **25.50M ops/s**(約 +10%)、`alloc_tls_hit=499,870 alloc_fb=230 free_tls_hit=460,450 free_fb=39,649`。 +- 所感: + - Pool v1 の TLS fast path を分離・直線化するだけで、狙っていた +5〜10% の上限付近まで改善が出た。まだ free_fb がそこそこ残っており、page_of / 自スレ判定の精度を上げて free_fb を減らす余地がある。 + - デフォルト運用は安全側を維持するため `HAKMEM_POOL_V1_FLATTEN_ENABLED=0` のままとし、C6-heavy/mid ベンチや実験時にのみ flatten 経路を ON にする Box として扱う。 + +## Phase81: Pool v1 flatten Phase2(free_fb 理由分解) + +- 変更: flatten stats に free fallback の内訳を追加(page_null / not_mine / other)。free で mid_desc が取れない場合は page_null、owner 不一致や TLS 無効による fallback は not_mine、それ以外は other としてカウント。 +- ベンチ条件: `./bench_mid_large_mt_hakmem 1 1000000 400 1`(Release)、`HAKMEM_TINY_HEAP_PROFILE=LEGACY`, `HAKMEM_TINY_HOTHEAP_V2=0`, `HAKMEM_POOL_V2_ENABLED=0`, `HAKMEM_POOL_V1_FLATTEN_ENABLED=1`, `HAKMEM_POOL_V1_FLATTEN_STATS=1`。 +- A/B: + - flatten OFF: **23.68M ops/s**、stats すべて 0。 + - flatten ON : **25.90M ops/s**(約 +9.4%)、`alloc_tls_hit=499,870 alloc_fb=230 free_tls_hit=460,060 free_fb=40,039 page_null=40,039 not_mine=0 other=0`。 +- 所感: free fallback のほぼすべてが「mid_desc が取れず page=null」で発生している。owner mismatch はゼロ。今後は page_of/mid_desc 判定の精度を上げることで free_fb を減らす余地がある。 + +## Phase82: mid_desc マスク整合による free_fb 削減 + +- 変更: `mid_desc_register/lookup/adopt` が扱うページアドレスを `POOL_PAGE_SIZE` で正規化し、64KiB に未アラインな mmap でも mid_desc_lookup が一致するように修正。これにより false negative の page_null を減らす。 +- ベンチ(同条件、flatten ON, Release, tiny/pool v2 OFF, LEGACY tiny): + - flatten OFF: **23.68M ops/s**(参考値・前フェーズと同じ)。 + - flatten ON : **26.70M ops/s**(約 +13% vs OFF)、`alloc_tls_hit=499,871 alloc_fb=229 free_tls_hit=489,147 free_fb=10,952 page_null=3,476 not_mine=7,476 other=0`。 +- 所感: page_null が大幅減(≈40k→≈3.4k)。not_mine が顕在化したため、次は owner 判定/自スレ判定の精度を軽く見直す余地がある。デフォルトは引き続き flatten OFF(安全側)で、bench/実験時のみ ON。