pool v1 flatten: break down free fallback causes and normalize mid_desc keys

This commit is contained in:
Moe Charm (CI)
2025-12-09 19:34:54 +09:00
parent 8f18963ad5
commit e274d5f6a9
4 changed files with 474 additions and 6 deletions

View File

@ -1,5 +1,125 @@
## HAKMEM 状況メモ (2025-12-05 更新 / C7 Warm/TLS Bind 反映) ## HAKMEM 状況メモ (2025-12-05 更新 / C7 Warm/TLS Bind 反映)
### Phase80: mid/smallmid Pool v1 flattenC6-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/BC6-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 Phase2free_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/ctxstats、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_STATSC7-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 161024B 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 を ONENV 未指定で ENABLED=1, CLASSES=0x80 相当)としつつ、混乱や回帰時に備えて `SMALL_HEAP_V3_ENABLED=0` / クラスマスクでいつでも v1 に戻せるようにしている。***
### Phase63: C6 v2 A/Bbench専用マスクでの初回計測
- 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 161024B (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=0C6 依然 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=48v2 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/BCold 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=1baseline。短尺の 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 161024B 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/BC7-only 研究箱の現状)
- Mixed 161024Bws=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 OFFC7_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 161024B (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 161024B, 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 161024B, 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 (257768B メイン) のベースラインは **HAKMEM ≈2829M ops/s** に対し mimalloc ≈54M / system ≈15M。Tiny 161024B は ~4142M と比べ、mid/pool 側が大きく劣る。
- ホットスポット: perf では `hak_pool_try_alloc/free`, `memset`, `mid_desc_lookup` が主因。pf/sys は小さく、CPU 側命令数削減がボトルネック。
- 目標: mid/smallmid で +5〜10%2829M → 3032Mをまず達成すること。
- 方針: 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 OFFC7 SAFE v1 が本線。C6 v2 は bench/研究専用の opt-in。ベンチ未実施次フェーズで C6-heavy / Mixed A/B を取得予定)。
- C7 v2 の安定性・性能は維持C6 追加による挙動変化はなし)。
### Phase 36: TinyHotHeap v2 を「Hot Box」として再定義設計ドキュメント整備 ### Phase 36: TinyHotHeap v2 を「Hot Box」として再定義設計ドキュメント整備
- 状況: HotHeap v2 は Phase35 まで「v1 TinyHeap/C7 SAFE の上に乗るラッパ」で、Mixed では構造的に勝てない状態だったため、**いったん棚上げ** の扱いになっていた。 - 状況: HotHeap v2 は Phase35 まで「v1 TinyHeap/C7 SAFE の上に乗るラッパ」で、Mixed では構造的に勝てない状態だったため、**いったん棚上げ** の扱いになっていた。
- 方針転換: `docs/analysis/TINY_HEAP_V2_DESIGN.md` に Phase36 セクションを追加し、TinyHeap v2 自体を per-thread Hot BoxTinyHotHeapBox v2として再定義。Superslab/Tier/Remote/Stats/Learning はすべて外側の Cold Box に落とし、境界を - 方針転換: `docs/analysis/TINY_HEAP_V2_DESIGN.md` に Phase36 セクションを追加し、TinyHeap v2 自体を per-thread Hot BoxTinyHotHeapBox v2として再定義。Superslab/Tier/Remote/Stats/Learning はすべて外側の Cold Box に落とし、境界を
@ -13,6 +133,29 @@
- 実装ガイド: `docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md`(本フェーズで骨子を追加、実装担当 AI/開発者向けの指示書)。 - 実装ガイド: `docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md`(本フェーズで骨子を追加、実装担当 AI/開発者向けの指示書)。
- 詳細設計: `docs/analysis/TINY_HEAP_V2_DESIGN.md`Phase36+ セクションに A/B/C 案と Box 構造を集約)。 - 詳細設計: `docs/analysis/TINY_HEAP_V2_DESIGN.md`Phase36+ セクションに A/B/C 案と Box 構造を集約)。
### Phase 36+ (ChatGPT Pro フィードバック統合 / TinyHeap v2 ロードマップ再定義)
- 状況整理:
- Mixed 161024B: HAKMEM ≈41M ops/s / mimalloc ≈113M / system ≈92M → HAKMEM は mimalloc の ~36%、system の ~45%。
- mid/smallmid: HAKMEM ≈2829M / 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 は C5C7 をカバーしつつ、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 → C5C7 への段階的 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 ポリシー修正(スローパス多発の是正) ### Phase 37: TinyHotHeap v2 C7 current_page ポリシー修正(スローパス多発の是正)
- ベンチ結果Release, PROFILE=C7_SAFE: - ベンチ結果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 - 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 - 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+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 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 SAFEv1/v2のみ。
### Phase 20: C6 Hot front の箱追加C7 対称の直線パス) ### Phase 20: C6 Hot front の箱追加C7 対称の直線パス)
- 新規ドキュメント: `docs/analysis/C6_HOTBOX_DESIGN.md` を追加し、C6 を TinyHeap でホット化する箱の目的と境界を定義SAFE のみ、ULTRA なし。C6 TinyHeap は当面 bench/実験扱いと明記。 - 新規ドキュメント: `docs/analysis/C6_HOTBOX_DESIGN.md` を追加し、C6 を TinyHeap でホット化する箱の目的と境界を定義SAFE のみ、ULTRA なし。C6 TinyHeap は当面 bench/実験扱いと明記。
@ -600,6 +744,32 @@
- v2 ON: **24.73M ops/s** - v2 ON: **24.73M ops/s**
- 所感: 回帰を避けるため標準は v1 を維持しつつ、どの変更が悪かったかをスイッチ単位でA/Bできるようにした。次は各スイッチON/OFFでの差分取り、必要なら v2 を研究箱のまま凍結。 - 所感: 回帰を避けるため標準は v1 を維持しつつ、どの変更が悪かったかをスイッチ単位でA/Bできるようにした。次は各スイッチON/OFFでの差分取り、必要なら v2 を研究箱のまま凍結。
### Phase69: PoolHotBox v2 初回 A/BCold 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-Fastv2 内で v1 fallback しないに変更。free_fb_v1 は front 側のみでカウントする前提に整理。
- ビルド: `make bench_random_mixed_hakmem -j4` 成功(警告のみ)。
- 次アクション: C6-heavy で v2 OFF/ON を再A/BPOOL_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: 現行デフォルトと研究箱の位置づけ ### 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。 - 標準構成: `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。 - HugePage/ヘッダ light/off: first-touch/bench 専用。デフォルトはすべて OFF。
- 次フェーズの方向性v3 テーマの入口): - 次フェーズの方向性v3 テーマの入口):
- TinyHeap v2 を C5C7 統合 HotHeap として再設計(現行 v2 とは別ライン)。 - TinyHeap v2 を C5C7 統合 HotHeap として再設計(現行 v2 とは別ライン)。
- first-touch/page-fault 系の本格対応HugePage/ヘッダ light の昇格可否検証)。 - first-touch/page-fault 系の本格対応HugePage/ヘッダ light の昇格可否検証)。
- mid/smallmid の pool/フロント最適化、または mid/large route のさらなるフラット化。 - mid/smallmid の pool/フロント最適化、または mid/large route のさらなるフラット化。
### Phase65-c7-v3 short/long A/BC7-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 sanity161024B, 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 は依然デフォルト OFFHAKMEM_SMALL_HEAP_V3_ENABLED=0。C7-only の研究/bench では `ENABLED=1, CLASSES=0x80` で opt-in 可能。
### Phase73: Tiny front v3 スナップショット化Mixed 161024B, 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 161024B (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 161024B, 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=2446HEAP_STATS は TinyHeap v1 経路外のため未出力)
- 次: size→class→route 前段のさらなるフラット化Phase2-B 相当)で 5〜10% を狙う。
### Phase75: Tiny front v3 route fast pathLUT→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 161024B, 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 161024B, 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 Boxv2 など)はこの 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` 件数に近づいていることを確認する(性能よりもまず安定性・境界の分離を優先)。

