Guard madvise ENOMEM and stabilize pool/tiny front v3

This commit is contained in:
Moe Charm (CI)
2025-12-09 21:50:15 +09:00
parent e274d5f6a9
commit a905e0ffdd
45 changed files with 3154 additions and 242 deletions

View File

@ -9,6 +9,23 @@
- `CURRENT_TASK.md` (Phase36 状況メモ)
- ステータス: Phase36 で C7-only の Hot 部current_page+freelistを v2 で自前管理する暫定実装を投入済み。Superslab/Tier/Stats は v1 に委譲する lease/refill/retire 境界で A/B できる状態。
### Phase65/66: C7-only フェーズ完了サマリ
- C7-only / Mixed 161024B の長尺プロファイルws=400, iters=1M, PROFILE=C7_SAFEで、v2 ON/OFF いずれも `HEAP_STATS[7].slow≈1`・refill≈1 に収束し、性能は v1 比で ±5% 以内(むしろ数%プラス)。
- 短尺ベンチ20k/ws=64で見える refill≒50 はウォームアップ由来と判断され、本命プロファイルに影響しないことを確認。
- 運用上の扱い:
- デフォルト構成では `HAKMEM_TINY_HOTHEAP_V2=0`C7 SAFE v1を維持。
- C7-only の bench/pro 用プロファイルでは `HAKMEM_TINY_HOTHEAP_V2=1 HAKMEM_TINY_HOTHEAP_CLASSES=0x80` を opt-in 推奨とし、v2 を本命候補の Hot Box として使う。
- C6 v2 は構造こそ通電したものの perf 未達のため、引き続き研究箱classes=0x40/0xC0 を明示したときだけ有効)に留める。
### Phase60 速報C7-only 空ページ保持ポリシー)
- 追加したもの:
- `max_partial_pages`C7 デフォルト 2`partial_count` を Hot Box に追加し、free で `used==0` の page は一度 partial へ温存。上限超のみ retire を呼ぶ。
- partial push/pop/peak と retire_v2 を v2 stats に追加(`HAKMEM_TINY_HOTHEAP_V2_STATS=1` で確認)。
- 初期ベンチ:
- C7-only v2 ON で slow_prepare は `alloc_lease` と一致≈48 回。live ブロックが多く page が空にならないため partial/retire は未発火。
- Mixed でも v2 ON は +2〜3% だが slow≈refill(42)。空ページを作れる workload またはページ容量/lease の見直しが次課題。
- 運用: v2 は引き続き研究箱(デフォルト OFF。C7-only で安定後に Mixed への適用を検討する。
---
## 0. ゴールと非ゴール
@ -396,3 +413,181 @@ Step 7-6: 再ベンチと判定基準
次フェーズで C6/C5 への拡張や C7 超ホットレーンB案を検討できる。
- 満たせない場合は v2 を引き続き **デフォルト OFF / 研究用箱** としたまま、
current_page / retire ポリシーの再修正に戻る。
---
## 8. Phase58: TinyColdIface 導入後の課題整理と次の実装指示
現状ステータスPhase58 時点)
------------------------------
- `core/box/tiny_cold_iface_v1.h``TinyColdIface` が導入され、v1 TinyHeap の
- `tiny_heap_prepare_page()``refill_page(cold_ctx, class_idx)`
- `tiny_heap_page_becomes_empty()``retire_page(cold_ctx, class_idx, page)`
としてラップ済み。Hot Box 側からはこの IF だけを見れば Superslab/Tier/Stats に触れられる状態になっている。
- C7 v2 の Cold 境界は次のように接続済み:
- `tiny_hotheap_v2_refill_slow()`:
- `TinyColdIface cold = tiny_cold_iface_v1();`
- `tiny_heap_ctx_t* cold_ctx = tiny_heap_ctx_for_thread();`
- `ipage = cold.refill_page(cold_ctx, class_idx)` で v1 から `tiny_heap_page_t` を 1 枚借りる。
- v2 側の `tiny_hotheap_page_v2` ノードを確保し、`base/capacity/meta/ss/slab_idx` をコピー。
- freelist が無い場合は v2 が `used=0` にリセットし、`tiny_hotheap_v2_build_freelist()` で carve する。
- freelist がある場合は `lease_page->meta->freelist` を v2 freelist で更新。
- `hc->current_page` に必ず freelist 付きページが入るよう Fail-Fast チェックを追加。
- `tiny_hotheap_v2_page_retire_slow()`:
- v2 の `current_page` / `partial_pages` / `full_pages` から対象ページを unlink。
- `cold.retire_page(cold_ctx, class_idx, page->lease_page)` を呼んで v1 側へ返却。
- `storage_page` 以外は `free(page)`、storage は `tiny_hotheap_v2_page_reset()` で初期化。
- TLS 初期化 (`tiny_hotheap_v2_tls_get()`) では、`tiny_hotheap_ctx_v2``calloc` し、各クラスの `storage_page` を reset、`stride``tiny_stride_for_class(i)` で事前設定するようにした。
- v2 Stats:
- `g_tiny_hotheap_v2_c7_*``alloc_calls/route_hits/alloc_fast/alloc_refill/alloc_fallback_v1/free_*`
`cold_refill_fail` / `cold_retire_calls` を追加し、`HAKMEM_TINY_HOTHEAP_V2_STATS=1` で destructor ダンプ。
残っている問題(なぜ v2 ON で SEGV するか)
-----------------------------------------
- `tiny_hotheap_v2_try_pop()` は依然として v1 TinyHeap の page API に強く依存している:
- `candidate->lease_page``ipage` として取り出し、
- `v1hcls->current_page = ipage;`
- `tiny_heap_page_pop(v1hcls, 7, ipage);`
- 必要なら `tiny_heap_page_mark_full(v1hcls, ipage);`
を呼んだうえで、
- `candidate->freelist = ipage->free_list;`
- `candidate->used = ipage->used;`
と v2 側 state にもコピーしている。
- これは Box Theory 的には
> Hot Box (v2) が Cold Box (v1 TinyHeap) の内部状態 (`free_list/used`) を直接操作している
ことになり、境界が二重になっている。
- refill で v2 が独自に carve した freelist と、v1 の `tiny_heap_page_pop()` が更新する `free_list/used/meta` がずれていくと、
- 無効な `ipage->base` / `capacity` / `meta` を deref したり
- v2 の `candidate->freelist` が壊れた状態で次の pop に進んでしまう
可能性がある。現状の SIGSEGV はこのあたりの不整合に起因していると考えられる。
Phase58 でやるべきこと(実装指示)
---------------------------------
### 8-1. `tiny_hotheap_v2_try_pop()` から v1 依存を外し、v2 freelist だけで完結させる
目的:
- Hot Box v2 は「自分の freelist を自分で pop する箱」とし、Cold Box v1 には refill/retire でしか触れないようにする。
作業指示:
- `tiny_hotheap_v2_try_pop()` を次の方針で書き換える:
- 引数から `tiny_heap_class_t* v1hcls` を削除し、`TinyColdIface` / v1 TinyHeap の型に依存しない形にする。
- `candidate` から直接 freelist を pop:
```c
static inline void* tiny_hotheap_v2_try_pop(tiny_hotheap_page_v2* candidate,
TinyHeapClassStats* stats,
int stats_on) {
if (!candidate || !candidate->freelist || candidate->used >= candidate->capacity) {
return NULL;
}
void* user = candidate->freelist;
candidate->freelist = *(void**)user;
candidate->used++;
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);
}
return tiny_region_id_write_header(user, 7);
}
```
- v1 TinyHeap (`tiny_heap_page_pop` / `tiny_heap_page_mark_full` / `v1hcls->current_page` など) の呼び出しは完全に削除する。
- `candidate->lease_page` は **Cold 側に返すための token** として保持するだけにし、Hot path では一切触らない。
### 8-2. Cold 境界 (`refill_page` / `retire_page`) を「イベント 2 箇所だけ」に限定する
目的:
- Hot Box v2 と Cold Box v1 の境界を「page を借りる」「page を返す」の 2 箇所だけにし、meta/used/freelist の二重管理をやめる。
作業指示:
- `tiny_hotheap_v2_refill_slow()`:
- `TinyColdIface.refill_page()` から返ってきた `tiny_heap_page_t* ipage` については、
- `base` / `capacity` / `slab_idx` / `meta` / `ss` など **geometry 情報だけ** を読む。
- `ipage->free_list` / `ipage->used` は Hot path では信頼しないv2 が自前で carve した freelist を使う)。
- v2 側の `tiny_hotheap_page_v2` に freelist を構築したら、以降の alloc/free は v2 freelist だけで完結させる。
- どうしても meta に初期値を書き戻す必要がある場合は、Cold Stats Box 経由の更新に寄せる(直接 meta->used に触らない方向を優先)。
- `tiny_hotheap_v2_page_retire_slow()`:
- `page->used == 0` かつ v2 内のリストから unlink した段階でのみ `TinyColdIface.retire_page()` を呼ぶ。
- 返却後は `page->lease_page` を含めて v2 側 state を破棄または reset し、同じ token を Hot path から再利用しない。
### 8-3. 安全側のガードと Stats での確認ポイント
目的:
- v2 を再度 ON にした際、segv や Cold IF への過剰依存がないかをすぐに検知できるようにする。
作業指示:
- `tiny_hotheap_v2_alloc()` / `tiny_hotheap_v2_free()` の入口で:
- `class_idx != 7` は即 `NULL` or fallbackC7-only 実験箱のまま)。
- `v2ctx == NULL` / `hc == NULL` / `hc->stride == 0` などは Fail-Fastabortで早期検出。
- `tiny_hotheap_v2_try_pop()`:
- `candidate->capacity == 0` や `candidate->used > candidate->capacity` の場合は即 `NULL` を返す(デバッグビルドでは 1 回だけログ)。
- Stats:
- C7-only ベンチ (`ws=64, iters=20k, PROFILE=C7_SAFE`) で:
- v2 OFF: 既存どおり `HEAP_STATS[7] fast≈11015 slow≈1`。
- v2 ON:
- `alloc_fast` が 1 回目の page refill 後に `route_hits` と同程度まで増えていくこと。
- `cold_refill_fail` が常に 0 付近であること。
- `alloc_fallback_v1` / `free_fallback_v1` が 0〜ごく少数に収まっていること。
備考
----
- Phase58 の目的は **「TinyColdIface を導入したうえで、v2 Hot Box と v1 TinyHeap の境界を 2 箇所 (refill/retire) に絞ること」** であり、
この段階では性能よりも安定性segv しないこと)と Box 境界の明確化を優先する。
- C7-only で v2 が v1 と同等レベルで安定したら、次フェーズで C6/C5 への拡張や perf チューニング(分岐削減・命令数削減)に進む。
---
## 9. Phase60/61: C7-only フェーズ完了と次のターゲット (C6 拡張) メモ
現状まとめC7 v2 フェーズの着地)
---------------------------------
- C7-only:
- v2 OFF (C7 SAFE v1) ≈ 42M ops/s / slow≈1。
- v2 ON (C7-only, classes=0x80) ≈ 50M ops/s / slow≈40〜50 → 空ページ保持ポリシー導入前の状態。
- 空ページ保持ポリシー導入 (`max_partial_pages` / `partial_count` 追加) 後も、C7-only ではページが実質 full のまま回るため partial/retire はほぼ発火せず、slow は主に refill 回数として現れる。
- Mixed 161024B:
- 短尺 (ws=256, iters=20k, PROFILE=C7_SAFE):
- v2 OFF: ≈43.3M ops/s, HEAP_STATS[7] fast=5691 slow=1。
- v2 ON: ≈44.6M ops/s約 +3%, HEAP_STATS[7] fast=5691 slow=1, fail/fallback=0。
- 長尺 (ws=400, iters=1M, PROFILE=C7_SAFE):
- v2 OFF: ≈41.6M ops/s, HEAP_STATS[7] fast≈283k slow=1。
- v2 ON: ≈42.4M ops/s約 +2%, HEAP_STATS[7] fast≈283k slow=1, fail/fallback=0。
- 境界:
- Cold IF (`TinyColdIface`) 経由の refill/retire は安定しており、`cold_refill_fail` / fallback_v1 は 0 に張り付き。
- C7 v2 の Hot パスは `tiny_hotheap_page_v2.freelist` のみで完結し、v1 TinyHeap の page/pop は参照しない構造になった。
結論C7 フェーズ)
-------------------
- C7 v2 Hot Box は:
- C7-only / Mixed / 短尺・長尺のいずれでも v1 C7 SAFE を上回るスループットを達成。
- slow_prepare は v2 OFF/ON ともに 1長尺またはワークロード由来の refill 回数程度に収まり、Cold IF の異常ではない。
- fail/fallback=0 で、v2→v1 フォールバックに頼らずに Hot Box 単体で完結できている。
- 運用上の位置づけ:
- 標準デフォルト: 引き続き `HAKMEM_TINY_HOTHEAP_V2=0`(どの環境でも即 v1 C7 SAFE に戻せるようにする)。
- bench/研究プロファイル: C7 クラスに限り `HAKMEM_TINY_HOTHEAP_V2=1` / `HAKMEM_TINY_HOTHEAP_CLASSES=0x80` を推奨設定とし、C7 v2 を本命候補として扱う。
次のターゲット候補 (C6/C5 拡張の入り口)
--------------------------------------
- 目的:
- TinyHotHeap v2 の設計ゴールどおり、C7-only から C6/C5 へ段階的に適用範囲を広げて、Mixed 161024B 全体の性能を押し上げる。
- Gate/Policy 側の前提:
- PolicySnapshot / Route LUT に `tiny_heap_version[class_idx]` または `HAKMEM_TINY_HOTHEAP_CLASSES` のマスクを持ち、
- C7: まず v2 を有効(研究/bench向け
- C6: 将来的に `classes |= 0x40` で C6 を v2 に昇格させる余地を残す。
- C5: 同様に `classes |= 0x20` で C5 を v2 に載せる最終フェーズ。
- C6 拡張時に検討すべきポイント(設計メモのみ、実装は別フェーズ):
- ワークロード分布: Mixed 161024B で C6 がどの程度トラフィックを持つか(既存 FRONT_CLASS stats を参考にする)。
- 以前の Tiny C6 v1 実験では Mixed 回帰があったため、C6 v2 では:
- Route/Gate で C6 の Hot path を C7 と同じ 1 LUT + 1 switch に揃える。
- Unified Cache / WarmPool / Superslab へのアクセス回数が増えないようにするpure Hot Box 化を徹底)。
- rollout 戦略:
1. C6-only bench プロファイルで C6 v2 を ON にし、C6-heavy なベンチで C6 v1 と A/B少なくとも回帰なし
2. Mixed 161024B で C7 v2 + C6 v2 構成と C7 v2 のみ構成を比較し、±5% 以内 or 微プラスであれば「C6 v2 を本命候補」とする。
3. いずれも満たせない場合は、C6 v2 を C7 v2 と同様に研究箱ENV で個別に OFF 可能)として維持し、本線は C7 v2 + C6 v1 のままにする。
メモ:
- 本ガイドは C7 フェーズまでの実装指針とログを含んでおり、C6/C5 拡張は「同じ TinyHotHeapBox v2 の枠内で class を増やす」作業として扱う。
- C6/C5 拡張の具体的な API 変更や stats 追加は、今後の Phase (例: Phase62 以降) で別途「C6 v2 指示書」として追記する想定。***