Add v4 C7/C6 fast classify and small-segment v4 scaffolding

This commit is contained in:
Moe Charm (CI)
2025-12-10 19:14:38 +09:00
parent 3261025995
commit f2ce7256cd
11 changed files with 221 additions and 17 deletions

View File

@ -31,6 +31,21 @@
- Batch+THP 寄り(+`HAKMEM_FREE_POLICY=batch HAKMEM_DISABLE_BATCH=0 HAKMEM_THP=auto`, SS_OS_STATS=1: **33.24M ops/s**`madvise=3 enomem=0 disabled=0`。perf: task-clock 49.57ms / minor-faults 6,731 / user 35.4ms / sys 15.1ms。
- 所感: pf/OPS とも大差なし。低 madvise での改善は見られず、Batch+THP 側がわずかに良好(+1〜2%。vm.max_map_count が厳しい環境で failfast を避けたい場合のみ keep/STRICT=0 に切替える運用が現実的。
### Phase PF2: pf/Segment 足場v4 強制で pf ベースライン取得)
- 目的: C7/C6 v4 を前提に pf/OS の現状値を押さえ、small-object 専用 Segment Box を設計する地ならし。
- Release pf ベースラインv3 OFF, v4=C7+C6 ON:
- ENV: `HAKMEM_PROFILE=MIXED_TINYV3_C7_SAFE HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_SS_OS_STATS=1 HAKMEM_SMALL_HEAP_V4_ENABLED=1 HAKMEM_SMALL_HEAP_V4_CLASSES=0xC0 HAKMEM_SMALL_HEAP_V3_ENABLED=0`
- perf stat: **31,779,973 ops/s**, cycles=205,322,023 / instructions=385,092,104 / task-clock=51.40ms / page-faults=6,702。`[SS_OS_STATS] alloc=2 free=4 madvise=2 enomem=0 disabled=0 mmap_total=2`
- DEBUG perf (cycles:u, -O0/-g, v4=C7+C6):
- Throughput=15,173,790 ops/s。self% 上位: tiny_alloc_gate_fast 13.33%, free 14.37% (small_heap_free_fast_v4 3.39%), main 12.93%, malloc 7.09%, ss_map_lookup 4.97%, memset/hak_super_registry_init 合算 ~4.5%, small_heap_alloc_fast_v4 2.23%, hak_tiny_size_to_class 2.21%, tiny_route_get 2.34%, front_gate_unified_enabled 2.36%, tiny_route_is_heap_kind 2.09%, xorshift32 2.08%。
- ドキュメント/足場:
- 追加: `docs/analysis/PF_STATUS_V4_202502.md`pf/OS 数値まとめ)、`docs/analysis/SMALLOBJECT_SEGMENT_V4_DESIGN.md`small-object Segment v4 設計メモ)。
- 追加: `core/box/smallsegment_v4_box.h` / `core/box/smallsegment_v4_env_box.h`(型と ENV だけ、挙動不変)。
- 健康診断Release 再ビルド後、v3 デフォルトで実行):
- Mixed 161024B (`HAKMEM_PROFILE=MIXED_TINYV3_C7_SAFE`): **36.16M ops/s**segv/assert なし)。
- C6-heavy (`HAKMEM_PROFILE=C6_HEAVY_LEGACY_POOLV1`, min=257/max=768): **28.33M ops/s**segv/assert なし)。
- 目安レンジからは少し低めMixed。今後の PF/Segment A/B はこのラインからの増減を確認する。
### Hotfix: madvise(ENOMEM) を握りつぶし、以降の madvise を停止Superslab OS Box
- 変更: `ss_os_madvise_guarded()` を追加し、madvise が ENOMEM を返したら `g_ss_madvise_disabled=1` にして以降の madvise をスキップ。EINVAL だけは従来どおり STRICT=1 で Fail-FastENV `HAKMEM_SS_MADVISE_STRICT` で緩和可)。
- stats: `[SS_OS_STATS]``madvise_enomem/madvise_other/madvise_disabled` を追加。HAKMEM_SS_OS_STATS=1 で確認可能。
@ -1071,3 +1086,13 @@ export HAKMEM_POOL_ZERO_MODE=header
- C6 v1: 28,690,913 ops/s
- C6 v4: 30,068,995 ops/s+4.8%segv/assert なし
- Mixed 161024B: デフォルトは C6 v1 のまま(`HAKMEM_SMALL_HEAP_V4_CLASSES=0x80` で C7-only。C6 v4 は研究箱として opt-in`0x40`)。
### Phase v4-5: C5 v4 パイロットC5-heavy 専用、強ゲート)
- 変更:
- v4 クラスビットに C5(bit5=0x20) を追加。HotBox_v4 / ColdIface_v4 / front free を C5 パスでも通電。
- `small_heap_v4_class_enabled(5)` 経由で route LUT が v4 を返せるようにしつつ、デフォルトは bit5=0研究箱
- ENV 例C5-only v4 の opt-in:
- `HAKMEM_SMALL_HEAP_V4_ENABLED=1`
- `HAKMEM_SMALL_HEAP_V4_CLASSES=0x20`
- 状態:
- 実装のみ。C5-heavy / Mixed A/B は未実施。デフォルトは C5 v1 のままMixed プロファイルも bit5=0で、segv/assert 無しを確認した上で昇格判断。

