Files
hakmem/docs/analysis/SMALLOBJECT_HOTBOX_V3_DESIGN.md
Moe Charm (CI) acc64f2438 Phase ML1: Pool v1 memset 89.73% overhead 軽量化 (+15.34% improvement)
## Summary
- ChatGPT により bench_profile.h の setenv segfault を修正(RTLD_NEXT 経由に切り替え)
- core/box/pool_zero_mode_box.h 新設:ENV キャッシュ経由で ZERO_MODE を統一管理
- core/hakmem_pool.c で zero mode に応じた memset 制御(FULL/header/off)
- A/B テスト結果:ZERO_MODE=header で +15.34% improvement(1M iterations, C6-heavy)

## Files Modified
- core/box/pool_api.inc.h: pool_zero_mode_box.h include
- core/bench_profile.h: glibc setenv → malloc+putenv(segfault 回避)
- core/hakmem_pool.c: zero mode 参照・制御ロジック
- core/box/pool_zero_mode_box.h (新設): enum/getter
- CURRENT_TASK.md: Phase ML1 結果記載

## Test Results
| Iterations | ZERO_MODE=full | ZERO_MODE=header | Improvement |
|-----------|----------------|-----------------|------------|
| 10K       | 3.06 M ops/s   | 3.17 M ops/s    | +3.65%     |
| 1M        | 23.71 M ops/s  | 27.34 M ops/s   | **+15.34%** |

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2025-12-10 09:08:18 +09:00

286 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