View File

@ -3,6 +3,7 @@
#define POOL_API_INC_H #define POOL_API_INC_H
#include "pagefault_telemetry_box.h" // Box PageFaultTelemetry (PF_BUCKET_MID) #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). // Pool v2 is experimental. Default OFF (use legacy v1 path).
static inline int hak_pool_v2_enabled(void) { static inline int hak_pool_v2_enabled(void) {
@ -35,6 +36,61 @@ static inline int hak_pool_v2_tls_fast_enabled(void) {
return g; 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 // 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 // a user pointer. All sampling/stat updates remain here so the callers stay
// small. // small.
@ -123,6 +179,13 @@ static inline void* hak_pool_try_alloc_v2_impl(size_t size, uintptr_t site_id) {
return NULL; 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)) { if (__builtin_expect(size >= 33000 && size <= 41000, 0)) {
HAKMEM_LOG("[Pool] ACCEPTED: class_idx=%d, proceeding with allocation\n", class_idx); 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); int class_idx = mid_by_desc ? (int)d_desc->class_idx : hak_pool_get_class_index(size);
if (class_idx < 0) return; 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; PoolBlock* block = (PoolBlock*)raw;
uint64_t owner_tid = 0; uint64_t owner_tid = 0;
if (d_desc) owner_tid = d_desc->owner_tid; 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); 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) { 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; } } 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; 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) { void* hak_pool_try_alloc(size_t size, uintptr_t site_id) {
if (!hak_pool_v2_route()) { 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_v1_impl(size, site_id);
} }
return hak_pool_try_alloc_v2_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) { void hak_pool_free(void* ptr, size_t size, uintptr_t site_id) {
if (!hak_pool_v2_route()) { 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; return;
} }
hak_pool_free_v2_impl(ptr, size, site_id); 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) { void hak_pool_free_fast(void* ptr, uintptr_t site_id) {
if (!hak_pool_v2_route()) { 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); hak_pool_free_fast_v1_impl(ptr, site_id);
return; return;
} }