View File

@ -5,8 +5,9 @@
// - 挙動はまだ v3/v1 のまま。alloc/free 本体は後続フェーズで実装する。
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include "tiny_geometry_box.h"
@ -46,3 +47,4 @@ small_heap_ctx_v4* small_heap_ctx_v4_get(void);
// Hot path API (C7-only stub; later phases will expand)
void* small_heap_alloc_fast_v4(small_heap_ctx_v4* ctx, int class_idx);
void small_heap_free_fast_v4(small_heap_ctx_v4* ctx, int class_idx, void* ptr);
int smallobject_hotbox_v4_can_own(int class_idx, void* ptr);

View File

@ -47,3 +47,7 @@ static inline int small_heap_v4_c7_enabled(void) {
static inline int small_heap_v4_c6_enabled(void) {
return small_heap_v4_class_enabled(6);
}
static inline int small_heap_v4_c5_enabled(void) {
return small_heap_v4_class_enabled(5);
}

View File

@ -0,0 +1,11 @@
// smallsegment_v4_box.h - Small-object専用 Segment Box の宣言だけを置く足場
// Phase PF2: 挙動はまだ変えず、型と API だけを先行定義する。
#pragma once
typedef struct small_segment_v4 small_segment_v4;
// class_idx ごとに小さな Segment を確保/再利用する想定。
// まだ実装はなく、次フェーズで Superslab/OS との接続を決める。
small_segment_v4* smallsegment_v4_acquire(int class_idx);
void* smallsegment_v4_alloc_page(small_segment_v4* seg, int class_idx);
void smallsegment_v4_release_if_empty(small_segment_v4* seg);

View File

@ -0,0 +1,28 @@
// smallsegment_v4_env_box.h - small-object segment v4 の ENV ゲート
// Phase PF2: ENV を宣言するだけ。実装は次フェーズで追加。
#pragma once
#include <stdlib.h>
static inline int smallsegment_v4_enabled(void) {
static int g = -1;
if (__builtin_expect(g == -1, 0)) {
const char* e = getenv("HAKMEM_SMALL_SEGMENT_V4_ENABLED");
if (e && *e && *e != '0') {
g = 1;
} else {
g = 0;
}
}
return g;
}
static inline const char* smallsegment_v4_size_env(void) {
static int g_init = 0;
static const char* g_val = NULL;
if (__builtin_expect(!g_init, 0)) {
g_val = getenv("HAKMEM_SMALL_SEGMENT_V4_SIZE");
g_init = 1;
}
return g_val;
}

View File

@ -77,6 +77,16 @@ static inline bool tiny_ptr_fast_classify_enabled(void) {
return g != 0;
}
// C7/C6 v4 free 用 fast classify gate (default OFF)
static inline bool tiny_ptr_fast_classify_v4_enabled(void) {
static int g = -1;
if (__builtin_expect(g == -1, 0)) {
const char* e = getenv("HAKMEM_TINY_PTR_FAST_CLASSIFY_V4_ENABLED");
g = (e && *e && *e != '0') ? 1 : 0;
}
return g != 0;
}
// Optional stats gate
static inline bool tiny_front_v3_stats_enabled(void) {
static int g = -1;

View File

@ -252,6 +252,15 @@ static inline int free_tiny_fast(void* ptr) {
}
tiny_route_kind_t route = tiny_route_for_class((uint8_t)class_idx);
if ((class_idx == 7 || class_idx == 6) &&
route == TINY_ROUTE_SMALL_HEAP_V4 &&
tiny_ptr_fast_classify_v4_enabled() &&
smallobject_hotbox_v4_can_own(class_idx, base)) {
small_heap_free_fast_v4(small_heap_ctx_v4_get(), class_idx, base);
return 1;
}
const int use_tiny_heap = tiny_route_is_heap_kind(route);
const TinyFrontV3Snapshot* front_snap =
__builtin_expect(tiny_front_v3_enabled(), 0) ? tiny_front_v3_snapshot_get() : NULL;
@ -319,7 +328,7 @@ static inline int free_tiny_fast(void* ptr) {
if (__builtin_expect(use_tiny_heap, 0)) {
switch (route) {
case TINY_ROUTE_SMALL_HEAP_V4:
if (class_idx == 7 || class_idx == 6) {
if (class_idx == 7 || class_idx == 6 || class_idx == 5) {
small_heap_free_fast_v4(small_heap_ctx_v4_get(), class_idx, base);
return 1;
}

View File

@ -1,6 +1,6 @@
// smallobject_hotbox_v4.c - SmallObject HotHeap v4 (C7-only real path)
// smallobject_hotbox_v4.c - SmallObject HotHeap v4 (C5/C6/C7 opt-in)
//
// Phase v4-3: C7 クラスについては v4 独自の freelist/current/partial で完結させる
// Phase v4-3.1: C7 は v4 独自の freelist/current/partial で完結。C6/C5 は強ゲート付きで同形パスを使う
#include <stdlib.h>
#include <string.h>
@ -17,8 +17,6 @@
// TLS context
static __thread small_heap_ctx_v4 g_ctx_v4;
#define V4_MAX_PARTIAL_PAGES 1
small_heap_ctx_v4* small_heap_ctx_v4_get(void) {
return &g_ctx_v4;
}
@ -27,6 +25,10 @@ small_heap_ctx_v4* small_heap_ctx_v4_get(void) {
// helpers
// -----------------------------------------------------------------------------
static inline int v4_class_supported(int class_idx) {
return class_idx == 7 || class_idx == 6 || class_idx == 5;
}
static inline void v4_page_push_partial(small_class_heap_v4* h, small_page_v4* page) {
if (!h || !page) return;
page->next = h->partial_head;
@ -53,6 +55,11 @@ static inline void v4_page_push_full(small_class_heap_v4* h, small_page_v4* page
h->full_head = page;
}
static inline uint32_t v4_partial_limit(int class_idx) {
// C7 は refill/retire を抑えるため少し広めに保持
return (class_idx == 7) ? 2u : 1u;
}
static inline int v4_ptr_in_page(const small_page_v4* page, const uint8_t* ptr) {
if (!page || !ptr) return 0;
uint8_t* base = page->base;
@ -97,16 +104,32 @@ static small_page_v4* v4_find_page(small_class_heap_v4* h, const uint8_t* ptr, v
return p;
}
}
// full リストは探さないv4 C7 は partial/current だけで完結する想定)
for (small_page_v4* p = h->full_head; p; prev = p, p = p->next) {
if (v4_ptr_in_page(p, ptr)) {
if (loc) *loc = V4_LOC_FULL;
if (prev_out) *prev_out = prev;
return p;
}
}
return NULL;
}
int smallobject_hotbox_v4_can_own(int class_idx, void* ptr) {
if (__builtin_expect(!v4_class_supported(class_idx), 0)) return 0;
if (!small_heap_v4_class_enabled((uint8_t)class_idx)) return 0;
if (!ptr) return 0;
small_heap_ctx_v4* ctx = small_heap_ctx_v4_get();
if (!ctx) return 0;
small_class_heap_v4* h = &ctx->cls[class_idx];
return v4_find_page(h, (const uint8_t*)ptr, NULL, NULL) != NULL;
}
// -----------------------------------------------------------------------------
// Cold iface (C7-only, Tiny v1 経由)
// Cold iface (C5/C6/C7, Tiny v1 経由)
// -----------------------------------------------------------------------------
static small_page_v4* cold_refill_page_v4(small_heap_ctx_v4* hot_ctx, uint32_t class_idx) {
if (__builtin_expect(class_idx != 7 && class_idx != 6, 0)) return NULL;
if (__builtin_expect(!v4_class_supported((int)class_idx), 0)) return NULL;
(void)hot_ctx;
tiny_heap_ctx_t* tctx = tiny_heap_ctx_for_thread();
if (!tctx) return NULL;
@ -163,13 +186,14 @@ const SmallColdIfaceV4* small_cold_iface_v4_get(void) {
static small_page_v4* small_alloc_slow_v4(small_heap_ctx_v4* ctx, int class_idx) {
small_class_heap_v4* h = &ctx->cls[class_idx];
const uint32_t partial_limit = v4_partial_limit(class_idx);
small_page_v4* cur = h->current;
if (cur && cur->freelist) {
return cur; // usable current
}
if (cur && !cur->freelist) {
// current をいったん partial/full に退避partial を優先)
if (h->partial_count < V4_MAX_PARTIAL_PAGES) {
if (h->partial_count < partial_limit) {
v4_page_push_partial(h, cur);
} else {
v4_page_push_full(h, cur);
@ -193,8 +217,8 @@ static small_page_v4* small_alloc_slow_v4(small_heap_ctx_v4* ctx, int class_idx)
}
void* small_heap_alloc_fast_v4(small_heap_ctx_v4* ctx, int class_idx) {
if (__builtin_expect(class_idx != 7 && class_idx != 6, 0)) {
return NULL; // C6/C7 専用
if (__builtin_expect(!v4_class_supported(class_idx), 0)) {
return NULL; // C5/C6/C7 以外は未対応
}
if (!small_heap_v4_class_enabled((uint8_t)class_idx)) return NULL;
small_class_heap_v4* h = &ctx->cls[class_idx];
@ -240,7 +264,7 @@ static void v4_unlink_from_list(small_class_heap_v4* h, v4_loc_t loc, small_page
}
void small_heap_free_fast_v4(small_heap_ctx_v4* ctx, int class_idx, void* ptr) {
if (__builtin_expect(class_idx != 7 && class_idx != 6, 0)) {
if (__builtin_expect(!v4_class_supported(class_idx), 0)) {
return;
}
if (!small_heap_v4_class_enabled((uint8_t)class_idx)) return;
@ -252,6 +276,8 @@ void small_heap_free_fast_v4(small_heap_ctx_v4* ctx, int class_idx, void* ptr) {
small_page_v4* page = v4_find_page(h, (const uint8_t*)ptr, &loc, &prev);
if (!page) return;
const uint32_t partial_limit = v4_partial_limit(class_idx);
// freelist push
void* head = page->freelist;
memcpy(ptr, &head, sizeof(void*));
@ -274,7 +300,7 @@ void small_heap_free_fast_v4(small_heap_ctx_v4* ctx, int class_idx, void* ptr) {
page->next = NULL;
return;
}
if (h->partial_count < V4_MAX_PARTIAL_PAGES) {
if (h->partial_count < partial_limit) {
v4_page_push_partial(h, page);
return;
}
@ -296,7 +322,7 @@ void small_heap_free_fast_v4(small_heap_ctx_v4* ctx, int class_idx, void* ptr) {
} else if (loc == V4_LOC_FULL && page->freelist) {
// full → partial に戻すcurrent があっても partial 上限までは復帰)
v4_unlink_from_list(h, loc, prev, page);
if (h->partial_count < V4_MAX_PARTIAL_PAGES) {
if (h->partial_count < partial_limit) {
v4_page_push_partial(h, page);
} else {
v4_page_push_full(h, page); // 上限超なら戻す

View File

@ -0,0 +1,52 @@
# PF/OS ベースライン (PF2, small-object v4 状態)
- コマンド (Release, v4: C7+C6 を v4 に強制、v3 OFF):
```
HAKMEM_PROFILE=MIXED_TINYV3_C7_SAFE \
HAKMEM_BENCH_MIN_SIZE=16 \
HAKMEM_BENCH_MAX_SIZE=1024 \
HAKMEM_SS_OS_STATS=1 \
HAKMEM_SMALL_HEAP_V4_ENABLED=1 \
HAKMEM_SMALL_HEAP_V4_CLASSES=0xC0 \
HAKMEM_SMALL_HEAP_V3_ENABLED=0 \
perf stat -e cycles,instructions,task-clock,page-faults \
./bench_random_mixed_hakmem 1000000 400 1
```
- 結果 (環境: リリースビルド, ws=400, iters=1M):
- Throughput: **31,779,973 ops/s** (time=0.031s)
- perf stat: cycles=205,322,023 / instructions=385,092,104 / task-clock=51.40ms / page-faults=6,702
- `[SS_OS_STATS]` : alloc=2 free=4 madvise=2 madvise_enomem=0 madvise_disabled=0 mmap_total=2
- 所感:
- v4 (C7+C6) 強制時の pf/OS 基準値。v3 基準 (~40M) より遅めだが、pf 数値と OS stats を PF2 の起点として固定。
- 今後 SmallSegmentBox_v4 を繋ぐ A/B では、page-faults/SS_OS_STATS をこの値からどこまで下げられるかを指標にする。
## DEBUG perf (cycles:u, -O0/-g, v4=C7+C6)
- ビルド:
```
make clean
CFLAGS='-O0 -g' USE_LTO=0 OPT_LEVEL=0 NATIVE=0 make bench_random_mixed_hakmem -j4
```
- コマンド:
```
HAKMEM_PROFILE=DEBUG_TINY_FRONT_PERF \
HAKMEM_BENCH_MIN_SIZE=16 \
HAKMEM_BENCH_MAX_SIZE=1024 \
HAKMEM_SMALL_HEAP_V4_ENABLED=1 \
HAKMEM_SMALL_HEAP_V4_CLASSES=0xC0 \
HAKMEM_SMALL_HEAP_V3_ENABLED=0 \
perf record -F 5000 --call-graph dwarf -e cycles:u \
-o perf.data.pf_v4 ./bench_random_mixed_hakmem 1000000 400 1
```
- Throughput: **15,173,790 ops/s** (DEBUG, ws=400, iters=1M, v4=C7+C6)
- self% 上位 (perf report --stdio):
- free 14.37%small_heap_free_fast_v4 内 3.39%
- tiny_alloc_gate_fast 13.33%
- main 12.93%
- malloc 7.09%
- ss_map_lookup 4.97% / hak_super_registry_init + memset 合算 ~4.5%
- small_heap_alloc_fast_v4 2.23%
- hak_tiny_size_to_class 2.21% / tiny_route_get 2.34% / front_gate_unified_enabled 2.36% / tiny_route_is_heap_kind 2.09%
- xorshift32 2.08%
- メモ:
- v4 強制下でも gate/classify/ss_map_lookup が依然目立つ。Segment/OS 側が整えば pf と合わせて自明に下がるかを PF3 で確認。

View File

@ -0,0 +1,30 @@
# SmallObject Segment v4 設計メモ (PF2 草稿)
## 目的と背景
- Mixed 161024B で mimalloc の 70〜80% に近づくため、small-object 向けの pf/OS レイヤをまとめて削る。
- C7/C6 v4 が揃った状態を前提に、Superslab 配置と Segment 分割を見直し、触る Superslab 数と VMA 数を減らす。
- まだ実装は行わず、箱の分割とパラメータの置き場所を決める段階。
## 現状のレイアウト(把握用)
- Superslab サイズは既存の Tiny/Superslab のまま4KiB ページ上で C1〜C7 を混載)。
- C5〜C7 のページが混在し、Mixed では 1M ops あたりに跨る Superslab が多い。
- pf としては `mmap/madvise/munmap` の回数が小さくない。C7-only v4 でも minor-faults がボトルネック側に寄りつつある。
## small-object 専用 Segment 案(方向性)
- Superslab を small-object 専用に 2MiB/4MiB 単位でまとめ、C5〜C7 をできるだけ同一 Segment 内に詰める。
- Mixed で触る Superslab の種類を減らし、TLB/pf を削る。
- Box 化のイメージ:
- **SmallSegmentBox_v4**: Segment の取得/解放と page carving を担当。SuperslabBox/OS を内側で呼ぶ。
- **SmallSegmentEnvBox_v4**: ENV で Segment サイズやポリシーを決定(例: `HAKMEM_SMALL_SEGMENT_V4_SIZE=2M/4M`)。
- **SmallSegmentStatsBox**: 触った Segment 数、mmap/madvise 回数、ページ再利用回数をカウントし、PF 調査用にダンプ。
- HotBox_v4 からは ColdIface を通して Segment Box に触れるだけにし、ホットパスは segment/pf を意識しない。
## 今後のフェーズ
- **PF2今回**: 現状の pf/OS ベースラインを v4 状態で取り直し、Segment Box の箱だけ追加(挙動不変)。
- **PF3**: SmallSegmentBox_v4 を実装し、C7/C6 v4 で small-object 専用 Segment を試す A/B を実施。
- **PF4**: Segment サイズ/ポリシーのチューニングと pf/OS スタッツの可視化強化。成功したら ENV プリセットに反映。
## メモ
- C5 v4 はまだ研究箱C5-heavy 専用。Mixed では C5 v1 を維持する予定。
- C6 v4 は C6-heavy で +4〜5% が見えており、Mixed ではデフォルト OFF研究箱
- PF 系の変更はすべて ENV ゲート付きにし、既存の Mixed/C6-heavy 健康診断がいつでも通る状態を維持する。

View File

@ -16,8 +16,10 @@
- **v4-2**: C7-only を v4 に寄せ、v3 互換の挙動で動かすENV ゲート付き、v4 が優先)。
- **v4-3**: C7-only を v4 自前の freelist/current/partial で動かすCold は Tiny v1 経由。v3 はベンチ用に残し ENV で A/B。
- **v4-3.1 (今回)**: C7 v4 で current/partial 再利用を強化し、prepare_calls を v3 並みに抑制。C7-only ベンチで v4 が v3 比 +1% 程度まで回復。
- **v4-4**: C5〜C7 を含む全 small-object クラスを v4 に段階移行。route LUT から v4 を返せるようにする。
- **v4-5**: Segment/Page/Block レイアウトと pf 削減、WarmPool チューニングを v4 用に調整
- **v4-4**: C6 v4 パイロットC6-heavy 専用 opt-in。C7 v4 を維持しつつ C6 を v4 に載せる。
- **v4-5**: C5 v4 パイロットC5-heavy 専用 opt-in。Mixed 標準は C5 v1 のまま
- **PF2 (今回)**: v4 状態での pf/OS ベースライン取得と small-object Segment Box の箱だけ追加(挙動不変)。
- **v4-6 / PF3 以降**: Segment/Page/Block レイアウトと pf 削減、WarmPool チューニングを v4 用に調整。
## 現行 v3/v2 の扱い
- v3: C7-only front v3 の prototype として構造だけ再利用する。性能・安定のベースライン。
@ -28,6 +30,7 @@
- `core/box/smallobject_cold_iface_v4.h`: ColdIface の関数ポインタ箱C7 専用 refill/retire を v1 Tiny に繋ぐ)。
- `core/box/tiny_route_env_box.h`: `TINY_ROUTE_SMALLHEAP_V4` を追加し、ENV `HAKMEM_SMALL_HEAP_V4_ENABLED` / `HAKMEM_SMALL_HEAP_V4_CLASSES` から C7 を v4 route に載せられる(未指定なら OFF
- `core/front/malloc_tiny_fast.h`: route switch に v4 の case を足し、C7 v4 が ON のときは v4 経路(現在は C7 自前 freelist, それ以外は v1/v3 へフォールバック、OFF 時は従来の v3/v1。
- `core/box/smallsegment_v4_box.h` / `core/box/smallsegment_v4_env_box.h`: PF2 で追加した small-object Segment Box の足場(型と ENV だけ、挙動不変)。設計メモは `docs/analysis/SMALLOBJECT_SEGMENT_V4_DESIGN.md` にまとめる。
## A/B と運用
- Phase v4-3.1 時点の健康診断:
@ -44,3 +47,7 @@
- C6 v1: 28.69M ops/s
- C6 v4: 30.07M ops/s+4.8%segv/assert なし
- Mixed 161024B はデフォルトで C6 v1 のままC6 v4 は研究箱)。今後 C6 v4 の安定度を見つつ拡張予定。
- Phase v4-5 (C5 v4 パイロット; C5-heavy 専用 opt-in):
- ENV: `HAKMEM_SMALL_HEAP_V4_ENABLED=1`, `HAKMEM_SMALL_HEAP_V4_CLASSES=0x20`C5-only v4。C7 v4 / C6 v4 とは独立にビットで切替。
- 目的: C5-heavy ワークロードで v4 が v1 を上回るか確認。Mixed 標準は C5 v1 のままC5 v4 は研究箱)。
- ステータス: 実装済み。C5-heavy / Mixed の A/B は未実施。segv/assert の有無と throughput を確認してから昇格判断。