SmallObject HotBox v3 Design (Tiny + mid/smallmid 統合案)
========================================================
目的と背景
----------
- 現状の性能スナップショット:
- Mixed 161024B (1t, ws=400, iters=1M, PROFILE=C7_SAFE, Tiny v2 OFF):
- HAKMEM ≈ 4050M ops/s
- mimalloc ≈ 110120M ops/s
- system ≈ 90M ops/s
- mid/smallmid (bench_mid_large_mt, 1t, ws=400, iters=1M):
- HAKMEM ≈ 2829M ops/s
- mimalloc ≈ 54M ops/s
- system ≈ 15M ops/s
- これまでの Tiny/Pool v2 の試行から見えたこと:
- C7 v2 (TinyHotHeap v2) は C7-only / Mixed 長尺では v1 C7_SAFE と同等以上まで到達したが、C6/C5 への水平展開や pool v2 は現フェーズでは perf 未達で凍結。
- pool v2 は page_of O(1) 化Cold IF=v1 pool/Superslab まで実装したが、C6-heavy 長尺で極端な遅さが発生し、現行設計のまま押し上げるのは難しい。
- 結論:
- mimalloc に 7〜8割で迫るには、「Tiny (16〜1KiB) と mid/smallmid の両方」を一体の SmallObject HotBox として設計し直す必要がある。
- Superslab/Segment/Tier/Guard/Remote といった Cold/Safety 層は Box として残しつつ、SmallObject 側の Hot Box を 1 枚に集約する方向で v3 を設計する。
進捗サマリ (Phase A/B 通電)
---------------------------
- ENV: `HAKMEM_SMALL_HEAP_V3_ENABLED` / `HAKMEM_SMALL_HEAP_V3_CLASSES` を追加(デフォルトは C7-only ON: ENABLED=1, CLASSES=0x80 相当)。
- 型/IF: `core/box/smallobject_hotbox_v3_box.h` に so_page/class/ctx と stats を定義。TLS 初期化でクラス別 stride/max_partial をセット。
- Cold IF: `smallobject_cold_iface_v1.h` で C7 専用の v1 Tiny ラッパを実装。refill で tiny_heap_prepare_page(7) を借り、retire で tiny_heap_page_becomes_empty に返す。
- Hot: `core/smallobject_hotbox_v3.c` で so_alloc/so_free を実装current/partial freelist を v3 で管理、refill 失敗は v1 fallback。ページ内 freelist carve は v3 側で実施。
- Route: `tiny_route_env_box.h``TINY_ROUTE_SMALL_HEAP_V3` を追加。クラスビットが立っているときだけ route snapshot で v3 に振り分け。
- Front: malloc/free で v3 route を試し、失敗時は v2/v1/legacy に落とす直線パス。デフォルトは OFF なので挙動は従来通り。
### Phase S1: C6 v3 研究箱C7 を壊さずにベンチ限定で解禁)
- Gate: `HAKMEM_SMALL_HEAP_V3_ENABLED`/`CLASSES` の bit7=C7デフォルト ON=0x80、bit6=C6research-only、デフォルト OFF。C6 を叩くときは `HAKMEM_TINY_C6_HOT=1` を併用して tiny front を確実に通す。
- Cold IF: `smallobject_cold_iface_v1.h` を C6 にも適用し、`tiny_heap_prepare_page`/`page_becomes_empty` を C7 と同じ形で使う。v3 stats に `page_of_fail` を追加し、free 側の page_of ミスを計測。
- Bench (Release, Tiny/Pool v2 OFF, ws=400, iters=1M):
- C6-heavy A/B: `MIN_SIZE=257 MAX_SIZE=768``CLASSES=0x80`C6 v1**47.71M ops/s**`CLASSES=0x40`C6 v3, stats ON**36.77M ops/s**cls6 `route_hits=266,930 alloc_refill=5 fb_v1=0 page_of_fail=0`。v3 は約 -23%。
- Mixed 161024B: `CLASSES=0x80`C7-only**47.45M ops/s**`CLASSES=0xC0`C6+C7 v3, stats ON**44.45M ops/s**cls6 `route_hits=137,307 alloc_refill=1 fb_v1=0 page_of_fail=0` / cls7 `alloc_refill=2,446`)。約 -6%。
- 運用方針: 標準プロファイルは `HAKMEM_SMALL_HEAP_V3_CLASSES=0x80`C7-only v3に確定。C6 v3 は bench/研究のみ明示 opt-in とし、C6-heavy/Mixed の本線には乗せない。性能が盛り返すまで研究箱据え置き。
- C6-heavy を v1 固定で走らせる推奨プリセット(研究と混同しないための明示例):
```
HAKMEM_BENCH_MIN_SIZE=257
HAKMEM_BENCH_MAX_SIZE=768
HAKMEM_TINY_HEAP_PROFILE=C7_SAFE
HAKMEM_TINY_C6_HOT=1
HAKMEM_SMALL_HEAP_V3_ENABLED=1
HAKMEM_SMALL_HEAP_V3_CLASSES=0x80 # C7-only v3
```
設計ゴール (SmallObjectHotBox v3)
---------------------------------
- 対象サイズ帯:
- 16〜約 2KiB 程度の「SmallObject」領域既存 Tiny C0〜C7 + mid/smallmid の一部)。
- それより大きいサイズは現行 mid/large ルートpool v1 / direct mmapを継続使用。
- Box 構造:
- Hot Box: `SmallObjectHotBox` (per-thread)
- 責務: size→class→page→block のみを扱う。ページ内 freelist pop/push、current/partial 管理。
- Tiny v2 / pool v2 で学んだ「page_of O(1)、current/partial/retire ポリシー」を統合。
- Cold Box: Superslab/Segment/Tier/Guard/Remote
- 責務: Superslab/Segment の割当て・解放、Tier/HOT/DRAINING/FREE 管理、Remote Queue、Guard/Budget。
- Hot からは refill/retire の 2 箇所でのみ触れる。
- Policy Box: `SmallObjectPolicySnapshot`
- 責務: クラスごとの有効/無効、max_partial_pages、warm_cap などのポリシーを `_Atomic` スナップショットで保持。
- Hot は snapshot を読むだけ。Learner/ENV は snapshot の更新のみ。
- Stats Box / Learning Box:
- 責務: page/event 単位の delta を受け取り、Cold 側で集計・観測・学習を行う。
- Hot は「page refill/retire 時」「alloc/free のカウンタ更新」以外では触らない。
- 境界:
- Hot → Cold は 2 箇所に集約:
- `so_refill_page(cold_ctx, class_idx)` … SmallObjectHotBox が page を 1 枚借りる。
- `so_page_retire(cold_ctx, class_idx, page)` … empty page を Cold 側に返却。
- 現行 TinyColdIface / PoolColdIface の経験を活かし、SmallObject 用 Cold IF を 1 枚設計する。
データ構造案
------------
### Page メタ (`so_page_t`)
```c
typedef struct so_page_t {
void* freelist; // ページ内 block 単位の LIFO freelist
uint32_t used; // 使用中 block 数
uint32_t capacity; // ページ内 block 総数
uint16_t class_idx; // SmallObject クラス ID
uint16_t flags; // HOT/PARTIAL/FULL などの軽量フラグ
uint32_t block_size; // 1 block のバイト数
void* base; // ページ base アドレス
void* slab_ref; // Superslab/Segment 側 token (Cold Box 用)
struct so_page_t* next;
} so_page_t;
```
ポイント:
- Tiny v2 / pool v2 のページ構造を統一し、「SmallObject 全体で同じ page 型」を使う。
- page_of は `POOL_PAGE_SIZE` や Superslab サイズに合わせた mask + header で O(1) を前提とするpool v2 で得た知見)。
### クラス状態 (`so_class_t`)
```c
typedef struct so_class_t {
so_page_t* current; // ホットページ
so_page_t* partial; // 空きありページのリスト
uint16_t max_partial; // partial に保持する上限枚数
uint16_t partial_count; // 現在の partial 枚数
uint32_t block_size; // クラスの block サイズ
} so_class_t;
```
- Tiny C7 Safe / TinyHotHeap v2 / pool v2 の current/partial/retire ポリシーを統合。
- full リストは v3 初期段階では不要(必要になったら追加)。
### TLS コンテキスト (`so_ctx_t`)
```c
typedef struct so_ctx_t {
so_class_t cls[SMALLOBJECT_NUM_CLASSES];
} so_ctx_t;
```
- TLS (`__thread`) で per-thread の SmallObject コンテキストを保持。
- 初期化時にクラスごとの `block_size` / `max_partial` / ポリシー値をセットする。
Cold IF (SmallObjectColdIface) のイメージ
----------------------------------------
```c
typedef struct SmallObjectColdIface {
so_page_t* (*refill_page)(void* cold_ctx, uint32_t class_idx);
void (*retire_page)(void* cold_ctx, uint32_t class_idx, so_page_t* page);
} SmallObjectColdIface;
```
- `refill_page`:
- Cold Box が Superslab/Segment から SmallObject 用ページを 1 枚切り出し、
- `base` / `block_size` / `capacity` / `slab_ref` を設定した `so_page_t` を返す。
- v3 では `so_page_t` 自体を Cold 側で確保する案と、Hot 側 node を再利用する案のどちらかを選べるようにしておく。
- `retire_page`:
- `used==0` のページを Cold Box に返却し、Tier/Guard/Remote の扱いは Cold 側に委譲。
- 現行の TinyColdIface / pool Cold IF をラップする形で、SmallObject 用 Cold IF を段階的に導入する。
Hot パス設計alloc/free
-------------------------
### alloc (Hot パス)
```c
void* so_alloc_fast(so_ctx_t* ctx, uint32_t ci) {
so_class_t* hc = &ctx->cls[ci];
so_page_t* p = hc->current;
if (likely(p && p->freelist && p->used < p->capacity)) {
void* blk = p->freelist;
p->freelist = *(void**)blk;
p->used++;
return blk;
}
if (hc->partial) {
p = hc->partial;
hc->partial = p->next;
p->next = NULL;
hc->current = p;
if (p->freelist && p->used < p->capacity) {
void* blk = p->freelist;
p->freelist = *(void**)blk;
p->used++;
return blk;
}
}
return NULL; // slow_refill へ
}
```
- Slow パス (`so_alloc_refill_slow`) では:
- `SmallObjectColdIface.refill_page()` で Cold Box からページを 1 枚借りる。
- `so_page_t` に geometry を設定し、ページ内 freelist を Hot Box 側で carve。
- `hc->current` にセットしてから `so_alloc_fast` で pop。
### free (Hot パス)
```c
void so_free_fast(so_ctx_t* ctx, uint32_t ci, void* ptr) {
so_class_t* hc = &ctx->cls[ci];
so_page_t* p = so_page_of(ptr); // O(1) page_of
*(void**)ptr = p->freelist;
p->freelist = ptr;
p->used--;
if (p->used == 0) {
if (hc->partial_count < hc->max_partial) {
p->next = hc->partial;
hc->partial = p;
hc->partial_count++;
} else {
so_page_retire_slow(ctx, ci, p); // Cold IF 経由
}
if (hc->current == p) hc->current = NULL;
} else {
if (!hc->current) hc->current = p;
}
}
```
- Tiny v2 / pool v2 で使った「空ページ温存 or retire」のポリシーを、クラス別 `max_partial` で制御する。
- page_of は pool v2 と同様に O(1) で実装し、Fail-Fast ではなく統計+前段 fallback で診断できるようにする。
Front/Gate/Route の統合方針
--------------------------
- size→class→route の LUT は既存 TinyRoute Box を流用しつつ、「SmallObjectHotBox v3 対応 route」を追加する。
- 例:
- `ROUTE_SMALL_HEAP_V3` … SmallObjectHotBox v3。
- `ROUTE_TINY_V1` / `ROUTE_POOL_V1` / `ROUTE_LEGACY` … 現行のまま。
- PolicySnapshot で:
```c
enum SmallObjectHeapVersion {
SO_HEAP_V1 = 0,
SO_HEAP_V3 = 1,
};
typedef struct SmallObjectPolicySnapshot {
uint8_t heap_version[SMALLOBJECT_NUM_CLASSES]; // V1/V3
uint8_t enabled_mask[SMALLOBJECT_NUM_CLASSES]; // クラスごとの ON/OFF
uint16_t max_partial[SMALLOBJECT_NUM_CLASSES];
} SmallObjectPolicySnapshot;
```
- Front からは:
- `class_idx = size_to_smallobject_class(size);`
- `route = g_smallobject_route[class_idx];`
- `switch (route) { SMALL_HEAP_V3 / TINY_V1 / POOL_V1 / LEGACY }`
という 1 LUT + 1 switch で決定。C7 v2 / Tiny v1 / pool v1 など既存経路もここで選べるようにする。
段階的 rollout 戦略
-------------------
1. Phase A: 設計・骨格導入bench/実験専用)
- `SmallObjectHotBox` 型・SmallObjectColdIface・PolicySnapshot を導入。
- まずは Tiny C7-only を SmallObjectHotBox v3 経由に差し替え(現行 C7 v2 を v3 枠に移すイメージ)。
- ENV (`HAKMEM_SMALL_HEAP_V3_ENABLED`, `HAKMEM_SMALL_HEAP_V3_CLASSES`) で C7-only を v3 に切り替え可能に。
2. Phase B: C6/C5 など Tiny クラスを v3 に拡張
- C6-heavy / C5-heavy ベンチで C6/C5 を v3 に載せ、v1 vs v3 の perf A/B を取得。
- Mixed 161024B で C7-only v3 vs C6+C7 v3 を比較。
- ここまでは pool v1 をそのまま使い、SmallObjectHotBox v3 側で Tiny 相当をまとめて扱う。
3. Phase C: mid/smallmid pool を SmallObject に寄せる
- mid/smallmid サイズクラスを SmallObjectHotBox v3 のクラスとして増やし、pool v1 経路の一部を v3 に移管する。
- Cold IF は Superslab/Segment 共通のまま、SmallObject クラスの範囲だけ v3 で扱う。
4. Phase D: v1/v2/v3 の役割を整理
- v1 TinyHeap / pool v1 は完全な fallback/研究箱とし、標準は SmallObjectHotBox v3 をメインにする。
- v2 系TinyHotHeap v2 / pool v2は v3 開発の "失敗を記録した箱" として docs/analysis に残す。
非ゴール(この設計フェーズでやらないこと)
-----------------------------------------
- Superslab/Segment/Tier/Guard/Remote のフル再設計Segment サイズや Tier ポリシー変更など)は v3 後半〜v4 テーマとする。
- first-touch/pf/HugePage/NUMA 最適化は SmallObjectHotBox v3 の上に乗る別箱として扱い、この設計では触らない。
- 学習層 (ACE/ELO) の仕様変更は行わず、PolicySnapshot の更新だけを学習側が持ち、Hot パスは snapshot を読むだけにする。
まとめ
------
- SmallObjectHotBox v3 は、TinyHotHeap v2 / pool v2 で得た知見を統合し、「SmallObject 全体を 1 枚の Hot Box」として扱う設計。
- Hot Box と Cold Box の境界を 2 箇所refill/retireに絞り、Policy/Stats/Learning を別箱に押し出すことで、Box Theory に沿った形で mimalloc に近い構造を目指す。
- 実装は `docs/design/SMALLOBJECT_HOTBOX_V3_IMPLEMENTATION_GUIDE.md` に従って段階的に行い、常に v1/v2 への rollback path を維持する。
## Phase65 簡易ベンチメモC7-only v3, Tiny/Pool v2 OFF
- 短尺 20k/ws=64: v3 OFF 41.26M ops/s → v3 ON 57.55M ops/salloc_refill=49, fallback_v1=0, page_of_fail=0
- 長尺 1M/ws=400: v3 OFF 38.26M ops/s → v3 ON 50.24M ops/salloc_refill=5077, fallback_v1=0
- Mixed 161024B 1M/ws=400: v3 OFF 41.56M ops/s → v3 ON 49.40M ops/salloc_refill=2446, fallback_v1=0
- デフォルトは C7-only ON (`HAKMEM_SMALL_HEAP_V3_ENABLED` 未指定 / `HAKMEM_SMALL_HEAP_V3_CLASSES` 未指定で class7 のみ v3)。明示的に `ENABLED=0` または CLASSES から bit7 を外すことで v1 経路に戻せる。
## Phase65-HEAP_STATS 追加観測C7-only v3 A/B, Tiny/Pool v2 OFF
- 短尺 20k/ws=64:
- v3 OFF: 40.91M ops/s, `HEAP_STATS[7] fast=11015 slow=1`。
- v3 ON: 56.43M ops/s, v3 stats `alloc_refill=49 fb_v1=0 page_of_fail=0`(短尺ウォームアップ由来の refill。HEAP_STATS は Tiny v1 経路のみ出力。
- 長尺 1M/ws=400:
- v3 OFF: 38.29M ops/s, `HEAP_STATS[7] fast=550099 slow=1`。
- v3 ON: 50.25M ops/s, v3 stats `alloc_refill=5077 fb_v1=0 page_of_fail=0`。
- 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 を ONENABLED=1, CLASSES デフォルト=0x80としつつ、混乱を避けるため `HAKMEM_SMALL_HEAP_V3_ENABLED=0` / クラスマスクでいつでも v1 に戻せるようにしている。***
***