2025-12-09 21:50:15 +09:00
|
|
|
|
SmallObject HotBox v3 Design (Tiny + mid/smallmid 統合案)
|
|
|
|
|
|
========================================================
|
|
|
|
|
|
|
|
|
|
|
|
目的と背景
|
|
|
|
|
|
----------
|
|
|
|
|
|
- 現状の性能スナップショット:
|
|
|
|
|
|
- Mixed 16–1024B (1t, ws=400, iters=1M, PROFILE=C7_SAFE, Tiny v2 OFF):
|
|
|
|
|
|
- HAKMEM ≈ 40–50M ops/s
|
|
|
|
|
|
- mimalloc ≈ 110–120M ops/s
|
|
|
|
|
|
- system ≈ 90M ops/s
|
|
|
|
|
|
- mid/smallmid (bench_mid_large_mt, 1t, ws=400, iters=1M):
|
|
|
|
|
|
- HAKMEM ≈ 28–29M 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 なので挙動は従来通り。
|
|
|
|
|
|
|
2025-12-10 09:08:18 +09:00
|
|
|
|
### Phase S1: C6 v3 研究箱(C7 を壊さずにベンチ限定で解禁)
|
|
|
|
|
|
- Gate: `HAKMEM_SMALL_HEAP_V3_ENABLED`/`CLASSES` の bit7=C7(デフォルト ON=0x80)、bit6=C6(research-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 16–1024B: `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
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-12-09 21:50:15 +09:00
|
|
|
|
設計ゴール (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 16–1024B で 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/s(alloc_refill=49, fallback_v1=0, page_of_fail=0)。
|
|
|
|
|
|
- 長尺 1M/ws=400: v3 OFF 38.26M ops/s → v3 ON 50.24M ops/s(alloc_refill=5077, fallback_v1=0)。
|
|
|
|
|
|
- Mixed 16–1024B 1M/ws=400: v3 OFF 41.56M ops/s → v3 ON 49.40M ops/s(alloc_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 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`)。
|
Phase SO-BACKEND-OPT-1: v3 backend 分解&Tiny/ULTRA 完成世代宣言
=== 実装内容 ===
1. v3 backend 詳細計測
- ENV: HAKMEM_SO_V3_STATS で alloc/free パス内訳計測
- 追加 stats: alloc_current_hit, alloc_partial_hit, free_current, free_partial, free_retire
- so_alloc_fast / so_free_fast に埋め込み
- デストラクタで [ALLOC_DETAIL] / [FREE_DETAIL] 出力
2. v3 backend ボトルネック分析完了
- C7-only: alloc_current_hit=99.99%, alloc_refill=0.9%, free_retire=0.1%, page_of_fail=0
- Mixed: alloc_current_hit=100%, alloc_refill=0.85%, free_retire=0.07%, page_of_fail=0
- 結論: v3 ロジック部分(ページ選択・retire)は完全最適化済み
- 残り 5% overhead は内部コスト(header write, memcpy, 分岐)
3. Tiny/ULTRA 層「完成世代」宣言
- 総括ドキュメント作成: docs/analysis/PERF_EXEC_SUMMARY_ULTRA_PHASE_20251211.md
- CURRENT_TASK.md に Phase ULTRA 総括セクション追加
- AGENTS.md に Tiny/ULTRA 完成世代宣言追加
- 最終成果: Mixed 16–1024B = 43.9M ops/s (baseline 30.6M → +43.5%)
=== ボトルネック地図 ===
| 層 | 関数 | overhead |
|-----|------|----------|
| Front | malloc/free dispatcher | ~40–45% |
| ULTRA | C4–C7 alloc/free/refill | ~12% |
| v3 backend | so_alloc/so_free | ~5% |
| mid/pool | hak_super_lookup | 3–5% |
=== フェーズ履歴(Phase ULTRA cycle) ===
- Phase PERF-ULTRA-FREE-OPT-1: C4–C7 ULTRA統合 → +9.3%
- Phase REFACTOR: Code quality (60行削減)
- Phase PERF-ULTRA-REFILL-OPT-1a/1b: C7 ULTRA refill最適化 → +11.1%
- Phase SO-BACKEND-OPT-1: v3 backend分解 → 設計限界確認
=== 次フェーズ(独立ライン) ===
1. Phase SO-BACKEND-OPT-2: v3 header write削減 (1-2%)
2. Headerless/v6系: out-of-band header (1-2%)
3. mid/pool v3新設計: C6-heavy 10M → 20–25M
本フェーズでTiny/ULTRA層は「完成世代」として基盤固定。
今後の大きい変更はHeaderless/mid系の独立ラインで検討。
🤖 Generated with Claude Code
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2025-12-11 22:45:14 +09:00
|
|
|
|
- まとめ: HEAP_STATS で slow≈1 を維持したまま v3 ON は C7-only/Mixed とも大幅プラス。デフォルトでは C7-only v3 を ON(ENABLED=1, CLASSES デフォルト=0x80)としつつ、混乱を避けるため `HAKMEM_SMALL_HEAP_V3_ENABLED=0` / クラスマスクでいつでも v1 に戻せるようにしている。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Phase SO-BACKEND-OPT-1: v3 Backend ボトルネック分析(計画)
|
|
|
|
|
|
|
|
|
|
|
|
### 現状認識(PERF-ULTRA-REBASE-4 時点)
|
|
|
|
|
|
|
|
|
|
|
|
**v3 backend の perf 内訳** (Mixed 16-1024B, iters=1M, ws=400):
|
|
|
|
|
|
|
|
|
|
|
|
| 関数 | self% | カテゴリ |
|
|
|
|
|
|
|------|--------|---------|
|
|
|
|
|
|
| **so_alloc_fast** | **2.46%** | v3 alloc hot path |
|
|
|
|
|
|
| **so_free** | **2.47%** | v3 free hot path |
|
|
|
|
|
|
| **so_alloc** | **1.21%** | v3 alloc slow path |
|
|
|
|
|
|
| **合計** | **~5.14%** | v3 backend 全体 |
|
|
|
|
|
|
|
|
|
|
|
|
**参考: 全体の主要ボトルネック**:
|
|
|
|
|
|
- free dispatcher: 25.48%
|
|
|
|
|
|
- malloc dispatcher: 21.13%
|
|
|
|
|
|
- C7 ULTRA alloc: 7.66%
|
|
|
|
|
|
- C7 ULTRA free: 3.50%
|
|
|
|
|
|
- C7 ULTRA refill/page_of: 1.78%
|
|
|
|
|
|
- **v3 backend: ~5.14%** ← 次のターゲット
|
|
|
|
|
|
|
|
|
|
|
|
### v3 の責務と設計上の位置づけ
|
|
|
|
|
|
|
|
|
|
|
|
**v3 が担当するクラス**:
|
|
|
|
|
|
- C7: ULTRA との役割分担(ULTRA cold path / fallback)
|
|
|
|
|
|
- C2-C6: v1 が遅い理由で v3 を選ぶケース(Mixed に含まれる)
|
|
|
|
|
|
- mid/smallmid: 将来的に pool v1 から v3 へ移行予定
|
|
|
|
|
|
|
|
|
|
|
|
**v3 が呼ばれるパターン**:
|
|
|
|
|
|
1. **C7 ULTRA miss**: C7 ULTRA が TLS segment から外れた ptr → v3 free の「class lookup + page_of」で検証
|
|
|
|
|
|
2. **C2-C6 fast path**: alloc/free が v3 route で current/partial から pop/push
|
|
|
|
|
|
3. **slow path**: page refill / retire / remote push など Cold IF 経由
|
|
|
|
|
|
|
|
|
|
|
|
### Phase SO-BACKEND-OPT-1 の目的
|
|
|
|
|
|
|
|
|
|
|
|
v3 backend の「何が重いのか」を細分化する:
|
|
|
|
|
|
- **alloc 側**: freelist carve / memset / class_idx 判定 / metadata access のうち、どれが hot か
|
|
|
|
|
|
- **free 側**: header write / magic check / page_of lookup / remote 判定のうち、どれが hot か
|
|
|
|
|
|
- **クラス別分布**: C4/C5 が vs C6/C7、どちらが多く呼ばれるか
|
|
|
|
|
|
- **slow path 率**: page_refill / remote / v1 fallback が何回発生しているか
|
|
|
|
|
|
|
|
|
|
|
|
### 実装方針
|
|
|
|
|
|
|
|
|
|
|
|
**HAKMEM_SO_V3_STATS=1** で有効化する stats 構造体:
|
|
|
|
|
|
```c
|
|
|
|
|
|
struct SmallObjectStatsV3 {
|
|
|
|
|
|
uint64_t total_alloc; // 総 alloc call 数
|
|
|
|
|
|
uint64_t total_free; // 総 free call 数
|
|
|
|
|
|
uint64_t alloc_by_class[8]; // C0-C7 毎の alloc
|
|
|
|
|
|
uint64_t free_by_class[8]; // C0-C7 毎の free
|
|
|
|
|
|
uint64_t alloc_refill; // slow path (page refill)
|
|
|
|
|
|
uint64_t alloc_current_hit; // fast path: current から pop
|
|
|
|
|
|
uint64_t alloc_partial_hit; // fast path: partial から pop
|
|
|
|
|
|
uint64_t free_current; // fast path: current に push
|
|
|
|
|
|
uint64_t free_partial; // fast path: partial に push
|
|
|
|
|
|
uint64_t free_retire; // slow path: page retire
|
|
|
|
|
|
uint64_t free_remote; // slow path: remote free
|
|
|
|
|
|
uint64_t page_of_fail; // free 時の page lookup 失敗(diagnostics)
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**埋め込み箇所**:
|
|
|
|
|
|
- `so_alloc_fast()`: fast hit/miss で分岐計測
|
|
|
|
|
|
- `so_free_fast()`: page locate / retire / remote で分岐計測
|
|
|
|
|
|
- `so_alloc_slow_refill()`: refill call count
|
|
|
|
|
|
|
|
|
|
|
|
### Phase SO-BACKEND-OPT-1 計測結果(実施完了)
|
|
|
|
|
|
|
|
|
|
|
|
**C7-only (1024B, 1M iter, ws=400) — C7 ULTRA 無効化**:
|
|
|
|
|
|
```
|
|
|
|
|
|
[SMALL_HEAP_V3_STATS] cls=7 route_hits=550100 alloc_calls=550100
|
|
|
|
|
|
alloc_refill=5045 alloc_fb_v1=0 free_calls=399828 free_fb_v1=0 page_of_fail=0
|
|
|
|
|
|
[ALLOC_DETAIL] alloc_current_hit=550095 alloc_partial_hit=5
|
|
|
|
|
|
[FREE_DETAIL] free_current=0 free_partial=1 free_retire=349
|
|
|
|
|
|
Throughput: 42.4M ops/s (baseline 62.9M with ULTRA)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Mixed 16–1024B (1M iter, ws=400) — C7 ULTRA 無効化**:
|
|
|
|
|
|
```
|
|
|
|
|
|
[SMALL_HEAP_V3_STATS] cls=7 route_hits=275089 alloc_calls=275089
|
|
|
|
|
|
alloc_refill=2340 alloc_fb_v1=0 free_calls=204753 free_fb_v1=0 page_of_fail=0
|
|
|
|
|
|
[ALLOC_DETAIL] alloc_current_hit=275089 alloc_partial_hit=0
|
|
|
|
|
|
[FREE_DETAIL] free_current=0 free_partial=0 free_retire=142
|
|
|
|
|
|
Throughput: 35.9M ops/s (baseline 43.4M with ULTRA)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 計測分析
|
|
|
|
|
|
|
|
|
|
|
|
**Alloc パス**:
|
|
|
|
|
|
| メトリクス | C7-only | Mixed | 評価 |
|
|
|
|
|
|
|-----------|---------|-------|------|
|
|
|
|
|
|
| current_hit率 | 99.99% | 100% | ✅ 優秀(page locality 最適) |
|
|
|
|
|
|
| partial_hit率 | 0.001% | 0% | 正常(current が供給) |
|
|
|
|
|
|
| refill率 | 0.9% | 0.85% | ✅ 許容(page churn 低) |
|
|
|
|
|
|
| alloc_fallback_v1 | 0% | 0% | ✅ v3 は robust |
|
|
|
|
|
|
|
|
|
|
|
|
**Free パス**:
|
|
|
|
|
|
| メトリクス | C7-only | Mixed | 評価 |
|
|
|
|
|
|
|-----------|---------|-------|------|
|
|
|
|
|
|
| retire率 | 0.09% | 0.07% | ✅ 低い(page churn 低) |
|
|
|
|
|
|
| free_fallback_v1 | 0% | 0% | ✅ v3 安定 |
|
|
|
|
|
|
| page_of_fail | 0/399828 | 0/204753 | ✅ 完璧(no corner case) |
|
|
|
|
|
|
| free_current | 0 | 0 | 正常 (empty page以外は partial/retire) |
|
|
|
|
|
|
|
|
|
|
|
|
### 推察される所見
|
|
|
|
|
|
|
|
|
|
|
|
1. **Alloc は最適化済み**: current_hit ≈100% で、ほぼ毎回 TLS から pop(page locality 完璧)
|
|
|
|
|
|
2. **Refill は低コスト**: 0.9% 程度の頻度で Cold IF にfallthrough(ページ管理が効率的)
|
|
|
|
|
|
3. **Free は堅牢**: page_of_fail = 0 で全 ptr が正確に所属ページを特定(O(1) lookup または十分な線形探索に成功)
|
|
|
|
|
|
4. **設計的な限界**: free_current=0 は normal pattern(empty page を温存するから)
|
|
|
|
|
|
|
|
|
|
|
|
### 次フェーズの決定ポイント
|
|
|
|
|
|
|
|
|
|
|
|
計測結果に基づいて以下から選択:
|
|
|
|
|
|
|
|
|
|
|
|
| シナリオ | 判定 | 対策 |
|
|
|
|
|
|
|---------|------|------|
|
|
|
|
|
|
| alloc_refill が大きい(>10%) | **判定**: ✅ NO(0.9%) | - |
|
|
|
|
|
|
| alloc_current_hit が小さい(<50%) | **判定**: ✅ NO(99.99%) | - |
|
|
|
|
|
|
| free_retire が大きい(>3%) | **判定**: ✅ NO(0.09%) | - |
|
|
|
|
|
|
| page_of_fail > 0 | **判定**: ✅ NO(0) | - |
|
|
|
|
|
|
|
|
|
|
|
|
**結論**: v3 backend の alloc/free hot path は既に最適化済み。**so_alloc/so_free の内部コスト(header write, memcpy, 分岐等)が 5% overhead の主因。**
|
|
|
|
|
|
|
|
|
|
|
|
### Phase SO-BACKEND-OPT-2 候補(ドキュメント段階、未実装)
|
|
|
|
|
|
|
|
|
|
|
|
v3 backend の so_alloc_fast/so_free_fast パスの「内部最適化」に進む場合:
|
|
|
|
|
|
|
|
|
|
|
|
1. **Header write 削減** (alloc path)
|
|
|
|
|
|
- 現在: alloc 毎に `tiny_region_id_write_header()` を呼び出し
|
|
|
|
|
|
- target: carve 時の一括初期化(light mode)
|
|
|
|
|
|
- expected: 1-2% 削減
|
|
|
|
|
|
|
|
|
|
|
|
2. **Freelist carve 最適化** (alloc slow path)
|
|
|
|
|
|
- 現在: refill 時に `so_build_freelist()` で手動 carve
|
|
|
|
|
|
- target: pre-carved freelist を Cold IF から返却
|
|
|
|
|
|
- expected: <1% 削減(refill 0.9% × 削減率)
|
|
|
|
|
|
|
|
|
|
|
|
3. **Memcpy 削減** (free fast path)
|
|
|
|
|
|
- 現在: `*(void**)ptr = page->freelist;` ← 8 byte store
|
|
|
|
|
|
- target: inline assembly or atomic で削減
|
|
|
|
|
|
- expected: 0.5-1% 削減
|
|
|
|
|
|
|
|
|
|
|
|
4. **分岐削減** (alloc/free両路)
|
|
|
|
|
|
- 現在: skip_header_c7, page->used==0, partial_count check 等
|
|
|
|
|
|
- target: hot path を完全直線化(unlikely() で cold path segregate)
|
|
|
|
|
|
- expected: 0.5-1% 削減
|
|
|
|
|
|
|
|
|
|
|
|
**推奨**: Phase SO-BACKEND-OPT-2 は実装前に perf profile (cycles:u) で so_alloc_fast/so_free_fast を詳細計測することを推奨。
|
|
|
|
|
|
|
2025-12-12 16:26:42 +09:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Phase v11b-1: Free Path Micro-Optimization (2025-12-12)
|
|
|
|
|
|
|
|
|
|
|
|
### 変更内容
|
|
|
|
|
|
|
|
|
|
|
|
perf profile で `free_tiny_fast()` のシリアル ULTRA チェック (C7→C6→C5→C4) が 11.73% overhead を占めていることを発見。`malloc_tiny_fast()` と同様のパターンを適用:
|
|
|
|
|
|
|
|
|
|
|
|
1. **C7 ULTRA early-exit**: Policy snapshot 前に C7 判定(最頻出パスを最短化)
|
|
|
|
|
|
2. **Single switch**: route_kind[class_idx] で一発分岐(jump table 生成)
|
|
|
|
|
|
3. **Dead code 削除**: 未使用の v4 チェック、重複 v7 チェックを除去
|
|
|
|
|
|
|
|
|
|
|
|
### 結果
|
|
|
|
|
|
|
|
|
|
|
|
| Workload | v11a-5 | v11b-1 | 改善 |
|
|
|
|
|
|
|----------|--------|--------|------|
|
|
|
|
|
|
| Mixed 16-1024B | 45.4M ops/s | 50.7M ops/s | **+11.7%** |
|
|
|
|
|
|
| C6-heavy | 49.1M ops/s | 52.0M ops/s | **+5.9%** |
|
|
|
|
|
|
| C6-heavy + MID v3.5 | 53.1M ops/s | 53.6M ops/s | +0.9% |
|
|
|
|
|
|
|
|
|
|
|
|
### 教訓
|
|
|
|
|
|
|
|
|
|
|
|
- alloc パス最適化 (v11a-5) と同じパターンが free パスにも有効
|
|
|
|
|
|
- シリアル if-else チェーン → switch (jump table) で大幅改善
|
|
|
|
|
|
- フロント層の分岐コストは backend より大きい(今回 +11.7% vs 想定 +1-2%)
|
|
|
|
|
|
|
2025-12-09 21:50:15 +09:00
|
|
|
|
***
|