View File

@ -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) { static void mid_desc_register(void* page, int class_idx, uint64_t owner_tid) {
mid_desc_init_once(); 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]); pthread_mutex_lock(&g_mid_desc_mu[h]);
MidPageDesc* d = (MidPageDesc*)hkm_libc_malloc(sizeof(MidPageDesc)); // P0 Fix: Use libc malloc MidPageDesc* d = (MidPageDesc*)hkm_libc_malloc(sizeof(MidPageDesc)); // P0 Fix: Use libc malloc
if (d) { 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); atomic_store(&d->in_use, 0);
d->blocks_per_page = 0; // optional; not used for emptiness in P0 d->blocks_per_page = 0; // optional; not used for emptiness in P0
atomic_store(&d->pending_dn, 0); 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 #endif // POOL_MID_DESC_INC_H

View File

@ -44,3 +44,32 @@
- v1 (POOL_V2_ENABLED=0): **27.40M ops/s** - v1 (POOL_V2_ENABLED=0): **27.40M ops/s**
- v2 (POOL_V2_ENABLED=1): **24.73M ops/s** - v2 (POOL_V2_ENABLED=1): **24.73M ops/s**
- 所感: v2 の変更が回帰要因と判明。標準は v1 に戻し、スイッチ単位の A/B でどの改変が悪いかを切り分ける方針。 - 所感: v2 の変更が回帰要因と判明。標準は v1 に戻し、スイッチ単位の A/B でどの改変が悪いかを切り分ける方針。
## Phase80: Pool v1 flatten 初回 A/BC6-heavy
- スコープ: pool v1 の自スレッドホットパスTLS ring/lo hitを別 Box として直線化し、mid/smallmidC6-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 Phase2free_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。