Files
hakmem/docs/analysis/SMALLOBJECT_HOTBOX_V3_DESIGN.md
Moe Charm (CI) 1a8652a91a Phase TLS-UNIFY-3: C6 intrusive freelist implementation (完成)
Implement C6 ULTRA intrusive LIFO freelist with ENV gating:
- Single-linked LIFO using next pointer at USER+1 offset
- tiny_next_store/tiny_next_load for pointer access (single source of truth)
- Segment learning via ss_fast_lookup (per-class seg_base/seg_end)
- ENV gate: HAKMEM_TINY_C6_ULTRA_INTRUSIVE_FL (default OFF)
- Counters: c6_ifl_push/pop/fallback in FREE_PATH_STATS

Files:
- core/box/tiny_ultra_tls_box.h: Added c6_head field for intrusive LIFO
- core/box/tiny_ultra_tls_box.c: Pop/push with intrusive branching (case 6)
- core/box/tiny_c6_ultra_intrusive_env_box.h: ENV gate (new)
- core/box/tiny_c6_intrusive_freelist_box.h: L1 pure LIFO (new)
- core/tiny_debug_ring.h: C6_IFL events
- core/box/free_path_stats_box.h/c: c6_ifl_* counters

A/B Test Results (1M iterations, ws=200, 257-512B):
- ENV_OFF (array): 56.6 Mop/s avg
- ENV_ON (intrusive): 57.6 Mop/s avg (+1.8%, within noise)
- Counters verified: c6_ifl_push=265890, c6_ifl_pop=265815, fallback=0

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

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2025-12-12 16:26:42 +09:00

465 lines
23 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 に戻せるようにしている。
---
## 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 161024B (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 から poppage locality 完璧)
2. **Refill は低コスト**: 0.9% 程度の頻度で Cold IF にfallthroughページ管理が効率的
3. **Free は堅牢**: page_of_fail = 0 で全 ptr が正確に所属ページを特定O(1) lookup または十分な線形探索に成功)
4. **設計的な限界**: free_current=0 は normal patternempty page を温存するから)
### 次フェーズの決定ポイント
計測結果に基づいて以下から選択:
| シナリオ | 判定 | 対策 |
|---------|------|------|
| alloc_refill が大きい(>10% | **判定**: ✅ NO0.9% | - |
| alloc_current_hit が小さい(<50% | **判定**: ✅ NO99.99% | - |
| free_retire が大きい(>3% | **判定**: ✅ NO0.09% | - |
| page_of_fail > 0 | **判定**: ✅ NO0 | - |
**結論**: 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 を詳細計測することを推奨。
---
## 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%
***