Phase 36-37: TinyHotHeap v2 HotBox redesign and C7 current_page policy fixes

- Redefine TinyHotHeap v2 as per-thread Hot Box with clear boundaries
- Add comprehensive OS statistics tracking for SS allocations
- Implement route-based free handling for TinyHeap v2
- Add C6/C7 debugging and statistics improvements
- Update documentation with implementation guidelines and analysis
- Add new box headers for stats, routing, and front-end management
This commit is contained in:
Moe Charm (CI)
2025-12-08 21:30:21 +09:00
parent 34a8fd69b6
commit 8f18963ad5
37 changed files with 3205 additions and 167 deletions

View File

@ -1,5 +1,42 @@
## HAKMEM 状況メモ (2025-12-05 更新 / C7 Warm/TLS Bind 反映)
### Phase 36: TinyHotHeap v2 を「Hot Box」として再定義設計ドキュメント整備
- 状況: HotHeap v2 は Phase35 まで「v1 TinyHeap/C7 SAFE の上に乗るラッパ」で、Mixed では構造的に勝てない状態だったため、**いったん棚上げ** の扱いになっていた。
- 方針転換: `docs/analysis/TINY_HEAP_V2_DESIGN.md` に Phase36 セクションを追加し、TinyHeap v2 自体を per-thread Hot BoxTinyHotHeapBox v2として再定義。Superslab/Tier/Remote/Stats/Learning はすべて外側の Cold Box に落とし、境界を
- alloc 側: `tiny_heap_refill_slow(th, ci)`
- free 側: `tiny_heap_page_retire_slow(th, page)`
の 1 箇所に集約する設計に切り替えた。
- 設計内容: `TinyHeapCtx` / `TinyClassHeap` / `TinyPageMeta` による per-thread TinyHotHeapC5〜C7を Hot Box とし、C7-only → C6/C5 へ段階的に拡張する A 案を第一候補として整理。C7 超ホットレーンB 案、mimalloc 風 Segment+Page+Block へのフル寄せC 案)は将来の選択肢として文書化。
- ENV/A/B: `HAKMEM_TINY_HOTHEAP_V2` / `HAKMEM_TINY_HOTHEAP_CLASSES` で v2 ON/OFF と対象クラスを切り替える方針を維持(デフォルトは依然 v2 OFF, v1 C7_SAFE。Route Snapshot (`g_tiny_route_class[ci]`) で v1/v2/legacy を 1 LUT + 1 分岐で選択するイメージを明示。
- 実装ステータス: 現時点では **設計とドキュメントのみ整備**。まだコードに TinyHotHeap v2 の新しい Hot Box 構造は反映していない(既存 v2 ラッパ実装もそのまま)。
- 次のアクション窓口:
- 実装ガイド: `docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md`(本フェーズで骨子を追加、実装担当 AI/開発者向けの指示書)。
- 詳細設計: `docs/analysis/TINY_HEAP_V2_DESIGN.md`Phase36+ セクションに A/B/C 案と Box 構造を集約)。
### Phase 37: TinyHotHeap v2 C7 current_page ポリシー修正(スローパス多発の是正)
- ベンチ結果Release, PROFILE=C7_SAFE:
- C7-only (ws=64, iters=20k): v2 OFF **40.09M ops/s** / v2 ON **25.57M ops/s**`HEAP_STATS[7] fast=97 slow=32758` → ほぼ slow_prepare
- Mixed 161024B (ws=256, iters=20k): v2 OFF **40.99M ops/s** / v2 ON **32.07M ops/s**`fast=141 slow=16654`)。
- 所感: v2 ON 時に `current_page` がほぼ活きず、C7-only/Mixed とも毎回 `slow_prepare` に落ちて大幅回帰しているcurrent_page stats: `prepare_calls=slow_prepare` で current_null≒0。現状では v2 を運用に使えないため、デフォルトは引き続き v2 OFFC7_SAFE + HOTHEAP_V2=0が安全。
- 対応方針: `docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md` に Phase37 セクションを追加し、C7-only 向けに
- v2 専用 current_page デバッグ統計の追加prepare_calls / prepare_with_current_null など)
- refill_slow で必ず `current_page` をセットするようにする
- free 側で current_page を維持・再利用するポリシーを導入
- empty page の retire 条件を見直し(即返却せず partial として保持する実験を許容)
- v1 C7 SAFE/TinyHeapBox の current_page ポリシーを v2 に移植
を実装タスクとして明示。
- 判定基準: Phase37 完了の目安として、
- C7-only で v2 OFF と v2 ON が ±5% 以内(できれば同等以上)
- `HEAP_STATS[7]``fast≈11015 slow≈1` に戻る
- v2 current_page stats で `prepare_with_current_null``prepare_calls` に対して ≪1% 程度
を満たすことを目標とする。満たせない場合は引き続き v2 は研究用箱(デフォルト OFFのままとする。
### Phase 33: C7 v2 HotHeap A/B薄型化の足がかり
- 条件: Release, HEAP_STATS=ON, C7 SAFE (`PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_LARSON_FIX=1`), v2 は C7 のみ (`HAKMEM_TINY_HOTHEAP_CLASSES=0x80`)。
- C7-only (ws=64, iters=20k): v2 OFF **39.42M ops/s** / v2 ON **43.55M ops/s** cls7 fast=11015 / slow=1 で一致、v2カウンタ増加。→ v2 の current/freelist 自前化で C7-only はわずかにプラス。
- Mixed 161024B (ws=256, iters=20k): v2 OFF **40.44M ops/s** / v2 ON **36.58M ops/s** cls7 fast=5691 / slow=1 で一致、v2カウンタ増加。→ Mixed では v2 のラップ/lease がまだオーバーヘッド。
- 所感: C7-only では v2 を保ったまま次の薄型化に進めそう。Mixed での回帰は lease 判定や v1 呼び出し重複が疑わしいため、Phase34 で「余計な枝/ロード」の整理候補に入れる。
### Phase 32: C7 HotHeap v2 で current_page を自前管理(ページ供給だけ v1 から lease
- v1 側に `tiny_heap_c7_lease_page_for_v2()` を追加し、C7 SAFE が保持するページ情報meta/ss/base/capacityを lease できる境界を用意。
- v2 TLS ctx に C7 用 storage_page を持たせ、current_page が空/枯渇したときに lease を巻き取り、pop/push は v1 の `tiny_heap_page_pop/free_local` を直接叩く形に変更meta/ss_active の整合は v1 に委譲)。
@ -505,3 +542,71 @@
3. Phase 3: 必要に応じて C0〜C4 も段階的に TinyHeap 側へ移植し、TinyFront は薄いラッパ層に縮退させる。
- 方針メモ:
- Box Theory は維持しつつ、「Hot TinyHeapシンプル・高速」と「Cold Superslab/SafetyBox複雑・安全」の二層構造に整理することで、mimalloc に近い性能と HAKMEM 固有の安全性・観測性・学習レイヤを両立させる方向性と認識。
### Phase3334: C7 v2 A/B と運用方針固定
- C7 v2 HotHeapcurrent/freelist を v2、自前 lease は v1A/B:
- C7-only ws=64 iters=20k PROFILE=C7_SAFE HOT=1 LARSON_FIX=1 HEAP_STATS=ON: v2 OFF **39.42M** / v2 ON **43.55M**cls7 fast=11015 slow=1
- Mixed 161024B ws=256 iters=20k 同条件: v2 OFF **40.44M** / v2 ON **36.58M**cls7 fast=5691 slow=1。混在では回帰。
- 運用方針: v2 は当面 **C7-only ベンチ専用**Mixed では v2 OFF 推奨。C7 SAFE v1 を標準とし、v2 は A/B 実験用。
- v2 stats 追加ENV `HAKMEM_TINY_HOTHEAP_V2_STATS=1`:
- `alloc_calls/alloc_fast/alloc_lease/alloc_fb_v1`
- `free_calls/free_fast/free_fb_v1`
- destructor で `[HOTHEAP_V2_C7_STATS] ...` を 1 行 dump。
- Mixed 回帰の切り分け: 上記 stats で「v2 fast/fallback の比率」を取り、枝コストか fallback 多発かを次フェーズで判断する。
### Phase35: v2 を封印し、標準を v1+C7_SAFE に固定
- 観測: v2 ON で C7-only/Mixed とも大幅劣化alloc_fast がほぼ当たらず lease→v1 fallback が支配、HEAP_STATS slow_prepare も爆増)。
- 方針:
- 標準設定は `HAKMEM_TINY_HOTHEAP_V2=0`。Mixed では必ず OFF。C7-only でも v2 は明示 ON の研究モード扱い。
- コード上の v2 分岐は `__builtin_expect(..., 0)` に寄せ、コメントで「現状は負けている実験箱」だと明示。性能比較や mimalloc 対決では v1+C7_SAFE に集中する。
- 次の焦点: v1+C7_SAFE のホットパス薄型化や mid-size 側の改善を優先し、v2 を触るのは「それでも足りない」ときの次善策に回す。
### Phase36: C7-only HotHeap v2 を Hot Box 化lease は v1、Hot 部は自前)
- 変更:
- `tiny_hotheap_v2_tls_get()` で全クラスの storage_page を resetstride 初期化し、TLS ctx を明示確保。v2 ページ node 取得ヘルパを追加storage 再利用+不足時は calloc
- C7 専用の Hot パスを実装: `tiny_hotheap_v2_alloc`/`free` で current_page→partial→refill の順に処理し、pop/push は lease した v1 page を更新して meta/ss を維持。used==0 は retire_slow 経由でリセット。
- Cold 境界を明示: `tiny_hotheap_v2_refill_slow``tiny_heap_c7_lease_page_for_v2()` から 1 枚借りて wrap`tiny_hotheap_v2_page_retire_slow`lease 情報を返却し resetが Hot→Cold 唯一の接点。
- Route/Front: route LUT をそのまま使い、C7 直線パスでも route==`TINY_ROUTE_HOTHEAP_V2` のときだけ v2 を試すよう整理free 側も同様。v2 を外したときの branch コストを最小化。
- v2 stats は現状維持alloc/free fast/lease/fallback を ENV `HAKMEM_TINY_HOTHEAP_V2_STATS` で計測)。
- 状態: v2 は依然 C7-only 実験箱。デフォルト/推奨は `HOTHEAP_V2=0`v1+C7_SAFE。A/B 計測は未実施make -j4 成功のみ)。
### Phase53: mid/smallmid シングルスレッド基線v2 OFF, C7_SAFE
- 条件: threads=1 / cycles=1,000,000 / ws=400 / reps=1。
- スループット: HAKMEM **28.43M ops/s**perf run 29.02M、mimalloc **54.22M**、system **15.29M**
- perf stat (HAKMEM): cycles=211,394,722、instructions=513,342,673 (IPC≈2.43)、task-clock=57.48msuser 33.29 / sys 25.22、page-faults=7,374。
- 所感: mid/smallmid では mimalloc が約1.9×。pf は ~7.3k で Tiny よりやや多めだが CPU 命令量と sys 比率が主因。次のターゲット選定用に数字を確定。
### Phase54: mid/smallmid CPU ホットパス分析perf record, userland
- 条件: `perf record -g -e cycles:u -o perf.data.mid_u ./bench_mid_large_mt_hakmem 1 1000000 400 1`C7_SAFE, v2 OFF
- ホットシンボルself%: hak_pool_try_alloc.part.0=14.7%、worker_run=9.2%、free/hak_free_at≈910%、__memset_avx2_unaligned_erms≈9%、mid_desc_lookup=3.8%、hak_super_lookup=1.4%、hak_pool_free≈0.7%。
- 所感: pool allocator と free/memset が支配的。mid_desc_lookup / hak_super_lookup も目立つ。Phase55 ターゲットは pool 系を筆頭候補に。
- ドキュメント: docs/analysis/MID_LARGE_CPU_HOTPATH_ANALYSIS.md に詳細。
### Phase55: pool allocator ホットパス軽量化(着手予定)
- スコープ: core/hakmem_pool.c / core/hakmem_smallmid.c / box/pool_* の fast path。
- 方針: hak_pool_try_alloc を「TLS/local freelist 即 return」直線化、debug/stat/slow を unlikely 側に寄せる。self-thread free では pool push を最優先にし、cross-thread/debug は後段に分離。mid_desc_lookup を入口で 1 回だけ決めて TLS にキャッシュする案を検討。
- 成功ライン (bench_mid_large_mt_hakmem 1 1000000 400 1, Release, v2 OFF, C7_SAFE): ops/s を +5〜10%2829M→3032M改善し、perf self% で hak_pool_try_alloc+free/hak_free_at の合計が数ポイント低下していれば〆とする。
### Phase56: pool ホットパス実装の初期薄型化(結果)
- 変更: PoolBlock→user 変換を `hak_pool_block_to_user()` にまとめ、TLS fast path/pop と self-thread free の最優先 push を直線化。owner 判定を mid_desc 1 回の lookup に寄せ、同一スレッド free は ring/lo push で即 return。
- ベンチ (C6-heavy: min=257/max=768, ws=256, iters=20k, C7_SAFE, v2 OFF): **25.93M ops/s**(従来 2829M から悪化。perf stat (1M, ws=400): cycles=225,766,496 / instructions=528,335,613 / task-clock=57.88ms、ops/s=25.71M。
- 所感: fast path整理だけでは効果が出ず回帰。pool self%/memset などが依然重い可能性が高い。次の一手は pool fast pathのさらなる枝削減や memset/desc cache の見直しが必要。
### Phase57: pool v2 回帰トリアージ(実装)
- `HAKMEM_POOL_V2_ENABLED` で旧/新 pool を A/B できる gate を追加(デフォルト 0 = 旧挙動)。細分スイッチとして `HAKMEM_POOL_V2_BLOCK_TO_USER` / `HAKMEM_POOL_V2_TLS_FAST_PATH` を用意v2時のみ有効
- v1/v2 の両実装を同居させ、公開 API はラッパでルート切替。mid_lookup も同様に gate。
- ベンチ (C6-heavy, 1M/400, Release):
- v2 OFF (v1): **27.40M ops/s**
- v2 ON: **24.73M ops/s**
- 所感: 回帰を避けるため標準は v1 を維持しつつ、どの変更が悪かったかをスイッチ単位でA/Bできるようにした。次は各スイッチON/OFFでの差分取り、必要なら v2 を研究箱のまま凍結。
### Phase Final: 現行デフォルトと研究箱の位置づけ
- 標準構成: `HAKMEM_TINY_HEAP_PROFILE=C7_SAFE`, `HAKMEM_TINY_HOTHEAP_V2=0`, `HAKMEM_TINY_STATS_BOX=1`, `HAKMEM_TINY_STATS_BATCH=0`, Pool は `HAKMEM_POOL_V2_ENABLED=0`。HugePage/ヘッダ light/off はすべて OFF。
- 研究箱:
- TinyHotHeap v2C7-only: 明示的に `HAKMEM_TINY_HOTHEAP_V2=1` のときだけ使用。Mixed では推奨 OFF。
- Pool v2: `HAKMEM_POOL_V2_ENABLED=1` を立てたときのみ。標準は v1 で回帰なしを優先。
- HugePage/ヘッダ light/off: first-touch/bench 専用。デフォルトはすべて OFF。
- 次フェーズの方向性v3 テーマの入口):
- TinyHeap v2 を C5C7 統合 HotHeap として再設計(現行 v2 とは別ライン)。
- first-touch/page-fault 系の本格対応HugePage/ヘッダ light の昇格可否検証)。
- mid/smallmid の pool/フロント最適化、または mid/large route のさらなるフラット化。

View File

@ -4,7 +4,422 @@
#include "pagefault_telemetry_box.h" // Box PageFaultTelemetry (PF_BUCKET_MID)
void* hak_pool_try_alloc(size_t size, uintptr_t site_id) {
// Pool v2 is experimental. Default OFF (use legacy v1 path).
static inline int hak_pool_v2_enabled(void) {
static int g = -1;
if (__builtin_expect(g == -1, 0)) {
const char* e = getenv("HAKMEM_POOL_V2_ENABLED");
g = (e && *e && *e != '0') ? 1 : 0;
}
return g;
}
// Fine-grained switches (only used when v2 is enabled).
static inline int hak_pool_v2_block_to_user_enabled(void) {
static int g = -1;
if (__builtin_expect(g == -1, 0)) {
const char* e = getenv("HAKMEM_POOL_V2_BLOCK_TO_USER");
g = (e && *e && *e != '0') ? 1 : 0;
if (g == -1) g = 1;
}
return g;
}
static inline int hak_pool_v2_tls_fast_enabled(void) {
static int g = -1;
if (__builtin_expect(g == -1, 0)) {
const char* e = getenv("HAKMEM_POOL_V2_TLS_FAST_PATH");
g = (e && *e && *e != '0') ? 1 : 0;
if (g == -1) g = 1;
}
return g;
}
// Thin helper to keep the hot path straight-line when converting a PoolBlock to
// a user pointer. All sampling/stat updates remain here so the callers stay
// small.
static inline void* hak_pool_block_to_user(PoolBlock* b, int class_idx, uintptr_t site_id) {
void* raw = (void*)b;
AllocHeader* hdr = (AllocHeader*)raw;
mid_set_header(hdr, g_class_sizes[class_idx], site_id);
void* user = (char*)raw + HEADER_SIZE;
mid_page_inuse_inc(raw);
t_pool_rng ^= t_pool_rng << 13;
t_pool_rng ^= t_pool_rng >> 17;
t_pool_rng ^= t_pool_rng << 5;
if ((t_pool_rng & ((1u << g_count_sample_exp) - 1u)) == 0u) {
g_pool.hits[class_idx]++;
}
pagefault_telemetry_touch(PF_BUCKET_MID, user);
return user;
}
// Legacy inline conversion used when v2 helper is disabled.
static inline void* hak_pool_block_to_user_legacy(PoolBlock* b, int class_idx, uintptr_t site_id) {
void* raw = (void*)b;
AllocHeader* hdr = (AllocHeader*)raw;
mid_set_header(hdr, g_class_sizes[class_idx], site_id);
void* user = (char*)raw + HEADER_SIZE;
mid_page_inuse_inc(raw);
t_pool_rng ^= t_pool_rng << 13;
t_pool_rng ^= t_pool_rng >> 17;
t_pool_rng ^= t_pool_rng << 5;
if ((t_pool_rng & ((1u << g_count_sample_exp) - 1u)) == 0u) {
g_pool.hits[class_idx]++;
}
pagefault_telemetry_touch(PF_BUCKET_MID, user);
return user;
}
static inline void* hak_pool_try_alloc_v2_impl(size_t size, uintptr_t site_id) {
// Debug: IMMEDIATE output to verify function is called
static int first_call = 1;
if (__builtin_expect(first_call, 0)) {
HAKMEM_LOG("[Pool] hak_pool_try_alloc FIRST CALL EVER!\n");
first_call = 0;
}
if (__builtin_expect(size == 40960, 0)) {
HAKMEM_LOG("[Pool] hak_pool_try_alloc called with 40KB (Bridge class 5)\n");
}
hak_pool_init(); // pthread_once() ensures thread-safe init (no data race!)
// Debug for 33-41KB allocations
if (__builtin_expect(size >= 33000 && size <= 41000, 0)) {
HAKMEM_LOG("[Pool] hak_pool_try_alloc: size=%zu (after init)\n", size);
}
// P1.7 guard: allow pool by default even when called from wrappers.
// Only block if explicitly disabled via env or during nested recursion.
extern int hak_in_wrapper(void);
extern __thread int g_hakmem_lock_depth;
int in_wrapper = hak_in_wrapper();
if (in_wrapper && g_hakmem_lock_depth > 1) {
if (__builtin_expect(size >= 33000 && size <= 41000, 0)) {
HAKMEM_LOG("[Pool] REJECTED: nested wrapper depth=%d\n", g_hakmem_lock_depth);
}
return NULL;
}
if (in_wrapper && !g_wrap_l2_enabled) {
if (__builtin_expect(size >= 33000 && size <= 41000, 0)) {
HAKMEM_LOG("[Pool] REJECTED: in_wrapper=%d, wrap_l2=%d\n", in_wrapper, g_wrap_l2_enabled);
}
return NULL;
}
if (!hak_pool_is_poolable(size)) {
if (__builtin_expect(size >= 33000 && size <= 41000, 0)) {
HAKMEM_LOG("[Pool] REJECTED: not poolable (min=%d, max=%d)\n", POOL_MIN_SIZE, POOL_MAX_SIZE);
}
return NULL;
}
// Get class and shard indices
int class_idx = hak_pool_get_class_index(size);
if (class_idx < 0) {
if (__builtin_expect(size >= 33000 && size <= 41000, 0)) {
HAKMEM_LOG("[Pool] REJECTED: class_idx=%d (size=%zu not mapped)\n", class_idx, size);
}
return NULL;
}
if (__builtin_expect(size >= 33000 && size <= 41000, 0)) {
HAKMEM_LOG("[Pool] ACCEPTED: class_idx=%d, proceeding with allocation\n", class_idx);
}
// MF2: Per-Page Sharding path
if (g_mf2_enabled) {
return mf2_alloc_fast(class_idx, size, site_id);
}
// OLD PATH: TLS fast path (ring then local LIFO); drain TC only when needed
PoolTLSRing* ring = &g_tls_bin[class_idx].ring;
if (g_tc_enabled && ring->top < g_tc_drain_trigger && mid_tc_has_items(class_idx)) {
HKM_TIME_START(t_tc_drain);
if (mid_tc_drain_into_tls(class_idx, ring, &g_tls_bin[class_idx])) {
HKM_TIME_END(HKM_CAT_TC_DRAIN, t_tc_drain);
if (ring->top > 0) {
HKM_TIME_START(t_ring_pop0);
PoolBlock* tlsb = ring->items[--ring->top];
HKM_TIME_END(HKM_CAT_POOL_TLS_RING_POP, t_ring_pop0);
return hak_pool_block_to_user(tlsb, class_idx, site_id);
}
} else { HKM_TIME_END(HKM_CAT_TC_DRAIN, t_tc_drain); }
}
if (g_tls_ring_enabled) {
if (ring->top == 0) {
atomic_fetch_add_explicit(&g_pool.ring_underflow, 1, memory_order_relaxed);
}
if (ring->top > 0) {
HKM_TIME_START(t_ring_pop1);
PoolBlock* tlsb = ring->items[--ring->top];
HKM_TIME_END(HKM_CAT_POOL_TLS_RING_POP, t_ring_pop1);
return hak_pool_block_to_user(tlsb, class_idx, site_id);
}
}
if (g_tls_bin[class_idx].lo_head) {
HKM_TIME_START(t_lifo_pop0);
PoolBlock* b = g_tls_bin[class_idx].lo_head;
g_tls_bin[class_idx].lo_head = b->next;
if (g_tls_bin[class_idx].lo_count) g_tls_bin[class_idx].lo_count--;
HKM_TIME_END(HKM_CAT_POOL_TLS_LIFO_POP, t_lifo_pop0);
return hak_pool_block_to_user(b, class_idx, site_id);
}
// Compute shard only when we need to access shared structures
int shard_idx = hak_pool_get_shard_index(site_id);
// Try to batch-pop from a non-empty shard using trylock to fill TLS ring
if (g_tls_ring_enabled) {
int s0 = choose_nonempty_shard(class_idx, shard_idx);
for (int probe = 0; probe < g_trylock_probes; ++probe) {
int s = (s0 + probe) & (POOL_NUM_SHARDS - 1);
pthread_mutex_t* l = &g_pool.freelist_locks[class_idx][s].m;
atomic_fetch_add_explicit(&g_pool.trylock_attempts, 1, memory_order_relaxed);
if (pthread_mutex_trylock(l) == 0) {
atomic_fetch_add_explicit(&g_pool.trylock_success, 1, memory_order_relaxed);
// First, drain any remote frees into freelist
if (atomic_load_explicit(&g_pool.remote_count[class_idx][s], memory_order_relaxed) != 0) {
drain_remote_locked(class_idx, s);
}
PoolBlock* head = g_pool.freelist[class_idx][s];
int to_ring = POOL_L2_RING_CAP - ring->top; if (to_ring < 0) to_ring = 0;
while (head && to_ring-- > 0) { PoolBlock* nxt = head->next; ring->items[ring->top++] = head; head = nxt; }
while (head) { PoolBlock* nxt = head->next; head->next = g_tls_bin[class_idx].lo_head; g_tls_bin[class_idx].lo_head = head; g_tls_bin[class_idx].lo_count++; head = nxt; }
g_pool.freelist[class_idx][s] = head;
if (!head) clear_nonempty_bit(class_idx, s);
pthread_mutex_unlock(l);
if (ring->top > 0) {
PoolBlock* tlsb = ring->items[--ring->top];
return hak_pool_block_to_user(tlsb, class_idx, site_id);
}
}
}
}
// Try TLS active pages (owner-only local bump-run, up to 3)
PoolTLSPage* ap = NULL;
if (g_tls_active_page_a[class_idx].page && g_tls_active_page_a[class_idx].count > 0 && g_tls_active_page_a[class_idx].bump < g_tls_active_page_a[class_idx].end) ap = &g_tls_active_page_a[class_idx];
else if (g_tls_active_page_b[class_idx].page && g_tls_active_page_b[class_idx].count > 0 && g_tls_active_page_b[class_idx].bump < g_tls_active_page_b[class_idx].end) ap = &g_tls_active_page_b[class_idx];
else if (g_tls_active_page_c[class_idx].page && g_tls_active_page_c[class_idx].count > 0 && g_tls_active_page_c[class_idx].bump < g_tls_active_page_c[class_idx].end) ap = &g_tls_active_page_c[class_idx];
if (ap) {
if (g_tls_ring_enabled && ring->top < POOL_L2_RING_CAP) {
int need = POOL_L2_RING_CAP - ring->top;
(void)refill_tls_from_active_page(class_idx, ring, &g_tls_bin[class_idx], ap, need);
}
PoolBlock* b = NULL;
if (ring->top > 0) { b = ring->items[--ring->top]; }
else if (ap->page && ap->count > 0 && ap->bump < ap->end) {
b = (PoolBlock*)(void*)ap->bump; ap->bump += (HEADER_SIZE + g_class_sizes[class_idx]); ap->count--; if (ap->bump >= ap->end || ap->count<=0){ ap->page=NULL; ap->count=0; }
}
if (b) {
g_pool.hits[class_idx]++;
return hak_pool_block_to_user(b, class_idx, site_id);
}
}
// Lock the shard freelist for this (class, shard)
pthread_mutex_t* lock = &g_pool.freelist_locks[class_idx][shard_idx].m;
HKM_TIME_START(t_lock);
struct timespec ts_lk1; int lk1 = hkm_prof_begin(&ts_lk1);
(void)ts_lk1; (void)lk1; // Unused profiling variables
pthread_mutex_lock(lock);
HKM_TIME_END(HKM_CAT_POOL_LOCK, t_lock);
hkm_prof_end(lk1, HKP_POOL_LOCK, &ts_lk1);
// Try to pop from freelist
PoolBlock* block = g_pool.freelist[class_idx][shard_idx];
if (!block) {
// Before refilling, try draining remote stack and simple shard steal
int stole = 0;
const FrozenPolicy* pol = hkm_policy_get();
if (pol) {
uint16_t cap = 0;
if (class_idx < 5) cap = pol->mid_cap[class_idx];
else if (class_idx == 5 && pol->mid_dyn1_bytes != 0) cap = pol->mid_cap_dyn1;
else if (class_idx == 6 && pol->mid_dyn2_bytes != 0) cap = pol->mid_cap_dyn2;
// Drain remotes
if (atomic_load_explicit(&g_pool.remote_count[class_idx][shard_idx], memory_order_relaxed) != 0) {
drain_remote_locked(class_idx, shard_idx);
block = g_pool.freelist[class_idx][shard_idx];
}
// Light shard steal when over cap
if (!block && cap > 0 && g_pool.pages_by_class[class_idx] >= cap) {
HKM_TIME_START(t_steal);
for (int d = 1; d <= 4 && !stole; d++) {
int s1 = (shard_idx + d) & (POOL_NUM_SHARDS - 1);
int s2 = (shard_idx - d) & (POOL_NUM_SHARDS - 1);
if (is_shard_nonempty(class_idx, s1)) {
pthread_mutex_t* l2 = &g_pool.freelist_locks[class_idx][s1].m;
pthread_mutex_lock(l2);
PoolBlock* b2 = g_pool.freelist[class_idx][s1];
if (b2) {
g_pool.freelist[class_idx][s1] = b2->next;
if (!g_pool.freelist[class_idx][s1]) clear_nonempty_bit(class_idx, s1);
block = b2; stole = 1;
}
pthread_mutex_unlock(l2);
}
if (!stole && is_shard_nonempty(class_idx, s2)) {
pthread_mutex_t* l3 = &g_pool.freelist_locks[class_idx][s2].m;
pthread_mutex_lock(l3);
PoolBlock* b3 = g_pool.freelist[class_idx][s2];
if (b3) {
g_pool.freelist[class_idx][s2] = b3->next;
if (!g_pool.freelist[class_idx][s2]) clear_nonempty_bit(class_idx, s2);
block = b3; stole = 1;
}
pthread_mutex_unlock(l3);
}
}
HKM_TIME_END(HKM_CAT_SHARD_STEAL, t_steal);
}
}
if (!stole && !block) {
// Freelist empty, refill page
PoolTLSPage* tap = NULL;
if (g_tls_active_page_a[class_idx].page == NULL || g_tls_active_page_a[class_idx].count == 0) tap = &g_tls_active_page_a[class_idx];
else if (g_tls_active_page_b[class_idx].page == NULL || g_tls_active_page_b[class_idx].count == 0) tap = &g_tls_active_page_b[class_idx];
else if (g_tls_active_page_c[class_idx].page == NULL || g_tls_active_page_c[class_idx].count == 0) tap = &g_tls_active_page_c[class_idx];
else tap = &g_tls_active_page_a[class_idx];
HKM_TIME_START(t_alloc_page);
if (alloc_tls_page(class_idx, tap)) {
HKM_TIME_END(HKM_CAT_POOL_ALLOC_TLS_PAGE, t_alloc_page);
pthread_mutex_unlock(lock);
// Top-up ring and return
ap = tap;
if (g_tls_ring_enabled && ring->top < POOL_L2_RING_CAP) {
int need = POOL_L2_RING_CAP - ring->top;
(void)refill_tls_from_active_page(class_idx, ring, &g_tls_bin[class_idx], ap, need);
}
PoolBlock* takeb = NULL;
if (ring->top > 0) {
HKM_TIME_START(t_ring_pop2);
takeb = ring->items[--ring->top];
HKM_TIME_END(HKM_CAT_POOL_TLS_RING_POP, t_ring_pop2);
} else if (ap->page && ap->count > 0 && ap->bump < ap->end) {
takeb = (PoolBlock*)(void*)ap->bump;
ap->bump += (HEADER_SIZE + g_class_sizes[class_idx]);
ap->count--;
if (ap->bump >= ap->end || ap->count == 0) {
ap->page = NULL;
ap->count = 0;
}
}
return hak_pool_block_to_user(takeb, class_idx, site_id);
}
HKM_TIME_START(t_refill);
struct timespec ts_rf; int rf = hkm_prof_begin(&ts_rf);
int ok = refill_freelist(class_idx, shard_idx);
HKM_TIME_END(HKM_CAT_POOL_REFILL, t_refill);
hkm_prof_end(rf, HKP_POOL_REFILL, &ts_rf);
if (!ok) {
t_pool_rng ^= t_pool_rng << 13; t_pool_rng ^= t_pool_rng >> 17; t_pool_rng ^= t_pool_rng << 5;
if ((t_pool_rng & ((1u<<g_count_sample_exp)-1u)) == 0u) g_pool.misses[class_idx]++;
pthread_mutex_unlock(lock);
return NULL;
}
}
}
// Pop block and adopt page
g_pool.freelist[class_idx][shard_idx] = block->next;
mid_desc_adopt(block, class_idx, (uint64_t)(uintptr_t)pthread_self());
if (g_pool.freelist[class_idx][shard_idx] == NULL) clear_nonempty_bit(class_idx, shard_idx);
pthread_mutex_unlock(lock);
// Store to TLS then pop
PoolBlock* take;
if (g_tls_ring_enabled && ring->top < POOL_L2_RING_CAP) { ring->items[ring->top++] = block; take = ring->items[--ring->top]; }
else { block->next = g_tls_bin[class_idx].lo_head; g_tls_bin[class_idx].lo_head = block; g_tls_bin[class_idx].lo_count++;
if (g_tls_ring_enabled && ring->top > 0) { take = ring->items[--ring->top]; }
else { take = g_tls_bin[class_idx].lo_head; g_tls_bin[class_idx].lo_head = take->next; if (g_tls_bin[class_idx].lo_count) g_tls_bin[class_idx].lo_count--; } }
return hak_pool_block_to_user(take, class_idx, site_id);
}
static inline void hak_pool_free_v2_impl(void* ptr, size_t size, uintptr_t site_id) {
if (!ptr) return;
hak_pool_init();
if (!hak_pool_is_poolable(size)) return;
if (g_mf2_enabled) { mf2_free(ptr); return; }
void* raw = (char*)ptr - HEADER_SIZE;
AllocHeader* hdr = (AllocHeader*)raw;
MidPageDesc* d_desc = mid_desc_lookup(ptr);
int mid_by_desc = d_desc != NULL;
if (!mid_by_desc && g_hdr_light_enabled < 2) {
if (hdr->magic != HAKMEM_MAGIC) { MF2_ERROR_LOG("Invalid magic 0x%X in pool_free, expected 0x%X", hdr->magic, HAKMEM_MAGIC); return; }
if (hdr->method != ALLOC_METHOD_POOL) { MF2_ERROR_LOG("Wrong method %d in pool_free, expected POOL (%d)", hdr->method, ALLOC_METHOD_POOL); return; }
}
int class_idx = mid_by_desc ? (int)d_desc->class_idx : hak_pool_get_class_index(size);
if (class_idx < 0) return;
PoolBlock* block = (PoolBlock*)raw;
uint64_t owner_tid = 0;
if (d_desc) owner_tid = d_desc->owner_tid;
else if (g_hdr_light_enabled < 2) owner_tid = hdr->owner_tid;
const uint64_t self_tid = (uint64_t)(uintptr_t)pthread_self();
if (g_pool.tls_free_enabled) {
const int same_thread = owner_tid != 0 && owner_tid == self_tid;
if (same_thread) {
PoolTLSRing* ring = &g_tls_bin[class_idx].ring;
if (g_tls_ring_enabled && ring->top < POOL_L2_RING_CAP) { ring->items[ring->top++] = block; }
else {
block->next = g_tls_bin[class_idx].lo_head;
g_tls_bin[class_idx].lo_head = block;
g_tls_bin[class_idx].lo_count++;
if ((int)g_tls_bin[class_idx].lo_count > g_tls_lo_max) {
size_t spill = g_tls_bin[class_idx].lo_count / 2;
int shard = hak_pool_get_shard_index(site_id);
// Spill half of local freelist to remote freelist
while (spill-- && g_tls_bin[class_idx].lo_head) {
PoolBlock* b = g_tls_bin[class_idx].lo_head;
g_tls_bin[class_idx].lo_head = b->next;
g_tls_bin[class_idx].lo_count--;
HKM_TIME_START(t_remote_push1);
uintptr_t old_head;
do {
old_head = atomic_load_explicit(&g_pool.remote_head[class_idx][shard], memory_order_acquire);
b->next = (PoolBlock*)old_head;
} while (!atomic_compare_exchange_weak_explicit(&g_pool.remote_head[class_idx][shard],
&old_head, (uintptr_t)b,
memory_order_release, memory_order_relaxed));
atomic_fetch_add_explicit(&g_pool.remote_count[class_idx][shard], 1, memory_order_relaxed);
HKM_TIME_END(HKM_CAT_POOL_REMOTE_PUSH, t_remote_push1);
}
set_nonempty_bit(class_idx, shard);
}
}
} else {
if (g_tc_enabled && owner_tid != 0) { MidTC* otc = mid_tc_lookup_by_tid(owner_tid); if (otc) { mid_tc_push(otc, class_idx, block); return; } }
int shard = hak_pool_get_shard_index(site_id); uintptr_t old_head; HKM_TIME_START(t_remote_push2);
do { old_head = atomic_load_explicit(&g_pool.remote_head[class_idx][shard], memory_order_acquire); block->next = (PoolBlock*)old_head; } while (!atomic_compare_exchange_weak_explicit(&g_pool.remote_head[class_idx][shard], &old_head, (uintptr_t)block, memory_order_release, memory_order_relaxed));
atomic_fetch_add_explicit(&g_pool.remote_count[class_idx][shard], 1, memory_order_relaxed); HKM_TIME_END(HKM_CAT_POOL_REMOTE_PUSH, t_remote_push2); set_nonempty_bit(class_idx, shard);
}
} else {
int shard_idx2 = hak_pool_get_shard_index(site_id); pthread_mutex_t* lock = &g_pool.freelist_locks[class_idx][shard_idx2].m; pthread_mutex_lock(lock); block->next = g_pool.freelist[class_idx][shard_idx2]; g_pool.freelist[class_idx][shard_idx2] = block; set_nonempty_bit(class_idx, shard_idx2); pthread_mutex_unlock(lock);
}
t_pool_rng ^= t_pool_rng << 13; t_pool_rng ^= t_pool_rng >> 17; t_pool_rng ^= t_pool_rng << 5; if ((t_pool_rng & ((1u<<g_count_sample_exp)-1u)) == 0u) g_pool.frees[class_idx]++;
mid_page_inuse_dec_and_maybe_dn(raw);
}
static inline int hak_pool_mid_lookup_v2_impl(void* ptr, size_t* out_size) {
if (g_mf2_enabled) { MidPage* page = mf2_addr_to_page(ptr); if (page) { int c = (int)page->class_idx; if (c < 0 || c >= POOL_NUM_CLASSES) return 0; size_t sz = g_class_sizes[c]; if (sz == 0) return 0; if (out_size) *out_size = sz; return 1; } }
MidPageDesc* d = mid_desc_lookup(ptr); if (!d) return 0; int c = (int)d->class_idx; if (c < 0 || c >= POOL_NUM_CLASSES) return 0; size_t sz = g_class_sizes[c]; if (sz == 0) return 0; if (out_size) *out_size = sz; return 1;
}
static inline void hak_pool_free_fast_v2_impl(void* ptr, uintptr_t site_id) {
if (!ptr || !g_pool.initialized) return; if (g_mf2_enabled) { MidPage* page = mf2_addr_to_page(ptr); if (page) { mf2_free(ptr); return; } }
MidPageDesc* d = mid_desc_lookup(ptr); if (!d) return; size_t sz = g_class_sizes[(int)d->class_idx]; if (sz == 0) return; hak_pool_free(ptr, sz, site_id);
}
static inline void* hak_pool_try_alloc_v1_impl(size_t size, uintptr_t site_id) {
// Debug: IMMEDIATE output to verify function is called
static int first_call = 1;
if (first_call) { HAKMEM_LOG("[Pool] hak_pool_try_alloc FIRST CALL EVER!\n"); first_call = 0; }
@ -289,7 +704,7 @@ void* hak_pool_try_alloc(size_t size, uintptr_t site_id) {
return user4;
}
void hak_pool_free(void* ptr, size_t size, uintptr_t site_id) {
static inline void hak_pool_free_v1_impl(void* ptr, size_t size, uintptr_t site_id) {
if (!ptr) return;
hak_pool_init();
if (!hak_pool_is_poolable(size)) return;
@ -353,14 +768,47 @@ void hak_pool_free(void* ptr, size_t size, uintptr_t site_id) {
mid_page_inuse_dec_and_maybe_dn(raw);
}
int hak_pool_mid_lookup(void* ptr, size_t* out_size) {
static inline int hak_pool_mid_lookup_v1_impl(void* ptr, size_t* out_size) {
if (g_mf2_enabled) { MidPage* page = mf2_addr_to_page(ptr); if (page) { int c = (int)page->class_idx; if (c < 0 || c >= POOL_NUM_CLASSES) return 0; size_t sz = g_class_sizes[c]; if (sz == 0) return 0; if (out_size) *out_size = sz; return 1; } }
MidPageDesc* d = mid_desc_lookup(ptr); if (!d) return 0; int c = (int)d->class_idx; if (c < 0 || c >= POOL_NUM_CLASSES) return 0; size_t sz = g_class_sizes[c]; if (sz == 0) return 0; if (out_size) *out_size = sz; return 1;
}
void hak_pool_free_fast(void* ptr, uintptr_t site_id) {
static inline void hak_pool_free_fast_v1_impl(void* ptr, uintptr_t site_id) {
if (!ptr || !g_pool.initialized) return; if (g_mf2_enabled) { MidPage* page = mf2_addr_to_page(ptr); if (page) { mf2_free(ptr); return; } }
MidPageDesc* d = mid_desc_lookup(ptr); if (!d) return; size_t sz = g_class_sizes[(int)d->class_idx]; if (sz == 0) return; hak_pool_free(ptr, sz, site_id);
}
// --- Public wrappers (env-gated) ----------------------------------------------
static inline int hak_pool_v2_route(void) { return hak_pool_v2_enabled(); }
void* hak_pool_try_alloc(size_t size, uintptr_t site_id) {
if (!hak_pool_v2_route()) {
return hak_pool_try_alloc_v1_impl(size, site_id);
}
return hak_pool_try_alloc_v2_impl(size, site_id);
}
void hak_pool_free(void* ptr, size_t size, uintptr_t site_id) {
if (!hak_pool_v2_route()) {
hak_pool_free_v1_impl(ptr, size, site_id);
return;
}
hak_pool_free_v2_impl(ptr, size, site_id);
}
void hak_pool_free_fast(void* ptr, uintptr_t site_id) {
if (!hak_pool_v2_route()) {
hak_pool_free_fast_v1_impl(ptr, site_id);
return;
}
hak_pool_free_fast_v2_impl(ptr, site_id);
}
int hak_pool_mid_lookup(void* ptr, size_t* out_size) {
if (!hak_pool_v2_route()) {
return hak_pool_mid_lookup_v1_impl(ptr, out_size);
}
return hak_pool_mid_lookup_v2_impl(ptr, out_size);
}
#endif // POOL_API_INC_H

View File

@ -361,6 +361,7 @@ void superslab_free(SuperSlab* ss) {
if (lazy_zero_enabled) {
#ifdef MADV_DONTNEED
(void)madvise((void*)ss, ss_size, MADV_DONTNEED);
ss_os_stats_record_madvise();
#endif
}
return;
@ -399,6 +400,7 @@ void superslab_free(SuperSlab* ss) {
atomic_load_explicit(&ss->total_active_blocks, memory_order_relaxed));
#endif
ss_os_stats_record_free();
munmap(ss, ss_size);
// Update statistics for actual release to OS

View File

@ -159,6 +159,7 @@ void tiny_ss_cache_set_class_cap(int class_idx, size_t new_cap) {
// Convert cache entry back to SuperSlab* and release it to OS.
SuperSlab* ss = (SuperSlab*)entry;
size_t ss_size = (size_t)1 << ss->lg_size;
ss_os_stats_record_free();
munmap((void*)ss, ss_size);
// Update global stats to keep accounting consistent.

View File

@ -10,8 +10,13 @@
#include <unistd.h>
// Global counters for debugging (non-static for external access)
_Atomic uint64_t g_ss_mmap_count = 0;
_Atomic uint64_t g_final_fallback_mmap_count = 0;
extern _Atomic uint64_t g_ss_mmap_count;
extern _Atomic uint64_t g_final_fallback_mmap_count;
extern _Atomic uint64_t g_ss_os_alloc_calls;
extern _Atomic uint64_t g_ss_os_free_calls;
extern _Atomic uint64_t g_ss_os_madvise_calls;
extern _Atomic uint64_t g_ss_os_huge_alloc_calls;
extern _Atomic uint64_t g_ss_os_huge_fail_calls;
// ============================================================================
// OOM Diagnostics
@ -84,6 +89,55 @@ static void log_superslab_oom_once(size_t ss_size, size_t alloc_size, int err) {
g_hakmem_lock_depth--; // Now safe to restore (all libc calls complete)
}
// ============================================================================
// HugePage (experimental) helper
// ============================================================================
static void* ss_os_acquire_hugepage_try(size_t ss_size, uintptr_t ss_mask, int populate) {
#ifdef MAP_HUGETLB
size_t huge_sz = ss_os_huge_size_bytes();
if (ss_size != huge_sz) {
// For now, only attempt hugepage when requested SuperSlab size matches the HugePage size.
return NULL;
}
int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB;
#ifdef MAP_POPULATE
if (populate) {
flags |= MAP_POPULATE;
}
#endif
#ifdef MAP_HUGE_2MB
// Best-effort: allow the kernel to pick 2MB huge pages explicitly when available.
if (huge_sz == (2ULL << 20)) {
flags |= MAP_HUGE_2MB;
}
#endif
void* ptr = mmap(NULL, huge_sz, PROT_READ | PROT_WRITE, flags, -1, 0);
if (ptr == MAP_FAILED) {
ss_os_stats_record_huge_fail();
return NULL;
}
if (((uintptr_t)ptr & ss_mask) != 0) {
munmap(ptr, huge_sz);
ss_os_stats_record_huge_fail();
return NULL;
}
ss_os_stats_record_huge_alloc();
ss_os_stats_record_alloc();
atomic_fetch_add(&g_ss_mmap_count, 1);
return ptr;
#else
(void)ss_size;
(void)ss_mask;
(void)populate;
return NULL;
#endif
}
// ============================================================================
// OS Acquisition Implementation
// ============================================================================
@ -94,6 +148,14 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p
(void)size_class; // Used only for logging in debug builds
// Experimental HugePage path (research-only, default OFF)
if (ss_os_huge_enabled()) {
void* huge = ss_os_acquire_hugepage_try(ss_size, ss_mask, populate);
if (huge != NULL) {
return huge;
}
}
#ifdef MAP_ALIGNED_SUPER
int map_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_ALIGNED_SUPER;
#ifdef MAP_POPULATE
@ -107,6 +169,7 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p
-1, 0);
if (ptr != MAP_FAILED) {
atomic_fetch_add(&g_ss_mmap_count, 1);
ss_os_stats_record_alloc();
if (((uintptr_t)ptr & ss_mask) == 0) {
// Successfully got aligned pointer from OS
return ptr;
@ -140,6 +203,7 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p
-1, 0);
if (raw != MAP_FAILED) {
uint64_t count = atomic_fetch_add(&g_ss_mmap_count, 1) + 1;
ss_os_stats_record_alloc();
#if !HAKMEM_BUILD_RELEASE
if (log_count < 10) {
fprintf(stderr, "[SUPERSLAB_MMAP] #%lu: class=%d size=%zu (total SuperSlab mmaps so far)\n",
@ -177,6 +241,7 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p
#ifdef MADV_POPULATE_WRITE
if (populate) {
int ret = madvise(ptr, ss_size, MADV_POPULATE_WRITE);
ss_os_stats_record_madvise();
if (ret != 0) {
// Fallback for kernels that support MADV_POPULATE_WRITE but it fails
// Use explicit page-by-page touching with writes
@ -195,8 +260,25 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p
p[i] = 0;
}
p[ss_size - 1] = 0;
ss_os_stats_record_madvise();
}
#endif
return ptr;
}
static void ss_os_stats_destructor(void) __attribute__((destructor));
static void ss_os_stats_destructor(void) {
if (!ss_os_stats_enabled()) {
return;
}
fprintf(stderr,
"[SS_OS_STATS] alloc=%llu free=%llu madvise=%llu mmap_total=%llu fallback_mmap=%llu huge_alloc=%llu huge_fail=%llu\n",
(unsigned long long)atomic_load_explicit(&g_ss_os_alloc_calls, memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&g_ss_os_free_calls, memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&g_ss_os_madvise_calls, memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&g_ss_mmap_count, memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&g_final_fallback_mmap_count, memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&g_ss_os_huge_alloc_calls, memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&g_ss_os_huge_fail_calls, memory_order_relaxed));
}

View File

@ -18,6 +18,7 @@
#include <stdint.h>
#include <stddef.h>
#include <stdatomic.h>
#include <stdlib.h>
// ============================================================================
// Global Counters (for debugging/diagnostics)
@ -25,6 +26,91 @@
extern _Atomic uint64_t g_ss_mmap_count;
extern _Atomic uint64_t g_final_fallback_mmap_count;
extern _Atomic uint64_t g_ss_os_alloc_calls;
extern _Atomic uint64_t g_ss_os_free_calls;
extern _Atomic uint64_t g_ss_os_madvise_calls;
extern _Atomic uint64_t g_ss_os_huge_alloc_calls;
extern _Atomic uint64_t g_ss_os_huge_fail_calls;
static inline int ss_os_stats_enabled(void) {
static int g_ss_os_stats_enabled = -1;
if (__builtin_expect(g_ss_os_stats_enabled == -1, 0)) {
const char* e = getenv("HAKMEM_SS_OS_STATS");
g_ss_os_stats_enabled = (e && *e && *e != '0') ? 1 : 0;
}
return g_ss_os_stats_enabled;
}
static inline void ss_os_stats_record_alloc(void) {
if (!ss_os_stats_enabled()) {
return;
}
atomic_fetch_add_explicit(&g_ss_os_alloc_calls, 1, memory_order_relaxed);
}
static inline void ss_os_stats_record_free(void) {
if (!ss_os_stats_enabled()) {
return;
}
atomic_fetch_add_explicit(&g_ss_os_free_calls, 1, memory_order_relaxed);
}
static inline void ss_os_stats_record_madvise(void) {
if (!ss_os_stats_enabled()) {
return;
}
atomic_fetch_add_explicit(&g_ss_os_madvise_calls, 1, memory_order_relaxed);
}
// ============================================================================
// HugePage Experiment (research-only)
// ============================================================================
static inline int ss_os_huge_enabled(void) {
static int g_ss_os_huge_enabled = -1;
if (__builtin_expect(g_ss_os_huge_enabled == -1, 0)) {
const char* e = getenv("HAKMEM_SS_HUGEPAGE_EXPERIMENT");
g_ss_os_huge_enabled = (e && *e && *e != '0') ? 1 : 0;
}
return g_ss_os_huge_enabled;
}
// Parse HAKMEM_SS_HUGEPAGE_SIZE (only "2M" supported explicitly; otherwise
// falls back to default 2MB). This is intentionally soft/experimental.
static inline size_t ss_os_huge_size_bytes(void) {
static size_t g_huge_size = 0;
if (__builtin_expect(g_huge_size == 0, 0)) {
const char* e = getenv("HAKMEM_SS_HUGEPAGE_SIZE");
if (e && *e) {
char* end = NULL;
unsigned long long v = strtoull(e, &end, 0);
if (end && (*end == 'M' || *end == 'm')) {
v *= 1024ULL * 1024ULL;
}
if (v > 0) {
g_huge_size = (size_t)v;
}
}
if (g_huge_size == 0) {
g_huge_size = (size_t)(2ULL << 20); // default 2MB
}
}
return g_huge_size;
}
static inline void ss_os_stats_record_huge_alloc(void) {
if (!ss_os_stats_enabled()) {
return;
}
atomic_fetch_add_explicit(&g_ss_os_huge_alloc_calls, 1, memory_order_relaxed);
}
static inline void ss_os_stats_record_huge_fail(void) {
if (!ss_os_stats_enabled()) {
return;
}
atomic_fetch_add_explicit(&g_ss_os_huge_fail_calls, 1, memory_order_relaxed);
}
// ============================================================================
// OS Acquisition API

View File

@ -0,0 +1,42 @@
// tiny_front_stats_box.h - Front class distribution counters (ENV gated)
#pragma once
#include <stdint.h>
#include <stdlib.h>
#include <stdatomic.h>
#include "../hakmem_tiny_config.h"
extern _Atomic uint64_t g_tiny_front_alloc_class[TINY_NUM_CLASSES];
extern _Atomic uint64_t g_tiny_front_free_class[TINY_NUM_CLASSES];
static inline int tiny_front_class_stats_enabled(void) {
static int g = -1;
if (__builtin_expect(g == -1, 0)) {
const char* e = getenv("HAKMEM_TINY_FRONT_CLASS_STATS");
g = (e && *e && *e != '0') ? 1 : 0;
}
return g;
}
static inline int tiny_front_class_stats_dump_enabled(void) {
static int g = -1;
if (__builtin_expect(g == -1, 0)) {
const char* e = getenv("HAKMEM_TINY_FRONT_CLASS_STATS_DUMP");
const char* e2 = getenv("HAKMEM_TINY_FRONT_CLASS_STATS");
g = ((e && *e && *e != '0') || (e2 && *e2 && *e2 != '0')) ? 1 : 0;
}
return g;
}
static inline void tiny_front_alloc_stat_inc(int class_idx) {
if (__builtin_expect(tiny_front_class_stats_enabled(), 0)) {
atomic_fetch_add_explicit(&g_tiny_front_alloc_class[class_idx], 1, memory_order_relaxed);
}
}
static inline void tiny_front_free_stat_inc(int class_idx) {
if (__builtin_expect(tiny_front_class_stats_enabled(), 0)) {
atomic_fetch_add_explicit(&g_tiny_front_free_class[class_idx], 1, memory_order_relaxed);
}
}

View File

@ -264,6 +264,37 @@ static inline tiny_heap_class_t* tiny_heap_class(tiny_heap_ctx_t* ctx, int class
return &ctx->cls[class_idx];
}
static inline int tiny_heap_tls_try_resolve(int class_idx,
void* base,
SuperSlab** out_ss,
int* out_slab_idx,
TinySlabMeta** out_meta) {
if (class_idx < 0 || class_idx >= TINY_NUM_CLASSES) return 0;
TinyTLSSlab* tls = &g_tls_slabs[class_idx];
if (!tls->ss || !tls->slab_base || !tls->meta) return 0;
const size_t stride = (size_t)tiny_stride_for_class(class_idx);
const size_t cap = (size_t)tls->meta->capacity;
if (stride == 0 || cap == 0) return 0;
uint8_t* low = tls->slab_base;
uint8_t* high = low + stride * cap;
if ((uint8_t*)base < low || (uint8_t*)base >= high) {
return 0;
}
if (out_ss) {
*out_ss = tls->ss;
}
if (out_slab_idx) {
*out_slab_idx = (int)tls->slab_idx;
}
if (out_meta) {
*out_meta = tls->meta;
}
return 1;
}
static inline int tiny_heap_page_is_valid(tiny_heap_class_t* hcls, tiny_heap_page_t* page) {
if (!hcls || !page) return 0;
return (page >= hcls->nodes) && (page < (hcls->nodes + TINY_HEAP_MAX_PAGES_PER_CLASS));
@ -630,14 +661,15 @@ static inline void tiny_heap_page_push_to_partial(tiny_heap_class_t* hcls, tiny_
static inline void tiny_heap_page_becomes_empty(tiny_heap_ctx_t* ctx, int class_idx, tiny_heap_page_t* page) {
tiny_heap_class_t* hcls = tiny_heap_class(ctx, class_idx);
if (!hcls || !page) return;
const int mode = tiny_heap_meta_mode_effective(class_idx);
if (tiny_heap_meta_ultra_enabled_for_class(class_idx)) {
// ULTRA: C7 は 1 ページ前提で保持し続ける。publish/unlink/release を避ける。
hcls->current_page = page;
return;
}
if (tiny_heap_meta_light_enabled_for_class(class_idx)) {
// SAFE: delta を反映
if (mode == 1) {
// SAFE: delta を反映C6/C7
if (class_idx == 6) {
tiny_c6_mark_delta_site(page, C6_DELTA_EMPTY);
}
@ -685,18 +717,43 @@ static inline void tiny_heap_page_mark_full(tiny_heap_class_t* hcls, tiny_heap_p
}
static inline void* tiny_heap_page_pop(tiny_heap_class_t* hcls, int class_idx, tiny_heap_page_t* page) {
const int mode = tiny_heap_meta_mode_effective(class_idx);
const int c6_pop_dbg = (class_idx == 6) && tiny_c6_debug_pop_enabled();
if (c6_pop_dbg) {
static _Atomic uint32_t g_pop_dbg = 0;
uint32_t pop_n = atomic_fetch_add_explicit(&g_pop_dbg, 1, memory_order_relaxed);
if (pop_n < 8) {
fprintf(stderr, "[POP_ENTRY] cls=%d page=%p\n", class_idx, (void*)page);
}
}
if (!tiny_heap_page_is_valid(hcls, page)) return NULL;
const int mode = tiny_heap_meta_mode_effective(class_idx);
if (class_idx == 7 && __builtin_expect(mode == 1, 1)) {
if (!page->meta || !page->ss || !page->base) return NULL;
if (class_idx == 6 && mode == 1) {
void* block = NULL;
if (page->free_list) {
block = page->free_list;
void* next = tiny_next_read(class_idx, block);
page->free_list = next;
atomic_store_explicit(&page->meta->freelist, next, memory_order_release);
} else {
if (page->used >= page->capacity) {
return NULL;
}
size_t stride = hcls->stride;
if (stride == 0) {
stride = tiny_heap_block_stride(class_idx);
hcls->stride = (uint16_t)stride;
}
block = (void*)(page->base + ((size_t)page->used * stride));
if (page->meta->carved < page->capacity) {
page->meta->carved++;
}
}
page->used++;
page->used_delta++;
page->active_delta++;
if (tiny_heap_delta_should_flush(class_idx, page)) {
tiny_heap_meta_flush_page(class_idx, page);
}
return tiny_region_id_write_header(block, class_idx);
}
const int c6_pop_dbg = (class_idx == 6) && tiny_c6_debug_pop_enabled();
if (!page->meta || !page->ss || !page->base) return NULL;
if (c6_pop_dbg && mode == 1) {
int fail = 0;
const char* reason = NULL;
SuperSlab* ss_chk = hak_super_lookup(page->base);
@ -1134,7 +1191,15 @@ static inline void tiny_heap_free_class_fast(tiny_heap_ctx_t* ctx, int class_idx
#else
void* base = (void*)((uint8_t*)ptr - 1);
#endif
SuperSlab* ss = hak_super_lookup(base);
SuperSlab* ss = NULL;
int slab_idx = -1;
if (class_idx == 7 || class_idx == 6) {
if (!tiny_heap_tls_try_resolve(class_idx, base, &ss, &slab_idx, NULL)) {
ss = NULL;
}
}
if (!ss) {
ss = hak_super_lookup(base);
if (!ss || ss->magic != SUPERSLAB_MAGIC) {
TinyHeapClassStats* stats = tiny_heap_stats_for_class(class_idx);
if (__builtin_expect(stats != NULL, 0)) {
@ -1143,7 +1208,10 @@ static inline void tiny_heap_free_class_fast(tiny_heap_ctx_t* ctx, int class_idx
tiny_heap_cold_drain_and_free(class_idx, base);
return;
}
int slab_idx = slab_index_for(ss, base);
}
if (slab_idx < 0) {
slab_idx = slab_index_for(ss, base);
}
if (slab_idx < 0 || slab_idx >= ss_slabs_capacity(ss)) {
TinyHeapClassStats* stats = tiny_heap_stats_for_class(class_idx);
if (__builtin_expect(stats != NULL, 0)) {

View File

@ -4,14 +4,90 @@
// - デフォルト OFF環境変数が未設定または 0 のとき)。
#pragma once
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "c7_hotpath_env_box.h" // tiny_c7_hot_enabled()
// TinyHeap profile modes (HAKMEM_TINY_HEAP_PROFILE)
enum {
TINY_HEAP_PROFILE_LEGACY = 0, // TinyHeap OFF / 既存 front
TINY_HEAP_PROFILE_C7_SAFE = 1, // C7 TinyHeap + SAFE meta
TINY_HEAP_PROFILE_C7_ULTRA_BENCH = 2, // C7 TinyHeap + ULTRA (bench 専用)
TINY_HEAP_PROFILE_CUSTOM = 3, // クラス/メタを ENV で直接指定
};
// ENV: HAKMEM_TINY_HEAP_PROFILE
static inline int tiny_heap_profile_mode(void) {
static int g_mode = -1;
if (__builtin_expect(g_mode == -1, 0)) {
const char* e = getenv("HAKMEM_TINY_HEAP_PROFILE");
if (!e || !*e) {
g_mode = TINY_HEAP_PROFILE_LEGACY;
} else if (strcasecmp(e, "C7_SAFE") == 0) {
g_mode = TINY_HEAP_PROFILE_C7_SAFE;
} else if (strcasecmp(e, "C7_ULTRA_BENCH") == 0 || strcasecmp(e, "C7_ULTRA") == 0) {
g_mode = TINY_HEAP_PROFILE_C7_ULTRA_BENCH;
} else if (strcasecmp(e, "CUSTOM") == 0) {
g_mode = TINY_HEAP_PROFILE_CUSTOM;
} else if (strcasecmp(e, "LEGACY") == 0) {
g_mode = TINY_HEAP_PROFILE_LEGACY;
} else {
int v = atoi(e);
if (v < 0) v = 0;
if (v > 3) v = 3;
g_mode = v;
}
}
return g_mode;
}
static inline unsigned tiny_heap_profile_default_class_mask(int profile_mode) {
switch (profile_mode) {
case TINY_HEAP_PROFILE_C7_SAFE:
case TINY_HEAP_PROFILE_C7_ULTRA_BENCH:
return 1u << 7; // C7 のみ
case TINY_HEAP_PROFILE_CUSTOM:
case TINY_HEAP_PROFILE_LEGACY:
default:
return 0u;
}
}
static inline int tiny_heap_profile_default_c7_meta_mode(int profile_mode) {
switch (profile_mode) {
case TINY_HEAP_PROFILE_C7_SAFE:
return 1;
case TINY_HEAP_PROFILE_C7_ULTRA_BENCH:
return 2;
case TINY_HEAP_PROFILE_CUSTOM:
case TINY_HEAP_PROFILE_LEGACY:
default:
return 0;
}
}
// ENV: HAKMEM_TINY_HEAP_BOX=1 で TinyHeap front を有効化
static inline int tiny_heap_box_enabled(void) {
static int g_enable = -1;
if (__builtin_expect(g_enable == -1, 0)) {
const char* e = getenv("HAKMEM_TINY_HEAP_BOX");
if (e && *e) {
g_enable = (*e != '0') ? 1 : 0;
} else {
// profile が LEGACY 以外なら自動で ON
g_enable = (tiny_heap_profile_mode() != TINY_HEAP_PROFILE_LEGACY) ? 1 : 0;
}
}
return g_enable;
}
// ENV gate: C6 Hot front (直線パス) を有効化するか
static inline int tiny_c6_hot_enabled(void) {
static int g_enable = -1;
if (__builtin_expect(g_enable == -1, 0)) {
const char* e = getenv("HAKMEM_TINY_C6_HOT");
g_enable = (e && *e && *e != '0') ? 1 : 0;
}
return g_enable;
@ -30,12 +106,12 @@ static inline int tiny_heap_class_enabled(int class_idx) {
unsigned v = (unsigned)strtoul(e, NULL, 0);
g_mask = v & 0xFFu;
} else {
// デフォルト: C7 のみ
g_mask = 1u << 7;
g_mask = tiny_heap_profile_default_class_mask(tiny_heap_profile_mode());
}
g_parsed = 1;
}
if (!tiny_heap_box_enabled()) return 0;
if (class_idx < 0 || class_idx >= TINY_NUM_CLASSES) return 0;
return (g_mask & (1u << class_idx)) != 0;
}
@ -45,6 +121,9 @@ static inline int tiny_heap_class_route_enabled(int class_idx) {
if (class_idx == 7) {
return tiny_heap_box_enabled() && tiny_c7_hot_enabled() && tiny_heap_class_enabled(class_idx);
}
if (class_idx == 6) {
return tiny_heap_box_enabled() && tiny_c6_hot_enabled() && tiny_heap_class_enabled(class_idx);
}
return tiny_heap_box_enabled() && tiny_heap_class_enabled(class_idx);
}
@ -52,3 +131,43 @@ static inline int tiny_heap_class_route_enabled(int class_idx) {
static inline int tiny_c7_heap_mode_enabled(void) {
return tiny_heap_class_route_enabled(7);
}
// ---------------------------------------------------------------------------
// TinyHotHeap v2 (未配線、Phase30: skeleton 用)
// ---------------------------------------------------------------------------
// ENV: HAKMEM_TINY_HOTHEAP_V2=1 で v2 Box を有効化(デフォルト OFF
static inline int tiny_hotheap_v2_enabled(void) {
static int g_enable = -1;
if (__builtin_expect(g_enable == -1, 0)) {
const char* e = getenv("HAKMEM_TINY_HOTHEAP_V2");
g_enable = (e && *e && *e != '0') ? 1 : 0;
}
return g_enable;
}
// ENV: HAKMEM_TINY_HOTHEAP_CLASSES (bitmask) - v2 用クラスマスク(デフォルト 0 = 全 OFF
static inline int tiny_hotheap_v2_class_enabled(uint8_t class_idx) {
static int g_parsed = 0;
static unsigned g_mask = 0;
if (__builtin_expect(!g_parsed, 0)) {
const char* e = getenv("HAKMEM_TINY_HOTHEAP_CLASSES");
if (e && *e) {
unsigned v = (unsigned)strtoul(e, NULL, 0);
g_mask = v & 0xFFu;
} else {
g_mask = 0; // デフォルトは全 OFFv2 は明示的に ON にする)
}
g_parsed = 1;
}
if (!tiny_hotheap_v2_enabled()) return 0;
if (class_idx >= TINY_NUM_CLASSES) return 0;
return (g_mask & (1u << class_idx)) != 0;
}
// C7 用 v2 gateC7 front からのみ参照)
static inline int tiny_c7_hotheap_v2_enabled(void) {
return tiny_hotheap_v2_class_enabled(7);
}

View File

@ -2,6 +2,9 @@
// 役割:
// - TinyHotHeap v2 の型と API を先行定義するが、現行経路には配線しない。
// - HAKMEM_TINY_HOTHEAP_V2 / HAKMEM_TINY_HOTHEAP_CLASSES で将来の A/B に備える。
// メモ (Phase35):
// - 現状の v2 は C7-only でも v1 より遅く、mixed では大きな回帰が確認されている。
// - 研究用/実験用フラグを明示的に立てたときだけ使用し、デフォルトは OFF とする。
#pragma once
#include <stdint.h>
@ -20,11 +23,11 @@ typedef struct tiny_hotheap_page_v2 {
uint16_t used;
uint16_t capacity;
uint16_t slab_idx;
uint8_t _pad;
uint16_t flags; // future use (HOT/PARTIAL/FULL)
void* base;
struct TinySlabMeta* meta; // Superslab slab meta
struct SuperSlab* ss; // Owning Superslab (meta/ss_active の整合は v1 が保持)
struct tiny_heap_page_t* lease_page; // v1 側の page 構造体freelist/used の正は常に lease_page
struct TinySlabMeta* meta; // Superslab slab metaCold 側は v1 が保持)
struct SuperSlab* ss; // Owning SuperslabCold 側は v1 に委譲)
struct tiny_heap_page_t* lease_page; // v1 側の page 構造体freelist/used lease_page と同期させる
struct tiny_hotheap_page_v2* next;
} tiny_hotheap_page_v2;
@ -34,7 +37,7 @@ typedef struct tiny_hotheap_class_v2 {
tiny_hotheap_page_v2* full_pages;
uint16_t stride;
uint16_t _pad;
tiny_hotheap_page_v2 storage_page; // C7 専用の 1 枚だけをまず保持
tiny_hotheap_page_v2 storage_page; // C7 専用の 1 枚だけをまず保持Phase36: reuse when空き
} tiny_hotheap_class_v2;
typedef struct tiny_hotheap_ctx_v2 {
@ -48,6 +51,28 @@ extern __thread tiny_hotheap_ctx_v2* g_tiny_hotheap_ctx_v2;
tiny_hotheap_ctx_v2* tiny_hotheap_v2_tls_get(void);
void* tiny_hotheap_v2_alloc(uint8_t class_idx);
void tiny_hotheap_v2_free(uint8_t class_idx, void* p, void* meta);
void tiny_hotheap_v2_record_route_fallback(void);
void tiny_hotheap_v2_record_free_fallback(void);
typedef struct tiny_hotheap_v2_stats_snapshot {
uint64_t route_hits;
uint64_t alloc_calls;
uint64_t alloc_fast;
uint64_t alloc_lease;
uint64_t alloc_refill;
uint64_t alloc_fallback_v1;
uint64_t alloc_route_fb;
uint64_t free_calls;
uint64_t free_fast;
uint64_t free_fallback_v1;
uint64_t prepare_calls;
uint64_t prepare_with_current_null;
uint64_t prepare_from_partial;
uint64_t free_made_current;
uint64_t page_retired;
} tiny_hotheap_v2_stats_snapshot_t;
void tiny_hotheap_v2_debug_snapshot(tiny_hotheap_v2_stats_snapshot_t* out);
static inline void tiny_hotheap_v2_page_reset(tiny_hotheap_page_v2* page) {
if (!page) return;
@ -55,6 +80,7 @@ static inline void tiny_hotheap_v2_page_reset(tiny_hotheap_page_v2* page) {
page->used = 0;
page->capacity = 0;
page->slab_idx = 0;
page->flags = 0;
page->base = NULL;
page->meta = NULL;
page->ss = NULL;

View File

@ -0,0 +1,56 @@
// tiny_route_env_box.h - Route snapshot for Tiny front (Heap vs Legacy)
// 役割:
// - 起動時に「各クラスが TinyHeap を使うか」をスナップショットし、ホットパスでは LUT 1 回に縮約する。
// - HAKMEM_TINY_HEAP_PROFILE / HAKMEM_TINY_HEAP_BOX / HAKMEM_TINY_HEAP_CLASSES の組合せをここで解決する。
#pragma once
#include <stdint.h>
#include <stdlib.h>
#include "../hakmem_tiny_config.h"
#include "tiny_heap_env_box.h"
typedef enum {
TINY_ROUTE_LEGACY = 0,
TINY_ROUTE_HEAP = 1, // TinyHeap v1
TINY_ROUTE_HOTHEAP_V2 = 2, // TinyHotHeap v2
} tiny_route_kind_t;
extern tiny_route_kind_t g_tiny_route_class[TINY_NUM_CLASSES];
extern int g_tiny_route_snapshot_done;
static inline void tiny_route_snapshot_init(void) {
for (int i = 0; i < TINY_NUM_CLASSES; i++) {
if (tiny_hotheap_v2_class_enabled((uint8_t)i)) {
g_tiny_route_class[i] = TINY_ROUTE_HOTHEAP_V2;
} else if (tiny_heap_box_enabled() && tiny_heap_class_route_enabled(i)) {
g_tiny_route_class[i] = TINY_ROUTE_HEAP;
} else {
g_tiny_route_class[i] = TINY_ROUTE_LEGACY;
}
}
g_tiny_route_snapshot_done = 1;
}
static inline tiny_route_kind_t tiny_route_for_class(uint8_t ci) {
if (__builtin_expect(!g_tiny_route_snapshot_done, 0)) {
tiny_route_snapshot_init();
}
if (__builtin_expect(ci >= TINY_NUM_CLASSES, 0)) {
return TINY_ROUTE_LEGACY;
}
return g_tiny_route_class[ci];
}
static inline int tiny_route_is_heap_kind(tiny_route_kind_t route) {
return route == TINY_ROUTE_HEAP || route == TINY_ROUTE_HOTHEAP_V2;
}
// C7 front が TinyHeap を使うかRoute snapshot 経由で判定)
static inline int tiny_c7_front_uses_heap(void) {
return tiny_route_is_heap_kind(tiny_route_for_class(7));
}
// C6 front が TinyHeap を使うかRoute snapshot 経由で判定)
static inline int tiny_c6_front_uses_heap(void) {
return tiny_route_is_heap_kind(tiny_route_for_class(6));
}

134
core/box/tiny_stats_box.h Normal file
View File

@ -0,0 +1,134 @@
// tiny_stats_box.h - Cold Tiny Stats Box
// 役割:
// - TinyHeap/TinyFront のホットパスから meta->used / ss_active_* 更新を切り出す箱。
// - C7 SAFE の delta flush を「即時適用 or バッチ適用」に切り替える足場。
#pragma once
#include <stdint.h>
#include <stdlib.h>
#include <stdatomic.h>
// 前方宣言(利用側で tiny_heap_page_t を定義済みであることが前提)
typedef struct tiny_heap_page_t tiny_heap_page_t;
typedef struct SuperSlab SuperSlab;
// ENV: HAKMEM_TINY_STATS_BOX=1 で Stats Box 経由の更新を有効化
static inline int tiny_stats_box_enabled(void) {
static int g = -1;
if (__builtin_expect(g == -1, 0)) {
const char* e = getenv("HAKMEM_TINY_STATS_BOX");
g = (e && *e && *e != '0') ? 1 : 0;
}
return g;
}
// ENV: HAKMEM_TINY_STATS_BATCH=1 で meta/active をバッチ適用pending に貯める)
static inline int tiny_stats_batch_enabled(void) {
static int g = -1;
if (__builtin_expect(g == -1, 0)) {
const char* e = getenv("HAKMEM_TINY_STATS_BATCH");
g = (e && *e && *e != '0') ? 1 : 0;
}
return g;
}
typedef struct {
uint32_t alloc_delta;
uint32_t free_delta;
} tiny_stats_accum_t;
// C7 SAFE の pending を扱うための薄い構造体page 内に埋め込まれる想定)
typedef struct {
int32_t used_delta;
int32_t active_delta;
} tiny_stats_pending_t;
// Hot からのイベントを受け取るフック(現状は no-op
static inline void tiny_stats_on_alloc(int class_idx, tiny_heap_page_t* page) {
(void)class_idx;
(void)page;
if (!tiny_stats_box_enabled()) return;
// TODO: ここにクラス別の軽量カウンタを積む余地を残す
}
static inline void tiny_stats_on_free(int class_idx, tiny_heap_page_t* page) {
(void)class_idx;
(void)page;
if (!tiny_stats_box_enabled()) return;
// TODO: ここにクラス別の軽量カウンタを積む余地を残す
}
// Superslab active counters既存 Box の関数をここから呼ぶ)
void ss_active_add(SuperSlab* ss, uint32_t n);
void ss_active_dec_one(SuperSlab* ss);
static inline void tiny_stats_apply_to_meta(tiny_heap_page_t* page, int32_t used_delta, int32_t active_delta) {
if (!page || !page->meta || !page->ss) return;
if (used_delta != 0) {
atomic_fetch_add_explicit(&page->meta->used, used_delta, memory_order_relaxed);
}
if (active_delta != 0) {
if (active_delta > 0) {
ss_active_add(page->ss, (uint32_t)active_delta);
} else {
int32_t n = -active_delta;
for (int32_t i = 0; i < n; i++) {
ss_active_dec_one(page->ss);
}
}
}
}
// バッチの flush 判定force=1 で無条件 flush
static inline void tiny_stats_maybe_flush_for_page(int class_idx, tiny_heap_page_t* page, int force) {
if (!tiny_stats_box_enabled()) return;
if (!tiny_stats_batch_enabled()) return;
if (!page) return;
(void)class_idx;
int32_t ud = page->stats_used_pending;
int32_t ad = page->stats_active_pending;
int32_t abs_ud = (ud >= 0) ? ud : -ud;
int32_t abs_ad = (ad >= 0) ? ad : -ad;
int32_t abs_max = (abs_ud > abs_ad) ? abs_ud : abs_ad;
uint16_t cap = page->capacity;
int32_t th = (cap > 0) ? ((int32_t)cap * 16) : 256;
if (th < 256) th = 256;
if (!force && abs_max < th) {
return;
}
if (ud != 0 || ad != 0) {
tiny_stats_apply_to_meta(page, ud, ad);
page->stats_used_pending = 0;
page->stats_active_pending = 0;
}
}
// flush: delta を Cold Stats 側に渡す(即時 or pending
static inline void tiny_stats_flush_for_page(int class_idx, tiny_heap_page_t* page, int32_t used_delta, int32_t active_delta) {
if (!page) return;
if (!tiny_stats_box_enabled()) {
tiny_stats_apply_to_meta(page, used_delta, active_delta);
return;
}
if (!page->meta || !page->ss) return;
if (used_delta == 0 && active_delta == 0) {
// すでに pending があれば empty 時に flush される
if (tiny_stats_batch_enabled()) {
tiny_stats_maybe_flush_for_page(class_idx, page, page->used == 0);
}
return;
}
if (!tiny_stats_batch_enabled()) {
tiny_stats_apply_to_meta(page, used_delta, active_delta);
return;
}
page->stats_used_pending += used_delta;
page->stats_active_pending += active_delta;
tiny_stats_maybe_flush_for_page(class_idx, page, page->used == 0);
}

View File

@ -24,6 +24,7 @@
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdatomic.h>
#include <pthread.h> // For pthread_self() in cross-thread check
#include "../hakmem_build_flags.h"
#include "../hakmem_tiny_config.h" // For TINY_NUM_CLASSES
@ -38,7 +39,10 @@
#include "../box/tiny_front_cold_box.h" // Phase 4-Step2: Cold Path Box
#include "../box/tiny_c7_hotbox.h" // Optional: C7 専用ホットボックス
#include "../box/tiny_heap_box.h" // TinyHeap 汎用 Box
#include "../box/tiny_hotheap_v2_box.h" // TinyHotHeap v2 (Phase31 A/B)
#include "../box/tiny_heap_env_box.h" // ENV gate for TinyHeap front (A/B)
#include "../box/tiny_route_env_box.h" // Route snapshot (Heap vs Legacy)
#include "../box/tiny_front_stats_box.h" // Front class distribution counters
// Helper: current thread id (low 32 bits) for owner check
#ifndef TINY_SELF_U32_LOCAL_DEFINED
@ -98,31 +102,47 @@ static inline int front_gate_unified_enabled(void) {
//
__attribute__((always_inline))
static inline void* malloc_tiny_fast(size_t size) {
// 1. size → class_idx (inline table lookup, 1-2 instructions)
// size → class_idx を 1 回だけ決定
int class_idx = hak_tiny_size_to_class(size);
// Optional: TinyHeap frontENV: HAKMEM_TINY_HEAP_BOX=1 + HAKMEM_TINY_HEAP_CLASSES bitmask
const int use_tiny_heap = (class_idx == 7)
? tiny_c7_heap_mode_enabled()
: tiny_heap_class_route_enabled(class_idx);
if (__builtin_expect(use_tiny_heap, 0)) {
tiny_heap_ctx_t* ctx = tiny_heap_ctx_for_thread();
if (class_idx == 7 && size == 1024) {
return tiny_c7_alloc_fast(size);
if (__builtin_expect(class_idx < 0 || class_idx >= TINY_NUM_CLASSES, 0)) {
return NULL;
}
return tiny_heap_alloc_class_fast(ctx, class_idx, size);
tiny_front_alloc_stat_inc(class_idx);
tiny_route_kind_t route = tiny_route_for_class((uint8_t)class_idx);
switch (route) {
case TINY_ROUTE_HOTHEAP_V2: {
if (class_idx == 7) {
void* v2p = tiny_hotheap_v2_alloc(7);
if (TINY_HOT_LIKELY(v2p != NULL)) {
return v2p;
}
tiny_hotheap_v2_record_route_fallback();
}
// fallthrough to TinyHeap v1
}
case TINY_ROUTE_HEAP: {
void* heap_ptr = NULL;
if (class_idx == 7) {
heap_ptr = tiny_c7_alloc_fast(size);
} else {
heap_ptr = tiny_heap_alloc_class_fast(tiny_heap_ctx_for_thread(), class_idx, size);
}
if (heap_ptr) {
return heap_ptr;
}
break;
}
case TINY_ROUTE_LEGACY:
default:
break;
}
// 2. Phase 4-Step2: Hot/Cold Path Box
// Try hot path first (cache hit, 1 branch)
// Legacy Tiny front
void* ptr = tiny_hot_alloc_fast(class_idx);
if (TINY_HOT_LIKELY(ptr != NULL)) {
// Hot path: Cache hit → return USER pointer
return ptr;
}
// 3. Cold path: Cache miss → refill + alloc
// noinline, cold attribute keeps this code out of hot path
return tiny_cold_refill_and_alloc(class_idx);
}
@ -141,7 +161,7 @@ __attribute__((always_inline))
static inline int free_tiny_fast(void* ptr) {
if (__builtin_expect(!ptr, 0)) return 0;
#if HAKMEM_TINY_HEADER_CLASSIDX
#if HAKMEM_TINY_HEADER_CLASSIDX
// 1. ページ境界ガード:
// ptr がページ先頭 (offset==0) の場合、ptr-1 は別ページか未マップ領域になる可能性がある。
// その場合はヘッダ読みを行わず、通常 free 経路にフォールバックする。
@ -169,6 +189,9 @@ static inline int free_tiny_fast(void* ptr) {
// 4. BASE を計算して Unified Cache に push
void* base = (void*)((char*)ptr - 1);
tiny_front_free_stat_inc(class_idx);
tiny_route_kind_t route = tiny_route_for_class((uint8_t)class_idx);
const int use_tiny_heap = tiny_route_is_heap_kind(route);
// TWO-SPEED: SuperSlab registration check is DEBUG-ONLY to keep HOT PATH fast.
// In Release builds, we trust header magic (0xA0) as sufficient validation.
@ -192,9 +215,6 @@ static inline int free_tiny_fast(void* ptr) {
#endif
}
const int use_tiny_heap = (class_idx == 7)
? tiny_c7_heap_mode_enabled()
: tiny_heap_class_route_enabled(class_idx);
if (__builtin_expect(g_larson_fix || use_tiny_heap, 0)) {
// Phase 12 optimization: Use fast mask-based lookup (~5-10 cycles vs 50-100)
SuperSlab* ss = ss_fast_lookup(base);
@ -203,6 +223,7 @@ static inline int free_tiny_fast(void* ptr) {
if (__builtin_expect(slab_idx >= 0 && slab_idx < ss_slabs_capacity(ss), 1)) {
uint32_t self_tid = tiny_self_u32_local();
uint8_t owner_tid_low = ss_slab_meta_owner_tid_low_get(ss, slab_idx);
TinySlabMeta* meta = &ss->slabs[slab_idx];
// LARSON FIX: Use bits 8-15 for comparison (pthread TIDs aligned to 256 bytes)
uint8_t self_tid_cmp = (uint8_t)((self_tid >> 8) & 0xFFu);
#if !HAKMEM_BUILD_RELEASE
@ -226,12 +247,18 @@ static inline int free_tiny_fast(void* ptr) {
fflush(stderr);
}
#endif
TinySlabMeta* meta = &ss->slabs[slab_idx];
if (tiny_free_remote_box(ss, slab_idx, meta, ptr, self_tid)) {
return 1; // handled via remote queue
}
return 0; // remote push failed; fall back to normal path
} else if (__builtin_expect(use_tiny_heap, 0)) {
}
// Same-thread + TinyHeap route → route-based free
if (__builtin_expect(use_tiny_heap, 0)) {
switch (route) {
case TINY_ROUTE_HOTHEAP_V2:
tiny_hotheap_v2_free((uint8_t)class_idx, base, meta);
return 1;
case TINY_ROUTE_HEAP: {
tiny_heap_ctx_t* ctx = tiny_heap_ctx_for_thread();
if (class_idx == 7) {
tiny_c7_free_fast_with_meta(ss, slab_idx, base);
@ -240,10 +267,17 @@ static inline int free_tiny_fast(void* ptr) {
}
return 1;
}
default:
break;
}
}
}
}
if (use_tiny_heap) {
// fallback: lookup failed but TinyHeap front is ON → use generic TinyHeap free
if (route == TINY_ROUTE_HOTHEAP_V2) {
tiny_hotheap_v2_record_free_fallback();
}
tiny_heap_free_class_fast(tiny_heap_ctx_for_thread(), class_idx, ptr);
return 1;
}

View File

@ -147,11 +147,331 @@ static void tiny_c7_delta_debug_destructor(void) {
// =============================================================================
// TinyHotHeap v2 (Phase30/31 wiring). Currently C7-only thin wrapper.
// NOTE: Phase34/35 時点では v2 は C7-only でも v1 より遅く、mixed では大きな回帰がある。
// 実験用フラグを明示 ON にしたときだけ使う前提で、デフォルトは v1 を推奨。
// =============================================================================
static _Atomic uint64_t g_tiny_hotheap_v2_c7_alloc = 0;
static _Atomic uint64_t g_tiny_hotheap_v2_c7_free = 0;
static _Atomic uint64_t g_tiny_hotheap_v2_c7_alloc_fallback = 0;
static _Atomic uint64_t g_tiny_hotheap_v2_c7_free_fallback = 0;
static inline int tiny_hotheap_v2_stats_enabled(void) {
static int g = -1;
if (__builtin_expect(g == -1, 0)) {
const char* e = getenv("HAKMEM_TINY_HOTHEAP_V2_STATS");
g = (e && *e && *e != '0') ? 1 : 0;
}
return g;
}
static _Atomic uint64_t g_tiny_hotheap_v2_c7_alloc_calls = 0;
static _Atomic uint64_t g_tiny_hotheap_v2_c7_route_hits = 0;
static _Atomic uint64_t g_tiny_hotheap_v2_c7_alloc_fast = 0;
static _Atomic uint64_t g_tiny_hotheap_v2_c7_alloc_lease = 0;
static _Atomic uint64_t g_tiny_hotheap_v2_c7_alloc_fallback_v1 = 0;
static _Atomic uint64_t g_tiny_hotheap_v2_c7_alloc_refill = 0;
static _Atomic uint64_t g_tiny_hotheap_v2_c7_alloc_route_fb = 0;
static _Atomic uint64_t g_tiny_hotheap_v2_c7_free_calls = 0;
static _Atomic uint64_t g_tiny_hotheap_v2_c7_free_fast = 0;
static _Atomic uint64_t g_tiny_hotheap_v2_c7_free_fallback_v1 = 0;
typedef struct {
_Atomic uint64_t prepare_calls;
_Atomic uint64_t prepare_with_current_null;
_Atomic uint64_t prepare_from_partial;
_Atomic uint64_t free_made_current;
_Atomic uint64_t page_retired;
} TinyHotHeapV2PageStats;
static TinyHotHeapV2PageStats g_tiny_hotheap_v2_page_stats = {0};
void tiny_hotheap_v2_record_route_fallback(void) {
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_route_fb, 1, memory_order_relaxed);
}
void tiny_hotheap_v2_record_free_fallback(void) {
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_free_fallback_v1, 1, memory_order_relaxed);
}
void tiny_hotheap_v2_debug_snapshot(tiny_hotheap_v2_stats_snapshot_t* out) {
if (!out) return;
memset(out, 0, sizeof(*out));
out->route_hits = atomic_load_explicit(&g_tiny_hotheap_v2_c7_route_hits, memory_order_relaxed);
out->alloc_calls = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_calls, memory_order_relaxed);
out->alloc_fast = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_fast, memory_order_relaxed);
out->alloc_lease = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_lease, memory_order_relaxed);
out->alloc_refill = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_refill, memory_order_relaxed);
out->alloc_fallback_v1 = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_fallback_v1, memory_order_relaxed);
out->alloc_route_fb = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_route_fb, memory_order_relaxed);
out->free_calls = atomic_load_explicit(&g_tiny_hotheap_v2_c7_free_calls, memory_order_relaxed);
out->free_fast = atomic_load_explicit(&g_tiny_hotheap_v2_c7_free_fast, memory_order_relaxed);
out->free_fallback_v1 = atomic_load_explicit(&g_tiny_hotheap_v2_c7_free_fallback_v1, memory_order_relaxed);
out->prepare_calls = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.prepare_calls, memory_order_relaxed);
out->prepare_with_current_null = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.prepare_with_current_null, memory_order_relaxed);
out->prepare_from_partial = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.prepare_from_partial, memory_order_relaxed);
out->free_made_current = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.free_made_current, memory_order_relaxed);
out->page_retired = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.page_retired, memory_order_relaxed);
}
static tiny_hotheap_page_v2* tiny_hotheap_v2_acquire_page_node(tiny_hotheap_class_v2* hc) {
if (!hc) return NULL;
if (hc->storage_page.meta == NULL && hc->storage_page.freelist == NULL &&
hc->storage_page.capacity == 0) {
tiny_hotheap_v2_page_reset(&hc->storage_page);
return &hc->storage_page;
}
tiny_hotheap_page_v2* node = (tiny_hotheap_page_v2*)calloc(1, sizeof(tiny_hotheap_page_v2));
if (!node) {
return NULL;
}
tiny_hotheap_v2_page_reset(node);
return node;
}
static tiny_hotheap_page_v2* tiny_hotheap_v2_find_page(tiny_hotheap_class_v2* hc,
uint8_t class_idx,
void* p,
TinySlabMeta* meta) {
if (!hc || !p) return NULL;
const size_t stride = hc->stride ? hc->stride : tiny_stride_for_class(class_idx);
const size_t max_span = stride * (size_t)(hc->current_page ? hc->current_page->capacity : 0);
tiny_hotheap_page_v2* candidates[3] = {hc->current_page, hc->partial_pages, hc->full_pages};
for (int i = 0; i < 3; i++) {
for (tiny_hotheap_page_v2* page = candidates[i]; page; page = page->next) {
if (meta && page->meta && page->meta != meta) continue;
if (!page->base || page->capacity == 0) continue;
uint8_t* base = (uint8_t*)page->base;
size_t span = stride * (size_t)page->capacity;
if ((uint8_t*)p >= base && (uint8_t*)p < base + span) {
(void)max_span; // silence unused warning in case stride==0
return page;
}
}
}
return NULL;
}
static inline void tiny_hotheap_v2_build_freelist(tiny_hotheap_page_v2* page,
uint8_t class_idx,
uint16_t stride) {
if (!page || stride == 0) {
return;
}
if (page->used >= page->capacity) {
page->freelist = NULL;
return;
}
void* head = NULL;
size_t start = page->capacity;
while (start > page->used) {
start--;
uint8_t* block = (uint8_t*)page->base + (start * (size_t)stride);
tiny_next_write(class_idx, block, head);
head = block;
}
page->freelist = head;
if (page->lease_page) {
page->lease_page->free_list = head;
page->lease_page->used = page->used;
if (page->lease_page->meta) {
atomic_store_explicit(&page->lease_page->meta->freelist, head, memory_order_release);
if (page->lease_page->meta->carved < page->capacity) {
page->lease_page->meta->carved = page->capacity;
}
}
}
}
static void tiny_hotheap_v2_unlink_page(tiny_hotheap_class_v2* hc, tiny_hotheap_page_v2* target) {
if (!hc || !target) return;
if (hc->current_page == target) {
hc->current_page = NULL;
}
tiny_hotheap_page_v2** lists[2] = {&hc->partial_pages, &hc->full_pages};
for (int i = 0; i < 2; i++) {
tiny_hotheap_page_v2** head = lists[i];
tiny_hotheap_page_v2* prev = NULL;
tiny_hotheap_page_v2* cur = *head;
while (cur) {
if (cur == target) {
if (prev) {
prev->next = cur->next;
} else {
*head = cur->next;
}
cur->next = NULL;
break;
}
prev = cur;
cur = cur->next;
}
}
}
static tiny_hotheap_page_v2* tiny_hotheap_v2_refill_slow(tiny_hotheap_ctx_v2* ctx, uint8_t class_idx) {
if (!ctx || class_idx != 7) {
return NULL;
}
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_refill, 1, memory_order_relaxed);
TinyHeapClassStats* stats = tiny_heap_stats_for_class(7);
if (__builtin_expect(stats != NULL, 0)) {
atomic_fetch_add_explicit(&stats->alloc_slow_prepare, 1, memory_order_relaxed);
}
tiny_hotheap_class_v2* hc = &ctx->cls[class_idx];
TinyHeapPageLease lease = tiny_heap_c7_lease_page_for_v2();
if (!lease.page) {
return NULL;
}
if (hc->stride == 0) {
hc->stride = (uint16_t)tiny_stride_for_class(class_idx);
}
tiny_hotheap_page_v2* page = tiny_hotheap_v2_acquire_page_node(hc);
if (!page) {
return NULL;
}
page->lease_page = lease.page;
page->meta = lease.meta;
page->ss = lease.ss;
page->base = lease.base;
page->capacity = lease.capacity;
page->slab_idx = lease.slab_idx;
page->freelist = lease.freelist;
page->used = lease.page->used;
if (page->lease_page) {
page->lease_page->capacity = page->capacity;
page->lease_page->free_list = page->freelist;
page->lease_page->base = (uint8_t*)page->base;
}
const uint16_t stride = hc->stride ? hc->stride : (uint16_t)tiny_stride_for_class(class_idx);
if (page->freelist == NULL && page->base && page->capacity > page->used) {
tiny_hotheap_v2_build_freelist(page, class_idx, stride);
} else if (page->lease_page && page->lease_page->meta) {
atomic_store_explicit(&page->lease_page->meta->freelist, page->freelist, memory_order_release);
}
tiny_hotheap_page_v2* old_cur = hc->current_page;
hc->current_page = page;
page->next = NULL;
if (old_cur && old_cur != page) {
old_cur->next = hc->partial_pages;
hc->partial_pages = old_cur;
}
if (!hc->current_page || !hc->current_page->freelist || hc->current_page->capacity == 0 ||
hc->current_page->used > hc->current_page->capacity) {
fprintf(stderr, "[HOTHEAP_V2_REFILL_ASSERT] current_page missing freelist (page=%p freelist=%p cap=%u used=%u)\n",
(void*)hc->current_page,
hc->current_page ? hc->current_page->freelist : NULL,
hc->current_page ? (unsigned)hc->current_page->capacity : 0u,
hc->current_page ? (unsigned)hc->current_page->used : 0u);
abort();
}
return hc->current_page;
}
static void tiny_hotheap_v2_page_retire_slow(tiny_hotheap_ctx_v2* ctx,
uint8_t class_idx,
tiny_hotheap_page_v2* page) {
if (!ctx || !page) return;
tiny_hotheap_class_v2* hc = &ctx->cls[class_idx];
tiny_hotheap_v2_unlink_page(hc, page);
TinyHeapPageLease lease = tiny_heap_page_lease_nil();
lease.page = page->lease_page;
lease.meta = page->meta;
lease.ss = page->ss;
lease.base = page->base;
lease.capacity = page->capacity;
lease.slab_idx = page->slab_idx;
lease.freelist = page->freelist;
tiny_heap_c7_return_page_from_v2(&lease);
if (page != &hc->storage_page) {
free(page);
} else {
tiny_hotheap_v2_page_reset(page);
}
if (!hc->current_page && hc->partial_pages) {
hc->current_page = hc->partial_pages;
hc->partial_pages = hc->partial_pages->next;
if (hc->current_page) {
hc->current_page->next = NULL;
}
}
if (tiny_hotheap_v2_stats_enabled()) {
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats.page_retired, 1, memory_order_relaxed);
}
}
static inline void* tiny_hotheap_v2_try_pop(tiny_hotheap_page_v2* candidate,
tiny_heap_class_t* v1hcls,
TinyHeapClassStats* stats,
int stats_on) {
if (!candidate || !candidate->lease_page || !v1hcls) {
return NULL;
}
tiny_heap_page_t* ipage = candidate->lease_page;
v1hcls->current_page = ipage; // keep v1 hot page pinned to avoid mark_full churn
if (!(ipage->free_list || ipage->used < ipage->capacity)) {
return NULL;
}
void* user = tiny_heap_page_pop(v1hcls, 7, ipage);
if (!user) {
return NULL;
}
if (ipage->used >= ipage->capacity && ipage->free_list == NULL) {
tiny_heap_page_mark_full(v1hcls, ipage);
}
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);
}
candidate->freelist = ipage->free_list;
candidate->used = ipage->used;
return tiny_region_id_write_header(user, 7);
}
__attribute__((destructor))
static void tiny_hotheap_v2_stats_dump(void) {
if (!tiny_hotheap_v2_stats_enabled()) {
return;
}
uint64_t alloc_calls = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_calls, memory_order_relaxed);
uint64_t route_hits = atomic_load_explicit(&g_tiny_hotheap_v2_c7_route_hits, memory_order_relaxed);
uint64_t alloc_fast = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_fast, memory_order_relaxed);
uint64_t alloc_lease = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_lease, memory_order_relaxed);
uint64_t alloc_fb = atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_fallback_v1, memory_order_relaxed);
uint64_t free_calls = atomic_load_explicit(&g_tiny_hotheap_v2_c7_free_calls, memory_order_relaxed);
uint64_t free_fast = atomic_load_explicit(&g_tiny_hotheap_v2_c7_free_fast, memory_order_relaxed);
uint64_t free_fb = atomic_load_explicit(&g_tiny_hotheap_v2_c7_free_fallback_v1, memory_order_relaxed);
TinyHotHeapV2PageStats ps = {
.prepare_calls = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.prepare_calls, memory_order_relaxed),
.prepare_with_current_null = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.prepare_with_current_null, memory_order_relaxed),
.prepare_from_partial = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.prepare_from_partial, memory_order_relaxed),
.free_made_current = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.free_made_current, memory_order_relaxed),
.page_retired = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats.page_retired, memory_order_relaxed),
};
if (alloc_calls || alloc_fast || alloc_lease || alloc_fb || free_calls || free_fast || free_fb ||
ps.prepare_calls || ps.prepare_with_current_null || ps.prepare_from_partial ||
ps.free_made_current || ps.page_retired) {
fprintf(stderr,
"[HOTHEAP_V2_C7_STATS] route_hits=%llu alloc_calls=%llu alloc_fast=%llu alloc_lease=%llu alloc_refill=%llu alloc_fb_v1=%llu alloc_route_fb=%llu free_calls=%llu free_fast=%llu free_fb_v1=%llu prep_calls=%llu prep_null=%llu prep_from_partial=%llu free_made_current=%llu page_retired=%llu\n",
(unsigned long long)route_hits,
(unsigned long long)alloc_calls,
(unsigned long long)alloc_fast,
(unsigned long long)alloc_lease,
(unsigned long long)atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_refill, memory_order_relaxed),
(unsigned long long)alloc_fb,
(unsigned long long)atomic_load_explicit(&g_tiny_hotheap_v2_c7_alloc_route_fb, memory_order_relaxed),
(unsigned long long)free_calls,
(unsigned long long)free_fast,
(unsigned long long)free_fb,
(unsigned long long)ps.prepare_calls,
(unsigned long long)ps.prepare_with_current_null,
(unsigned long long)ps.prepare_from_partial,
(unsigned long long)ps.free_made_current,
(unsigned long long)ps.page_retired);
}
}
tiny_hotheap_ctx_v2* tiny_hotheap_v2_tls_get(void) {
tiny_hotheap_ctx_v2* ctx = g_tiny_hotheap_ctx_v2;
if (__builtin_expect(ctx == NULL, 0)) {
@ -161,94 +481,81 @@ tiny_hotheap_ctx_v2* tiny_hotheap_v2_tls_get(void) {
abort();
}
g_tiny_hotheap_ctx_v2 = ctx;
// C7 用 stride を最初にだけ設定(他クラスは未使用のまま)
ctx->cls[7].stride = (uint16_t)tiny_stride_for_class(7);
for (int i = 0; i < TINY_HOTHEAP_MAX_CLASSES; i++) {
tiny_hotheap_v2_page_reset(&ctx->cls[i].storage_page);
ctx->cls[i].stride = (uint16_t)tiny_stride_for_class(i);
}
}
return ctx;
}
void* tiny_hotheap_v2_alloc(uint8_t class_idx) {
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc, 1, memory_order_relaxed);
int stats_on = tiny_hotheap_v2_stats_enabled();
if (stats_on) {
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_route_hits, 1, memory_order_relaxed);
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_calls, 1, memory_order_relaxed);
}
if (__builtin_expect(class_idx != 7, 0)) {
return NULL; // いまは C7 専用
}
tiny_hotheap_ctx_v2* v2ctx = tiny_hotheap_v2_tls_get();
tiny_hotheap_class_v2* vhcls = &v2ctx->cls[7];
tiny_hotheap_class_v2* vhcls = v2ctx ? &v2ctx->cls[7] : NULL;
tiny_hotheap_page_v2* v2page = vhcls ? vhcls->current_page : NULL;
if (!v2page && vhcls) {
v2page = &vhcls->storage_page;
tiny_hotheap_v2_page_reset(v2page);
vhcls->current_page = v2page;
}
tiny_heap_ctx_t* v1ctx = tiny_heap_ctx_for_thread();
tiny_heap_class_t* v1hcls = tiny_heap_class(v1ctx, 7);
TinyHeapClassStats* stats = tiny_heap_stats_for_class(7);
// Hot path: current_page の lease をそのまま使う
if (v2page && v2page->lease_page && v1hcls) {
tiny_heap_page_t* ipage = v2page->lease_page;
if (ipage->free_list || ipage->used < ipage->capacity) {
void* user = tiny_heap_page_pop(v1hcls, 7, ipage);
// Hot path: current_page → partial → refill
void* user = tiny_hotheap_v2_try_pop(v2page, v1hcls, stats, stats_on);
if (user) {
if (ipage->used >= ipage->capacity && ipage->free_list == NULL) {
tiny_heap_page_mark_full(v1hcls, ipage);
}
if (__builtin_expect(stats != NULL, 0)) {
atomic_fetch_add_explicit(&stats->alloc_fast_current, 1, memory_order_relaxed);
}
v2page->freelist = ipage->free_list;
v2page->used = ipage->used;
return user;
}
while (vhcls && vhcls->partial_pages) {
if (stats_on) {
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats.prepare_calls, 1, memory_order_relaxed);
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats.prepare_from_partial, 1, memory_order_relaxed);
if (vhcls->current_page == NULL) {
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats.prepare_with_current_null, 1, memory_order_relaxed);
}
}
if (__builtin_expect(stats != NULL, 0)) {
atomic_fetch_add_explicit(&stats->alloc_slow_prepare, 1, memory_order_relaxed);
v2page = vhcls->partial_pages;
vhcls->partial_pages = vhcls->partial_pages->next;
v2page->next = NULL;
vhcls->current_page = v2page;
user = tiny_hotheap_v2_try_pop(v2page, v1hcls, stats, stats_on);
if (user) {
return user;
}
}
// Lease a page from v1 (C7 SAFE) and wrap it
TinyHeapPageLease lease = tiny_heap_c7_lease_page_for_v2();
if (!lease.page || !vhcls || !v1hcls) {
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_fallback, 1, memory_order_relaxed);
tiny_hotheap_page_v2* leased = tiny_hotheap_v2_refill_slow(v2ctx, 7);
if (!leased || !v1hcls) {
if (stats_on) {
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_fallback_v1, 1, memory_order_relaxed);
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_route_fb, 1, memory_order_relaxed);
}
size_t size = vhcls ? (vhcls->stride ? vhcls->stride : tiny_stride_for_class(7)) : tiny_stride_for_class(7);
return tiny_c7_alloc_fast(size); // safety fallback to v1
}
if (!v2page) {
v2page = &vhcls->storage_page;
tiny_hotheap_v2_page_reset(v2page);
vhcls->current_page = v2page;
vhcls->current_page = leased;
v2page = leased;
if (v1hcls && v2page && v2page->lease_page) {
v1hcls->current_page = v2page->lease_page;
}
if (stats_on) {
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_lease, 1, memory_order_relaxed);
}
v2page->lease_page = lease.page;
v2page->meta = lease.meta;
v2page->ss = lease.ss;
v2page->base = lease.base;
v2page->capacity = lease.capacity;
v2page->slab_idx = lease.slab_idx;
v2page->freelist = lease.freelist;
v2page->used = lease.page->used;
if (lease.page->free_list || lease.page->used < lease.page->capacity) {
void* user = tiny_heap_page_pop(v1hcls, 7, lease.page);
user = tiny_hotheap_v2_try_pop(v2page, v1hcls, stats, stats_on);
if (user) {
if (lease.page->used >= lease.page->capacity && lease.page->free_list == NULL) {
tiny_heap_page_mark_full(v1hcls, lease.page);
}
if (__builtin_expect(stats != NULL, 0)) {
atomic_fetch_add_explicit(&stats->alloc_fast_current, 1, memory_order_relaxed);
}
v2page->freelist = lease.page->free_list;
v2page->used = lease.page->used;
return user;
}
}
// Lease 取得後でも pop できなければ v1 に委譲
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_fallback, 1, memory_order_relaxed);
if (stats_on) {
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_fallback_v1, 1, memory_order_relaxed);
}
size_t size = vhcls ? (vhcls->stride ? vhcls->stride : tiny_stride_for_class(7)) : tiny_stride_for_class(7);
return tiny_c7_alloc_fast(size);
}
@ -257,26 +564,61 @@ void tiny_hotheap_v2_free(uint8_t class_idx, void* p, void* meta) {
if (__builtin_expect(class_idx != 7, 0)) {
return;
}
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_free, 1, memory_order_relaxed);
int stats_on = tiny_hotheap_v2_stats_enabled();
if (stats_on) {
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_free_calls, 1, memory_order_relaxed);
}
tiny_hotheap_ctx_v2* v2ctx = tiny_hotheap_v2_tls_get();
tiny_hotheap_class_v2* vhcls = v2ctx ? &v2ctx->cls[7] : NULL;
tiny_hotheap_page_v2* v2page = vhcls ? vhcls->current_page : NULL;
TinySlabMeta* meta_ptr = (TinySlabMeta*)meta;
tiny_heap_ctx_t* v1ctx = tiny_heap_ctx_for_thread();
tiny_heap_class_t* v1hcls = tiny_heap_class(v1ctx, 7);
if (v2page && v2page->lease_page && meta_ptr && v1hcls &&
v2page->meta == meta_ptr && tiny_heap_ptr_in_page_range(v2page->lease_page, p)) {
tiny_heap_page_free_local(v1ctx, 7, v2page->lease_page, p);
v2page->freelist = v2page->lease_page->free_list;
v2page->used = v2page->lease_page->used;
vhcls->current_page = v2page; // keep pinned
tiny_hotheap_page_v2* page = tiny_hotheap_v2_find_page(vhcls, 7, p, meta_ptr);
if (page && page->lease_page && v1hcls && tiny_heap_ptr_in_page_range(page->lease_page, p)) {
tiny_heap_page_free_local(v1ctx, 7, page->lease_page, p);
page->freelist = page->lease_page->free_list;
page->used = page->lease_page->used;
if (v1hcls) {
v1hcls->current_page = page->lease_page;
}
if (vhcls && vhcls->current_page != page) {
tiny_hotheap_v2_unlink_page(vhcls, page);
page->next = vhcls->current_page;
vhcls->current_page = page;
if (stats_on) {
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats.free_made_current, 1, memory_order_relaxed);
}
} else if (stats_on) {
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats.free_made_current, 1, memory_order_relaxed);
}
// C7-only: keep the page hot even when empty to avoid churn
if (vhcls) {
if (!vhcls->current_page) {
vhcls->current_page = page;
} else if (vhcls->current_page != page) {
tiny_hotheap_v2_unlink_page(vhcls, page);
page->next = vhcls->current_page;
vhcls->current_page = page;
}
}
if (page->used == 0 && vhcls && vhcls->partial_pages != page && vhcls->current_page == page) {
// park empty page in partial to allow re-use without immediate Superslab return
page->next = vhcls->partial_pages;
vhcls->partial_pages = page;
vhcls->current_page = page; // still treat as current
}
if (stats_on) {
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_free_fast, 1, memory_order_relaxed);
}
return;
}
// Fallback: mimic v1 free path
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_free_fallback, 1, memory_order_relaxed);
if (stats_on) {
atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_free_fallback_v1, 1, memory_order_relaxed);
}
SuperSlab* ss = hak_super_lookup(p);
if (ss && ss->magic == SUPERSLAB_MAGIC) {
int slab_idx = slab_index_for(ss, p);

View File

@ -15,6 +15,9 @@ void hak_tiny_init(void) {
// Step 1: Simple initialization (static global is already zero-initialized)
g_tiny_initialized = 1;
// Route snapshot (TinyHeap vs Legacy) を起動時に固定
tiny_route_snapshot_init();
// Reset fast-cache defaults and apply preset (if provided)
tiny_config_reset_defaults();

View File

@ -4,6 +4,7 @@
// Date: 2025-11-28
#include "hakmem_tiny_superslab_internal.h"
#include "box/ss_os_acquire_box.h"
// ============================================================================
// Cache System - Global Variables
@ -60,10 +61,12 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p
-1, 0);
if (ptr != MAP_FAILED) {
atomic_fetch_add(&g_ss_mmap_count, 1);
ss_os_stats_record_alloc();
if (((uintptr_t)ptr & ss_mask) == 0) {
ss_stats_os_alloc(size_class, ss_size);
return ptr;
}
ss_os_stats_record_free();
munmap(ptr, ss_size);
ptr = NULL;
} else {
@ -79,6 +82,7 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p
-1, 0);
if (raw != MAP_FAILED) {
uint64_t count = atomic_fetch_add(&g_ss_mmap_count, 1) + 1;
ss_os_stats_record_alloc();
#if !HAKMEM_BUILD_RELEASE
if (log_count < 10) {
fprintf(stderr, "[SUPERSLAB_MMAP] #%lu: class=%d size=%zu (total SuperSlab mmaps so far)\n",
@ -98,11 +102,13 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p
size_t prefix_size = aligned_addr - raw_addr;
if (prefix_size > 0) {
ss_os_stats_record_free();
munmap(raw, prefix_size);
}
size_t suffix_size = alloc_size - prefix_size - ss_size;
if (suffix_size > 0) {
// 余剰領域は常に munmap して、実際に使用する SuperSlab サイズだけを残す。
ss_os_stats_record_free();
munmap((char*)ptr + ss_size, suffix_size);
}
@ -111,6 +117,7 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p
// CRITICAL FIX (2025-12-05): Use MADV_POPULATE_WRITE for efficiency
#ifdef MADV_POPULATE_WRITE
int ret = madvise(ptr, ss_size, MADV_POPULATE_WRITE);
ss_os_stats_record_madvise();
if (ret != 0) {
// Fallback: explicit memset
memset(ptr, 0, ss_size);
@ -118,6 +125,7 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p
#else
// Fallback for kernels < 5.14
memset(ptr, 0, ss_size);
ss_os_stats_record_madvise();
#endif
ss_stats_os_alloc(size_class, ss_size);

View File

@ -30,6 +30,11 @@ uint64_t g_ss_freed_by_class[8] = {0};
// Global counters for debugging (non-static for external access)
_Atomic uint64_t g_ss_mmap_count = 0;
_Atomic uint64_t g_final_fallback_mmap_count = 0;
_Atomic uint64_t g_ss_os_alloc_calls = 0;
_Atomic uint64_t g_ss_os_free_calls = 0;
_Atomic uint64_t g_ss_os_madvise_calls = 0;
_Atomic uint64_t g_ss_os_huge_alloc_calls = 0;
_Atomic uint64_t g_ss_os_huge_fail_calls = 0;
// Superslab/slab observability (Tiny-only; relaxed updates)
_Atomic uint64_t g_ss_live_by_class[8] = {0};
@ -200,6 +205,35 @@ void superslab_print_global_stats(void) {
pthread_mutex_unlock(&g_superslab_lock);
}
// ============================================================================
// OS call counters (optional, ENV gated)
// ============================================================================
static int ss_os_stats_env_enabled(void) {
static int g = -1;
if (__builtin_expect(g == -1, 0)) {
const char* e = getenv("HAKMEM_SS_OS_STATS");
g = (e && *e && *e != '0') ? 1 : 0;
}
return g;
}
static void ss_os_stats_dump(void) __attribute__((destructor, used));
static void ss_os_stats_dump(void) {
if (!ss_os_stats_env_enabled()) {
return;
}
fprintf(stderr,
"[SS_OS_STATS] alloc=%llu free=%llu madvise=%llu mmap_total=%llu fallback_mmap=%llu huge_alloc=%llu huge_fail=%llu\n",
(unsigned long long)atomic_load_explicit(&g_ss_os_alloc_calls, memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&g_ss_os_free_calls, memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&g_ss_os_madvise_calls, memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&g_ss_mmap_count, memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&g_final_fallback_mmap_count, memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&g_ss_os_huge_alloc_calls, memory_order_relaxed),
(unsigned long long)atomic_load_explicit(&g_ss_os_huge_fail_calls, memory_order_relaxed));
}
void ss_stats_dump_if_requested(void) {
const char* env = getenv("HAKMEM_SS_STATS_DUMP");
if (!env || !*env || *env == '0') {

View File

@ -168,6 +168,47 @@ static inline void watch_alloc_trigger(void* base, int class_idx, AllocSource so
// ========== Write Header (Allocation) ==========
// Header write mode (bench-only switch; default FULL)
enum tiny_header_mode
{
TINY_HEADER_MODE_FULL = 0,
TINY_HEADER_MODE_LIGHT = 1,
TINY_HEADER_MODE_OFF = 2,
};
static inline int tiny_header_mode(void)
{
static int g_header_mode = -1;
if (__builtin_expect(g_header_mode == -1, 0))
{
const char* e = getenv("HAKMEM_TINY_HEADER_MODE");
if (e && *e)
{
char c = e[0];
if (c == 'l' || c == 'L' || c == '1')
{
g_header_mode = TINY_HEADER_MODE_LIGHT;
}
else if (c == 'o' || c == 'O' || c == '0')
{
g_header_mode = TINY_HEADER_MODE_OFF;
}
else
{
g_header_mode = TINY_HEADER_MODE_FULL;
}
}
else
{
// Backward compatibility: HAKMEM_TINY_WRITE_HEADER=0 behaves like "off".
const char* old = getenv("HAKMEM_TINY_WRITE_HEADER");
g_header_mode =
(old && *old && *old == '0') ? TINY_HEADER_MODE_OFF : TINY_HEADER_MODE_FULL;
}
}
return g_header_mode;
}
// Write class_idx to header (called after allocation)
// Input: base (block start from SuperSlab)
// Returns: user pointer (base + 1, skipping header)
@ -238,19 +279,28 @@ static inline void* tiny_region_id_write_header(void* base, int class_idx) {
} while (0);
#endif // !HAKMEM_BUILD_RELEASE
// P3: Header writeをデフォルトONTLS SLL向けに常時復元、ENVでOFF可能
// class_map があっても TLS SLL 境界でヘッダーが必要になるため、A/B 切替は
// HAKMEM_TINY_WRITE_HEADER=0 でのみ OFF旧デフォルトにする。
// Memory layout preserved: user = base + 1ヘッダー領域は常に予約
static int g_write_header = -1;
if (__builtin_expect(g_write_header == -1, 0)) {
const char* e = getenv("HAKMEM_TINY_WRITE_HEADER");
g_write_header = (e && *e && *e == '0') ? 0 : 1;
// Header write policy (bench-only switch, default FULL)
int header_mode = tiny_header_mode();
uint8_t desired_header = (uint8_t)(HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK));
uint8_t existing_header = *header_ptr;
if (__builtin_expect(header_mode == TINY_HEADER_MODE_FULL, 1)) {
*header_ptr = desired_header;
PTR_TRACK_HEADER_WRITE(base, desired_header);
} else if (header_mode == TINY_HEADER_MODE_LIGHT) {
// Keep header consistent but avoid redundant stores.
if (existing_header != desired_header) {
*header_ptr = desired_header;
PTR_TRACK_HEADER_WRITE(base, desired_header);
}
} else { // TINY_HEADER_MODE_OFF (bench-only)
// Only touch the header if it is clearly invalid to keep free() workable.
uint8_t existing_magic = existing_header & 0xF0;
if (existing_magic != HEADER_MAGIC ||
(existing_header & HEADER_CLASS_MASK) != (desired_header & HEADER_CLASS_MASK)) {
*header_ptr = desired_header;
PTR_TRACK_HEADER_WRITE(base, desired_header);
}
if (__builtin_expect(g_write_header, 1)) {
// Legacy mode: write header for debugging or compatibility
*header_ptr = HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK);
PTR_TRACK_HEADER_WRITE(base, HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK));
}
void* user = header_ptr + 1; // skip header for user pointer (layout preserved)
PTR_TRACK_MALLOC(base, 0, class_idx); // Track at BASE (where header is)
@ -272,7 +322,7 @@ static inline void* tiny_region_id_write_header(void* base, int class_idx) {
// ========== END ALLOCATION LOGGING ==========
// Optional guard: log stride/base/user for targeted class
if (tiny_guard_is_enabled()) {
if (header_mode != TINY_HEADER_MODE_OFF && tiny_guard_is_enabled()) {
size_t stride = tiny_stride_for_class(class_idx);
tiny_guard_on_alloc(class_idx, base, user, stride);
}

View File

@ -0,0 +1,60 @@
# C6_HOTBOX_DESIGN
目的: class6 を C7 SAFE と同じ TinyHeap ベースでホット化するための箱を定義する。まずは SAFEmeta きちんと保持だけを対象とし、ULTRAmeta を大胆に省く)は C6 ではやらない。
## 境界(箱の責務)
- **内側 (C6 HotBox)**: TinyHeapBox 上での heap → page → block 管理、current_page ポリシー。
- **外側 (Cold Box)**: Superslab / Tier / Guard / Stats。TLS SLL とは原則切断TinyHeap クラスには SLL を使わせない)。
## ENV / A/B 切替
- `HAKMEM_TINY_HEAP_BOX` … TinyHeap front 全体の ON/OFF。
- `HAKMEM_TINY_HEAP_CLASSES` … クラスごとの TinyHeap マスク。デフォルトは `0x80` (C7 のみ)。
- `HAKMEM_TINY_C6_HOT` … C6 Hot front を有効化するスイッチ1 で C6 専用直線パスを許可)。
- `HAKMEM_TINY_C6_META_MODE` … 0=OFF, 1=SAFE当面は SAFE のみ。ULTRA はやらない)。
- **注意**: meta_mode=1 は現時点では bench/実験専用。長めの C6-heavy でクラッシュする既知事象があり、通常運用は
meta_mode=0 または C6 TinyHeap OFF を推奨する。
- デバッグ用meta_mode=1 時の Fail-Fast/可視化):
- `HAKMEM_TINY_C6_DEBUG_POP=1` … pop まわりの Fail-Fast を有効化。
- `HAKMEM_TINY_C6_DELTA_TRACE=1` … delta を触った箇所を last_delta_site に記録。
- `HAKMEM_TINY_C6_DELTA_DEBUG=1` … destructor で delta サマリを dump。
- `HAKMEM_TINY_C6_DELTA_TRACE=1` が有効なときは delta サイト (ALLOC/FREE/ATTACH/EMPTY/THRESHOLD) をページに刻み、
`HAKMEM_TINY_C6_DEBUG_POP=1` では pop/free の Fail-Fast範囲外 freelist/ss mismatch/cap0 等)と 512 行までの debug log を出す。
## 現状の事実2025-12 時点)
- C6 TinyHeap`HAKMEM_TINY_HEAP_CLASSES=0x40`)は C6-heavy / Mixed どちらも回帰気味(最新測定 2025-12-05
- C6-heavy (min=257/max=768, ws=256, iters=20k, debug OFF)
- LEGACY: **41.74M ops/s**HEAP_STATS 0
- TinyHeap mode0 (`C6_META_MODE=0`): **36.07M ops/s**cls6 fast=5381 / slow_prepare=1
- TinyHeap mode1 (`C6_META_MODE=1` SAFE): **28.86M ops/s**cls6 fast=2692 / slow_prepare=2690
- Mixed 161024B (ws=256, iters=20k)
- LEGACY: **40.90M ops/s**
- C7_SAFE (C6 OFF): **40.96M ops/s**cls7 fast=5691 / slow=1
- C6+C7 SAFE (`HEAP_CLASSES=0xC0`、両方 meta_mode=1): **27.21M ops/s**cls6 fast=1388 / slow=1366、cls7 fast=5664 / slow=19
- mode0 は slow_prepare≈1 でも約 -14%C6-heavyと大きくマイナス、mode1 は slow_prepare が増えてさらに悪化。現状は bench/実験専用マスク0x40/0xC0とし、通常は LEGACY か C7_SAFE のみを推奨。
- slow_prepare が小さいまま回帰するため、原因は Front/Hot の命令コストや meta 更新コストdelta/flush 実装の重さにあると推定。mode1 は delta/flush を触ることで slow_prepare も増えやすい点に注意。
- meta_mode=1 の暫定防御Phase2122:
- attach で meta->freelist を範囲チェックし、OOB は NULL に潰す。
- empty→release で meta->freelist を NULL にし、debug 時は page->free_list に poison。
- pop で freelist OOB / ss mismatch / cap0 などを Fail-Fast。
- 上記後、C6-heavy iters=1000/1500/2000/20000 で再現していた SIGSEGV は出なくなり、delta サマリは 0/0/0ただし bench 専用扱いは継続)。
## 要件と設計方針
- 当面は **SAFE のみ**meta/active を delta + flush で正確寄りに保つ。ULTRA は C6 では導入しない。
- A/B できるように:
- C6 Hot front: `HAKMEM_TINY_C6_HOT=1` で直線パスを有効化。
- C6 TinyHeap ON/OFF: `HAKMEM_TINY_HEAP_CLASSES` のビットで切替(デフォルトは OFF
- Box Theory 準拠:
- HotBoxTinyHeap 内部)と Cold BoxSuperslab/Tier/Guard/Statsの境界は 1 箇所。
- TLS SLL は C6 HotBox からは触らない。
## 今後のフォーカス
- C7 SAFE で効いた current_page 固定+ delta/閾値 flush を C6 に横展開するかを検討。
- Front/Hot の命令数を減らすため、C6 専用の直線フロントGate → Heap 一本化)を C7 と対称に整える。
- 成果の指標:
- C6-heavy で LEGACY と同等以上を目指す。
- Mixed 161024B で C6 を載せてもマイナスが小さい±1M 以内)こと。
v1 の結論と凍結方針
--------------------
- v1 では C6 TinyHeap/Hot はどのモードでも C6-heavy/Mixed で明確なマイナス。meta_mode=1 は bench/実験専用とし、通常は mode0 か OFF を推奨。
- C6 の本格的な再設計は TinyHeap v2C5C7 をまとめて組み直す箱で行う。C6 を触るときは bench/実験マスク (0x40/0xC0) とデバッグ ENV を明示的に有効にする。

View File

@ -36,6 +36,21 @@ A/B 切替ポリシーHAKMEM_TINY_C7_HOT
- `HAKMEM_TINY_C7_HOT=1` : C7HotBox を有効化。Gate で `class_idx==7` を検出したときだけ `tiny_c7_alloc_fast` / `tiny_c7_free_fast` を経由する。
- `HAKMEM_TINY_C7_HOT=0` : 完全に従来経路へフォールバックUnified Cache / Warm / Superslab の既存ルート)。
- ENV で即時戻せるようにし、Box 境界は slow helper`tiny_c7_alloc_slow_from_heap` / `tiny_c7_page_becomes_empty`1 箇所に集約する。
- TinyHeap プロファイル: `HAKMEM_TINY_HEAP_PROFILE` を導入LEGACY/C7_SAFE/C7_ULTRA_BENCH/CUSTOM
- C7_SAFE: class mask=0x80, C7 meta_mode=1SAFE`HAKMEM_TINY_HEAP_BOX` 自動 ON。C7_HOT は別途 1 を推奨。
- C7_ULTRA_BENCH: class mask=0x80, C7 meta_mode=2bench 専用)。
- CUSTOM: 既存の `HAKMEM_TINY_HEAP_CLASSES` / `HAKMEM_TINY_C7_META_MODE` を直接指定。
- 推奨セット:
- 本番寄せ C7: `HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_LARSON_FIX=1`
- C7-only bench: `HAKMEM_TINY_HEAP_PROFILE=C7_ULTRA_BENCH HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_LARSON_FIX=1`
- 補足: class6 にも対称の Hot front`HAKMEM_TINY_C6_HOT` + class maskを追加済みだが、C6 は回帰が大きく bench/実験専用のまま。
- Phase16: Route Snapshot Box (`tiny_route_env_box.h`) を追加し、Gate は「size→class→route LUT→heap/legacy」の 1 分岐構造に整理。C7 SAFE profile でも mixed での回帰を縮めつつ、C7-only は LEGACY≈39.7M / SAFE≈41.1M / ULTRA≈46.1M (20k/ws=64, Release, LARSON_FIX=1)。
現状の位置づけ (Phase 19)
--------------------------
- C7 SAFE: C7-only 20k/ws64 ≈46.6M ops/s、Mixed 161024B は LEGACY と ±1M 以内。C7-heavy で推奨。
- C7 ULTRA_BENCH: C7-only 20k/ws64 ≈52Mbench 専用、Superslab/Tier 統計は緩むため本番では OFF
- C6 TinyHeap: C6-heavy / Mixed で throughput が明確に悪化(例: LEGACY≈44.3M → C6 TinyHeap≈38.6M)。`HAKMEM_TINY_HEAP_CLASSES=0x40/0xC0` は bench/実験専用マスクとして扱う。
メモ
----
@ -119,6 +134,52 @@ Phase 9: Tiny lane 判定を TinyHeap と揃える
- Mixed 161024B: OFF≈47.6M / C7 only TinyHeap≈36.9M / C6+C7 TinyHeap≈30.3M(警告なし)。
- 依然として性能は TinyHeap OFF より低いケースがあるため、C6/C7 の slow_prepare 削減や current_page 利用強化を次フェーズで行う。
Phase ULTRA: C7 meta モードの三段化bench 専用 ULTRA 追加)
------------------------------------------------------------
- ENV `HAKMEM_TINY_C7_META_MODE` を追加0:OFF, 1:SAFE meta-light=従来の delta+閾値 flush/clamp, 2:ULTRA。未指定時は `HAKMEM_TINY_C7_META_LIGHT` を後方互換ゲートとして扱い SAFE=1 相当。
- ULTRA(mode=2) は C7-only ベンチ専用。per-alloc で meta->used / ss_active_* を一切更新せず、delta/flush もスキップする高速モード。2024-xx-xx 時点で pop/push の meta->freelist/carved atomic も省略し、ベンチ専用の最小オーバーヘッド構成にしているBox 境界は維持しつつ Superslab/Tier の統計整合は犠牲にする)。
- SAFE(mode=1) はこれまでのページ境界 flush + 閾値 flush + attach clamp を維持し、本番で使うなら mode=0/1。ULTRA は「bench/研究用途のみ」と明記。
- C7-only 20k/ws=64 (Release, HEAP_BOX=1, HEAP_CLASSES=0x80, HOT=1, LARSON_FIX=1):
- mode=0: ≈38.7M ops/salloc_fast_current=10052 / alloc_slow_prepare=7681
- mode=1: ≈34.1M ops/salloc_fast_current=5837 / alloc_slow_prepare=5179
- mode=2: ≈41.6M ops/salloc_fast_current=5948 / alloc_slow_prepare=5068
- current_page 可視化Phase 11: `g_c7_page_stats` を追加し、prepare_calls / prepare_with_current_null / prepare_from_partial / current_set_from_free / current_dropped_to_partial を `HAKMEM_TINY_C7_HEAP_STATS=1` でダンプ。C7-only ULTRA (20k/ws=64) では `prepare_with_current_null=prepare_calls` となり、free 側で current_page を保持できていないことが見えたため、current 固定化ポリシーを継続検討中。
- current 固定化の初期版ULTRA 専用): empty/full での unlink を避け、free 後は常に current_page に据え置く + prepare で current を優先するように変更。C7-only 20k/ws=64 (mode=2) で ops≈52.0M、alloc_fast_current=11015 / alloc_slow_prepare=1 / prepare_calls=1 まで改善。SAFE への逆輸入は未定ULTRA は bench 限定)。
- SAFE (mode=1) への current ポリシー逆輸入 (Phase 12):
- free で used>0 の page を current に据え直し、empty でも delta flush のみで detach/publish を避けて current を保持。mark_full でも C7 meta_light が current を指す場合は unlink しない。
- prepare_page も C7 meta_light で current に空きがあれば即 returnrefill へ降りない)。
- ベンチ (C7-only, ws=64, HEAP_BOX=1, HEAP_CLASSES=0x80, HOT=1, LARSON_FIX=1): SAFE mode=1 20k ≈46.6M ops/salloc_fast_current=11015 / alloc_slow_prepare=1 / free_fast_local=8726, prepare_calls=1。長時間 100k≈46.7M / 200k≈44.99M、`HAKMEM_TINY_C7_DELTA_DEBUG=1` でも delta 残 0 を確認。
- ULTRA(mode=2) は bench 専用のまま。本番寄り構成は mode=0/1 を推奨。
Phase 13: mixed での SAFE 効果と multi-class stats
---------------------------------------------------
- Stats を `TinyHeapClassStats[]` に拡張ENV: `HAKMEM_TINY_HEAP_STATS` / `_DUMP`、旧 `_C7_` 互換し、C6/C7 の fast/slow/fallback を同時に計測可能にした。
- Mixed 161024B (iters=20k, ws=256, LARSON_FIX=1):
- TinyHeap OFF: ≈43.7M ops/s。
- C7 SAFE のみ TinyHeap (`HEAP_BOX=1 HEAP_CLASSES=0x80 META_MODE=1 HOT=1`): ≈44.9M ops/s`HEAP_STATS[7] fast=5691 slow_prepare=1`)。
- C6+C7 TinyHeap (`HEAP_CLASSES=0xC0`): ≈39.3M ops/s`HEAP_STATS[6] fast=2744 slow=1`, `HEAP_STATS[7] fast=5691 slow=1`)。
- C6 偏重 (min=257 max=768):
- TinyHeap OFF: ≈43.8M ops/s。
- C6 TinyHeap のみ: ≈38.5M ops/s`HEAP_STATS[6] fast=5372 slow=1`)。
- C6+C7 TinyHeap: ≈40.6M ops/s`HEAP_STATS[6] fast=5372 slow=1`, `HEAP_STATS[7] fast=5691 slow=1`)。
- 方針: C7 SAFE は mixed でも悪化せず、C7-only では legacy 超え → デフォルト TinyHeap 候補。C6 は slow_prepare 自体は少ないが経路オーバーヘッドで低下するため、当面は bench/実験用 (HEAP_CLASSES=0x40/0xC0)。C7-only を突き抜けるベンチは ULTRA (META_MODE=2) を手動で使う。
Phase 17: C7 フロント超直線化HotPipeline 前倒し)
----------------------------------------------------
- Route Snapshot を使った C7 判定ヘルパ `tiny_c7_front_uses_heap()` を追加。Gate から class7 が TinyHeap 経路かどうかを 1 LUT で判定できるようにした。
- `malloc_tiny_fast` の冒頭に「size==1024 かつ C7 route=HEAP」専用パスを追加。クラス判定/LUT/route を飛ばして `tiny_c7_alloc_fast` へ直行し、miss 時だけ Legacy Tiny slow (`tiny_cold_refill_and_alloc(7)`) に静かにフォールバック。
- `free_tiny_fast` も C7 route=HEAP を先に評価し、Larson fix ブロックの owner 判定後に `tiny_c7_free_fast_with_meta` へ直行する経路を明示route はスナップショットから 1 回だけ読む)。
- ベンチ (Release, iters=20k, ws=64, LARSON_FIX=1, HOT=1):
- C7-only: PROFILE=LEGACY ≈37.1M / C7_SAFE ≈38.2M / C7_ULTRA_BENCH ≈45.3M ops/s。
- Mixed 161024B (ws=256): PROFILE=LEGACY ≈40.3M / C7_SAFE ≈40.7M ops/s差 ~-1M まで縮小)。
- 今後の選択肢メモ: (A) C6 TinyHeap を C7 SAFE 流current 固定meta-light SAFEに寄せるか、(B) Tiny front/gate/UC の命令削減を perf で詰めるかを次フェーズで決める。
Phase 18 メモC6 SAFE 実験とプロファイル整理)
------------------------------------------------
- TinyHeap プロファイル箱に加え、`HAKMEM_TINY_C6_META_MODE`0/1を追加。現状の C6 SAFE は整合優先で挙動は mode 0 相当delta/flush 未使用、meta/active は per-alloc 更新)だが、クラスマスクで C6 を TinyHeap に載せたまま A/B 計測できる。
- C6 偏重 20k/ws=256 では LEGACY ≈44.3M → C6 TinyHeap (mask=0x40, META_MODE=0/1) ≈38.6〜38.8M、C6+C7 TinyHeap (0xC0, META_MODE=1) ≈39.9M。Mixed 161024B でも C6 TinyHeap は ≈38.5〜38.7M、C6+C7 TinyHeap ≈39.5Mslow_prepare はいずれも ≈1 と低い)。
- デフォルト profile は引き続き C7 SAFE / C7 ULTRA_BENCH / LEGACY の 3 択。C6 を TinyHeap に載せるのは bench/研究用(クラスマスク 0x40/0xC0 を明示)とし、本番では 0x80=C7 のみを推奨。C6 向け meta-light を安全に再導入する場合は Superslab 解放まわりの安全性を再確認する。
TinyHeapBox への載せ替えPhase 1.0 構造)
------------------------------------------
- C7HotBox の実体を `core/box/tiny_heap_box.h` の汎用 TinyHeapBox 上に配置し、型は `tiny_heap_ctx_t` / `tiny_heap_page_t` へ統一。

View File

@ -0,0 +1,74 @@
Cold Tiny Stats Box (初期メモ)
=============================
目的
----
- Tiny のホットパス (TinyHeap/Front) では「page->used の増減だけ」に集中し、Superslab/Tier/Guard 用の meta->used / ss_active_* / 学習系カウンタの更新は Cold 側に押し出す。
- C7 SAFE で導入した page 内 delta + flush を一般化し、「Stats Box」がイベントを受け取って統計を更新する構造に寄せる。
境界の考え方
------------
- Hot 側 (TinyHeap / TinyFront / HotBox):
- やること: page->used の増減、必要なら「EVENT_ALLOC / EVENT_FREE」などの軽い通知を 1 回投げるだけ。
- やらないこと: meta->used / ss_active_* / Tier 判定用カウンタを直接いじる。
- Cold Stats Box 側:
- Hot からのイベントをバッファ or 簡易カウンタで受け取り、
- Superslab/Tier/Guard が参照する統計値に反映する(当面は flush 境界で即時更新。次フェーズでバッチ化を検討)。
C7 SAFE との関係
----------------
- C7 SAFE ではすでに page 内に used_delta/active_delta を持ち、empty/threshold/attach 時に flush して meta/active を更新する「ミニ Aggregator」がある。
- 本フェーズ以降は「flush 部分だけを Stats Box に委譲」→「Stats Box で pending に貯め、所定のトリガでまとめて meta/active を反映」という二段構えに進化させる。
- 将来的には Stats Box 内で本格的なバッチ更新・学習カウンタ更新を担う余地を残す。
遅延の許容と不変条件C7 基準)
--------------------------------
- Superslab/Tier/Guard が前提にする不変条件:
- meta->used / ss_active_* は「ページが Superslab を離れる前」に必ず整合した値に戻っていること。
- Tier 判定や Guard/OOM 判定は「一時的に +α(最大 K ページぶん」までなら許容するが、K は環境から明示的に決める。
- 今回の方針:
- 1 ページあたり最大「capacity×16」程度までの pending なら遅延を許容ENV で ON のときのみ)。
- ページが empty になり Superslab/Tier に返る直前、または pending が閾値を超えたときに必ず flush。
- Superslab を完全 release する前に pending=0 になるempty で flush を強制するため、Tier/Guard の破綻は避ける。
ENV / A/B ポリシー
------------------
- HAKMEM_TINY_STATS_BOX=0/1 で Stats Box 経由を切替。
- 0: これまで通り TinyHeap 内で meta/active を直接更新。
- 1: C7 SAFE の flush を Stats Box 経由に分離。
- HAKMEM_TINY_STATS_BATCH=0/1 で meta/active の適用タイミングを切替Stats Box が有効な場合のみ有効)。
- 0: flush ごとに即 meta->used / ss_active_* を更新(従来挙動)。
- 1: flush で得た delta を page pending に貯め、閾値/empty/release/destructor でまとめて反映。
- 当面は C7 のみ対象。他クラスや C6 への適用は次フェーズ以降の検討事項。
初期 A/BC7-only 20k/ws=64, PROFILE=C7_SAFE, HOT=1, HEAP_STATS=ON
--------------------------------------------------------------------
- STATS_BOX=0: 42.99M ops/scls7 fast=11015 / slow=1
- STATS_BOX=1: 42.92M ops/scls7 fast=11015 / slow=1
- いずれも delta summary=0 で挙動差なし(場所だけ Box に分離)。
今フェーズの実装(概要)
-------------------------
- Stats Box に page 単位の pending(used_delta/active_delta) を追加。
- C7 SAFE の flush は「delta を Stats Box に渡すだけ」に変更し、Stats Box が
- STATS_BATCH=0: 即座に meta/active 更新。
- STATS_BATCH=1: pending に加算し、閾値超え/empty でまとめて meta/active へ反映。
- Hot 側TinyHeap/C7 SAFEのコード量は変えず、Cold 側だけで更新タイミングを差し替えられる形にした。
Phase27 A/Bバッチ有効時の影響
---------------------------------
- C7-only 20k/ws=64PROFILE=C7_SAFE, HOT=1, LARSON_FIX=1, HEAP_STATS=ON
- STATS_BOX=0: 43.31M ops/s
- STATS_BOX=1, BATCH=0: 43.06M ops/sfast/slow 同一)
- STATS_BOX=1, BATCH=1: 35.10M ops/s大幅マイナス
- STATS_BOX=1, BATCH=1, META_MODE=2ULTRA bench: 48.55M ops/s
- Mixed 161024B 20k/ws=256
- LEGACY: 40.92M ops/s
- C7_SAFE + STATS_BOX=1, BATCH=0: 42.72M ops/s
- C7_SAFE + STATS_BOX=1, BATCH=1: 35.27M ops/s
- 判断: BATCH=1 は C7-only/Mixed とも大きく劣化。bench 専用に留め、標準は STATS_BOX=1 & BATCH=0または STATS_BOX=0とする。
標準設定 (v1)
--------------
- 推奨: PROFILE=C7_SAFE 時は `HAKMEM_TINY_STATS_BOX=1 HAKMEM_TINY_STATS_BATCH=0` を標準とし、レガシー比較時は STATS_BOX=0 でも可。
- Bench/実験: `HAKMEM_TINY_STATS_BATCH=1` は bench 専用C7-only/Mixed とも大幅マイナス。C7_ULTRA_BENCH で試すときのみ併用を許容。

View File

@ -0,0 +1,31 @@
# FINAL_PERF_STATUS_2025XX
箱理論で進めた v1 系の最終スナップショット。ベスト構成と残差、次の大きなテーマを一枚にまとめる。
## ベスト構成(デフォルト想定)
- Tiny: `HAKMEM_TINY_HEAP_PROFILE=C7_SAFE`, `HAKMEM_TINY_HOTHEAP_V2=0`, `HAKMEM_TINY_STATS_BOX=1`, `HAKMEM_TINY_STATS_BATCH=0`, `HAKMEM_TINY_LARSON_FIX=1`
- Pool: `HAKMEM_POOL_V2_ENABLED=0`(回帰防止のため v1 を標準)
- HugePage/ヘッダ-light/実験系: すべて OFF研究箱扱い
## 性能一覧(リリースビルド、代表プロファイル)
- C7-only (ws=64, iters=20k): ≈40.6M ops/s
- mimic/system との差: mimalloc ≈112.9M, system ≈92.1M → HAKMEM は mimic の ~36%、system の ~44%
- Mixed 161024B (ws=256, iters=20k): ≈3839M ops/sv2 OFF, C7_SAFE
- mimalloc/system は未計測同条件だが C7-only 比率から推定し ~40% 前後
- mid/smallmid (bench_mid_large_mt_hakmem 1 1,000,000 400 1, v2 OFF): ≈27.428.4M ops/s
- mimalloc ≈54.2M → HAKMEM は ~50% 近辺
## 埋めた「大きな穴」
- Tiny front/route フラット化size→class→route→alloc/free を 1 LUT+switch に縮約)
- Warm Pool / Superslab OS stats で pf/sys の大穴を特定し、OS alloc/free はほぼゼロ化
- SS_OS_STATS/Cold Stats Box で Superslab⇔OS/Stats を可視化し、デフォルトは A/B で安全側
- C7 SAFE TinyHeap を標準に固定v2 は研究箱。Tiny v2 は完全ゲート付きでデフォルト OFF
- Pool v1/v2 ゲート追加:`HAKMEM_POOL_V2_ENABLED=0` を標準にし、回帰を即切り戻せるようにした
## ここから先v3 テーマ候補)
- TinyHeap v2 をゼロから設計C5C7 を統合する HotHeap、現在の実験 v2 とは別物)
- First-touch / header-light / HugePage の本格対応bench 専用から昇格させるか検証)
- mid/smallmid の pool 系さらなる軽量化 or 別フロント
- Tiny front 以外mid/largeでの route フラット化・命令数削減
備考: v2 系・ヘッダ light・HugePage はすべて「明示的に ENV を立てた研究モード」のまま据え置き。標準ベンチ/比較は上記ベスト構成で見る。***

View File

@ -0,0 +1,51 @@
# FIRST_TOUCH_PAGEFAULT_REDUCTION_PLAN
## 現状サマリ(基準プロファイル: 161024B, ws=400, iters=1M, C7_SAFE, v2 OFF
- HAKMEM: ~6,600 page-faults / ~40.6M ops/s, user≈29 ms / sys≈18.5 ms
- mimalloc: ~150 page-faults / ~112.9M ops/s
- system malloc: ~130 page-faults / ~92.1M ops/s
- Superslab OS 呼び出しHAKMEM_SS_OS_STATS=1: alloc=2 / free=3 / madvise=2 → OS mmap/munmap は支配的でない
- WarmPool (C7): ヒット ~99% でほぼ効いている。残存 pf は first-write が主因と推定。
## 方針(研究用 / デフォルト OFF
- Mode A: Superslab を HugePage / 大きめサイズで確保し、first-write そのものを減らす実験
- Mode B: allocator 側の初期書き込み(ヘッダ/ゼロ埋めを減らし、first-touch をアプリ側に寄せる実験
- いずれも ENV で opt-in。デフォルトは OFF本番プロファイルは現状維持
## Mode A: HugePage / 大きめ Superslab 実験 Box実験実装済み
- Box 案: SuperSlabHugePageBoxCold 側で実験用)
- ENV:
- `HAKMEM_SS_HUGEPAGE_EXPERIMENT=1` で HugePage を試行(デフォルト 0
- `HAKMEM_SS_HUGEPAGE_SIZE` でページサイズ指定(未指定なら 2MB を仮定)
- 実装Phase52:
- ss_os_acquire が ENV ON のときだけ `MAP_HUGETLB``MAP_HUGE_2MB` も併用できる環境では付与)を試し、失敗時は静かに通常 mmap にフォールバック。Stats で huge_alloc / huge_fail を出力。
- Superslab サイズが 2MB と一致するときのみ HugePage を試す(その他サイズは従来経路)。
## Mode B: allocator 側 first-write 削減案
- ヘッダ書き込みを減らすオプションC5/C6/C7 region_id/guard ヘッダを実験的に省略)。安全性とトレードオフなので研究用 BOX (FirstTouchPolicyBox) に閉じ込める。
- ゼロ初期化の重複を洗う:
- どこで memset/clear をしているかTiny front / mid / Superslabを列挙し、アプリが必ず書く領域を allocator 側で二重初期化していないか確認。
- Box Theory ルール:
- ポリシーは FirstTouchPolicyBox に集約し、ホットパスは policy snapshot を読むだけにする。
- Tiny ヘッダ/初期化の 3 モードbench 専用 ENV 実装済み):
- `full`(デフォルト): region_id_write_header を常に書き戻す(従来どおり)。
- `light`: 既存ヘッダと一致する場合は再書き込みを避け、必要最小限だけ書く。
- `off`bench 専用): 既存ヘッダが正しければ書かず、壊れているときだけ最小限を書き戻すfree の整合性確保のための最低限。guard/memset もスキップ。
- ENV: `HAKMEM_TINY_HEADER_MODE=full|light|off`(未指定は full。旧 `HAKMEM_TINY_WRITE_HEADER=0``off` 相当として互換維持。
- 実験結果Mixed 161024B, ws=400, iters=1M, C7_SAFE, v2 OFF:
- HEADER_MODE=full: ≈42.40M ops/s, page-faults ≈6,662, cycles ≈176M。
- HEADER_MODE=light: ≈38.75M ops/s, page-faults ≈6,661, cycles ≈187M。
- HEADER_MODE=off: ≈39.33M ops/s, page-faults ≈6,662, cycles ≈184M。
- 所感:
- pf 回数は 3 モードとも ≈6.66k でほぼ同一。ヘッダ write 軽量化では first-touch page-fault は減らない。
- cycles/ops は full が最良で、light/off は判定・分岐コストの増加により約 7〜9% の性能低下。
- 現時点では運用デフォルトは full のままが最良。light/off は bench 専用の research モードとして維持し、本番プロファイルでは使用しない。
## 評価プロファイルpf/sys A/B 用に固定)
- プロファイル: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1`
- メトリクス: ops/s, page-faults, user/sysperf stat: cycles,instructions,task-clock,page-faults
- このプロファイルで Mode A/B を A/B し、pf がどこまで下がるかを見る。
## ガードレール
- HugePage / header-skip / zero-skip は研究専用でデフォルト OFF。ENV 明示なしでは有効化しない。
- 本番プロファイルに入れる場合は Fail-Fast / Guard明示 opt-inと長時間安定性テストを必須にする。

View File

@ -0,0 +1,46 @@
# MID/Large CPU Hotpath (Phase54)
- 条件: `./bench_mid_large_mt_hakmem 1 1000000 400 1` (Release)
`HAKMEM_TINY_HEAP_PROFILE=C7_SAFE`, `HAKMEM_TINY_C7_HOT=1`, `HAKMEM_TINY_HOTHEAP_V2=0`, `HAKMEM_TINY_LARSON_FIX=1`
- スループット: HAKMEM ≈28.1M ops/smimalloc 54.2M / system 15.3M は Phase53 より)
- perf stat (cycles:u): IPC≈2.4、page-faults≈7.4kPhase53 と同等)
## cycles:u ホットシンボルself%
- hak_pool_try_alloc.part.0 … 14.7% pool alloc ホットパス)
- worker_run … 9.2% (ドライバ側のループと malloc 呼び出しを含む)
- free / hak_free_at.constprop.0 … ~910%glibc free 連携+自前 free
- __memset_avx2_unaligned_erms … ~9%pool 初期化/clear と推定)
- mid_desc_lookup … 3.8%
- hak_super_lookup … 1.4%
- hak_pool_free.part.0 … 0.7%
## 所感
- pool 系hak_pool_try_alloc / hak_pool_freeと free/memset が支配的で、mid_desc_lookup と super_lookup も目立つ。
- kernel 枠の大きな inclusive% は free/memset 配下にぶら下がっており、userland 側の pool/front の命令数削減が効果的そう。
## Phase55 方針pool allocator ホットパス軽量化)
- スコープ: core/hakmem_pool.c / core/hakmem_smallmid.c / core/box/pool_* の pool fast path。
- hak_pool_try_alloc を直線化:
- 「TLS/local freelist hitなら即 return」を先頭に寄せ、debug/統計/slow は unlikely 側へ。
- mid_desc_lookup/クラス情報は入口で 1 回だけ計算し、TLS へのキャッシュを検討。
- hak_pool_free / hak_free_at:
- self-thread free は pool push を最優先にし、cross-thread/debug は unlikely に寄せる。
- free 時の memset/初期化が不要なケースを洗い出し、スキップ余地をメモ。
- mid_desc_lookup のキャッシュ:
- size→class→desc を入口で 1 回だけ決める仕組み(既存キャッシュの再利用も含め)を検討。
- 成功ラインbench_mid_large_mt_hakmem 1 1000000 400 1, Release):
- ops/s を 2829M → 3032M+5〜10%)へ。
- perf report で hak_pool_try_alloc + free 周辺 self% が数ポイント低下。
## Phase56 結果pool fast path 初期実装)
- 変更: PoolBlock→user 変換をヘルパに寄せ、TLS ring/lo pop と self-thread free push を直線化。owner 判定は mid_desc の 1 回 lookup で共有。
- ベンチC6-heavy, ws=256, iters=20k, C7_SAFE, v2 OFF: **25.9M ops/s**(目標 3032M に届かず、前回 2829M から回帰)。
- perf stat同条件 1M/400: cycles=225,766,496、instructions=528,335,613、task-clock=57.88ms、ops/s≈25.7M。
- 所感: fast-path整理だけでは効果薄く、むしろ低下。pool 内の memset/desc まわりやリング補充順序をより大胆に削らないと改善しない。次のステップとして追加の枝削減・キャッシュ導入を検討。
## Phase57 回帰トリアージpool v2 をゲート化)
- 変更: `HAKMEM_POOL_V2_ENABLED` を追加し、v1/v2 の pool 実装を env で切替。細分スイッチとして `HAKMEM_POOL_V2_BLOCK_TO_USER` / `HAKMEM_POOL_V2_TLS_FAST_PATH` を用意(デフォルト ON, v2 時のみ有効)。
- ベンチC6-heavy, 1M/400, Release:
- v1 (POOL_V2_ENABLED=0): **27.40M ops/s**
- v2 (POOL_V2_ENABLED=1): **24.73M ops/s**
- 所感: v2 の変更が回帰要因と判明。標準は v1 に戻し、スイッチ単位の A/B でどの改変が悪いかを切り分ける方針。

View File

@ -0,0 +1,29 @@
# Phase53: Mid/Smallmid ベンチ簡易計測(シングルスレッド)
- 条件: 1 thread / cycles=1,000,000 / ws=400 / reps=1
- ビルド: `make bench_mid_large_mt_hakmem bench_mid_large_mt_mi bench_mid_large_mt_system -j4`
- プロファイル: TinyHotHeap v2 OFF、C7_SAFE プロファイル、LARSON_FIX=1、その他デフォルト
## スループット比較
- HAKMEM: `./bench_mid_large_mt_hakmem 1 1000000 400 1`**28.43M ops/s**(別 run perf 時 29.02M
- mimalloc: `./bench_mid_large_mt_mi 1 1000000 400 1`**54.22M ops/s**
- system malloc: `./bench_mid_large_mt_system 1 1000000 400 1`**15.29M ops/s**
## perf stat (HAKMEM, user+sys)
- コマンド: `perf stat -e cycles,instructions,task-clock,page-faults ./bench_mid_large_mt_hakmem 1 1000000 400 1`
- 結果:
- cycles: 211,394,722
- instructions: 513,342,673 (IPC ≈ 2.43)
- task-clock: 57.48 ms (user 33.29 ms / sys 25.22 ms)
- page-faults: 7,374
- スループット: 29.02M ops/s計測 run の値)
## 所感 / 次の判断材料
- mid/smallmid (257768B主体) では mimalloc が HAKMEM の約 1.9×。system はさらに低い。
- page-faults は 7.3k と Tiny (161024B) よりやや多めだが、主因は CPU 側命令量と sys 部分の比率。
- 次の選択肢:
- mid/smallmid パスTiny 以外)のホットパスを狙う箱
- もしくは Tiny 側での残り課題と比較して優先度を決める材料に。

View File

@ -0,0 +1,205 @@
# Phase42 Front Route Flatten ベースライン計測
- ビルド: `make bench_random_mixed_hakmem -j4`
## C7-only (ws=64, iters=20000, v2 OFF)
- ENV: `HAKMEM_BENCH_C7_ONLY=1 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_STATS_BOX=1 HAKMEM_TINY_STATS_BATCH=0 HAKMEM_TINY_LARSON_FIX=1`
- 結果: `Throughput = 41365988 ops/s`
- 備考: HEAP_STATS のダンプは出ず要調査。FRONT_CLASS: cls7 alloc=11016。
## Mixed 161024B (ws=256, iters=20000, v2 OFF)
- ENV: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_FRONT_CLASS_STATS=1`
- 結果: `Throughput = 43538552 ops/s`
- 備考: FRONT_CLASS alloc countscls2:147, cls3:341, cls4:720, cls5:1420, cls6:2745, cls7:5692。HEAP_STATS ダンプは出ず。
## Tiny-only 8128B (ws=256, iters=20000, v2 OFF) ※任意測定
- ENV: `HAKMEM_BENCH_MIN_SIZE=8 HAKMEM_BENCH_MAX_SIZE=128 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_FRONT_CLASS_STATS=1`
- 結果: `Throughput = 65979164 ops/s`
- 備考: FRONT_CLASS alloc countscls2:147, cls3:341, cls4:720, cls5:9857。HEAP_STATS ダンプは出ず。
---
# Phase43 (HEAP_STATS 再有効化の再計測) ※v2 OFF
- HEAP_STATS 用 ENV を明示 (`HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1`) したが、現状はダンプが出ていない。fast/slow の値は別途要確認。
## C7-only (ws=64, iters=20000)
- ENV: `HAKMEM_BENCH_C7_ONLY=1 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_STATS_BOX=1 HAKMEM_TINY_STATS_BATCH=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1 HAKMEM_TINY_FRONT_CLASS_STATS=1`
- Throughput: `37,777,547 ops/s`
- HEAP_STATS cls7: fast=11015 slow=1 / free_fast_local=8726、C7_PAGE_STATS: prepare_calls=1 prepare_with_current_null=1
- FRONT_CLASS: cls7 alloc=11016 free=8726
## Mixed 161024B (ws=256, iters=20000)
- ENV: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1 HAKMEM_TINY_FRONT_CLASS_STATS=1`
- Throughput: `42,029,698 ops/s`
- HEAP_STATS cls7: fast=5691 slow=1 / free_fast_local=4573cls5/6 未出力)
- FRONT_CLASS alloc: cls2=147 cls3=341 cls4=720 cls5=1420 cls6=2745 cls7=5692
## Tiny-only 8128B (ws=256, iters=20000)
- ENV: `HAKMEM_BENCH_MIN_SIZE=8 HAKMEM_BENCH_MAX_SIZE=128 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1 HAKMEM_TINY_FRONT_CLASS_STATS=1`
- Throughput: `65,890,043 ops/s`
- HEAP_STATS: 出力なしcls7 も 0→ クラス0〜6は計測対象外の可能性。要調査。
### メモ
- C7-only では HEAP_STATS/PAGE_STATS が出ることを確認。Mixed/Tiny-only では cls7 以外の HEAP_STATS が落ちておらず、環境/計測対象の条件を再確認する余地あり。
# Phase43'': 計測専用で C6/C7 を TinyHeap に載せた場合v2 OFF
## Mixed 161024B (ws=256, iters=20000, class mask=0xC0, C6+C7)
- ENV: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HEAP_CLASSES=0xC0 HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_C6_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1 HAKMEM_TINY_FRONT_CLASS_STATS=1`
- Throughput: `32,227,500 ops/s`(性能は大幅に低下、計測専用)
- HEAP_STATS:
- cls6: fast=2744 slow=1 free_fast_local=2745
- cls7: fast=5691 slow=1 free_fast_local=4572
- FRONT_CLASS alloc: cls2=147 cls3=341 cls4=720 cls5=1420 cls6=2745 cls7=5692
## Tiny-only 8128B (ws=256, iters=20000, class mask=0xC0, C6+C7)
- ENV: `HAKMEM_BENCH_MIN_SIZE=8 HAKMEM_BENCH_MAX_SIZE=128 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HEAP_CLASSES=0xC0 HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_C6_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1 HAKMEM_TINY_FRONT_CLASS_STATS=1`
- Throughput: `63,341,251 ops/s`
- HEAP_STATS: 出力なし(対象クラスが 5 未満中心のため?)
### 所感
- C6 を TinyHeap 経由にすると Mixed で大きくマイナス32M。cls6/7 の slow はいずれも 1 なので、性能低下はフロント/ホットパスの命令数増が主因と見られる。
- 計測目的でのみクラスマスクを広げるのは有効だが、本番プロファイルは C7 のみ TinyHeap (0x80) を維持するのが安全。
# Phase48: first-touch/page-fault 実験用プロファイルの固定
- 評価は以下で統一し、pf/sys A/B の基準にする:
- `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1`
- perf stat メトリクス: ops/s, page-faults, task-clock(user/sys), cycles, instructions
- 目的: HugePage/first-touch 削減などの実験をこのプロファイルで比較し、pf がどれだけ減るかを確認する。
# Phase45: 161024B perf stat A/BHAKMEM vs mimalloc vs system, ws=400, iters=1,000,000, v2 OFF, C7 SAFE
共通ENV: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024`
| Allocator | Throughput (ops/s) | cycles | instructions | IPC | task-clock | user/sys | page-faults |
|-----------|-------------------:|-------:|-------------:|----:|-----------:|---------:|------------:|
| HAKMEM (C7_SAFE, v2 OFF) | 40,623,450 | 181,136,824 | 372,126,696 | 2.05 | 46.82 ms | 29.4 ms / 18.5 ms | 6,659 |
| mimalloc | 112,895,946 | 38,800,615 | 59,184,115 | 1.53 | 10.66 ms | 10.31 ms / 1.41 ms | 146 |
| system malloc | 92,111,481 | 52,144,974 | 107,607,240 | 2.06 | 12.54 ms | 12.27 ms / 1.36 ms | 133 |
所感/次手候補:
- HAKMEM は sys 時間と page-fault が顕著pf が mimallocの ~45x, sys 時間が高い。pf/sys 寄りのボトルネックが支配的に見える。
- 次の箱候補:
- pf/sys 改善を狙う: Superslab/Warm Pool/pagefault 周りのCold Box見直し。
- 命令数削減: front整理は済みなので、Tiny v1ホットパスや mid/large を軽くする路線も候補。
### メモ
- HEAP_STATSfast/slowのダンプが出ない状態。今後のベンチでは `HAKMEM_TINY_HEAP_STATS[_DUMP]` の有効化経路を再確認する必要あり。
# Phase46: pf/sys 内訳の初期確認HAKMEM, 161024B, ws=400, iters=1,000,000, v2 OFF, C7_SAFE
- `perf record -o perf.data.pf -e page-faults -- ./bench_random_mixed_hakmem 1000000 400 1`
- `perf report --stdio -i perf.data.pf`
- サマリ: page-fault サンプルの ~96% が `__memset_avx2_unaligned_erms`(初回タッチ系)。カーネル側の munmap/madvise 由来はほぼ見えず、残っている 6.6k faults は first-write が中心と推定。
- 基準プロファイル(以降 pf/sys A/B の基準にする想定):
- `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1`
- TODO / 次の箱候補:
- WarmPool / Superslab 再利用が十分効いているかの stats 確認(該当 ENV を後続で調査)。
- empty Superslab を即 OS 返却していないかのポリシー確認。
- pf/sys 系の変更は上記プロファイルで揃えて A/B を取る。
# Phase46': WarmPool stats 1 回だけ確認Mixed 161024B, ws=256, iters=20k, v2 OFF, C7_SAFE
- ENV: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_WARM_POOL_STATS=1`
- 結果stderr 抜粋):
- WarmPool-STATS: C7 hits=173 / misses=1 / prefilled=1約 99.4% ヒット。C2/C3/C4/C6 は warmup/prefill の 1 miss/1 prefill のみ。C0/C1/C5 は 0。
- WARM logs: `REL_C7_WARM_PREFILL calls=1 slabs=1`, `REL_C7_CARVE attempts=173 success=173`, `REL_C7_WARM pop=174 push=173`.
- Throughput: 43,809,979 ops/sws=256, iters=20k
- 所感:
- C7 は warm pool でほぼヒットしており、残り pf は first-write 優位という推定と矛盾しない。
- 他クラスの warm pool はほぼ未使用prefill 1 回のみ。Mixed での pf/sys は C7 以外の Superslab 利用状況も別途見る必要あり。
# Phase49: Mixed 161024B userland-only CPU パス観測v2 OFF, C7_SAFE
- ENV: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1`
- perf stat1M iters, ws=400, userlandのみ: throughput=41,000,086 ops/s, cycles=126,239,279, instructions=324,810,642IPC≈2.57, branch-misses=1,186,675, time=0.0438suser 0.0295s / sys 0.0143s
- perf recordcycles:u, 1M/400上位シンボルself%基準、子なし集計):
- free 24.3%libc wrapper経由の前段処理
- malloc 18.0%
- main 15.3%(ベンチハーネス)
- tiny_heap_page_pop 8.8%TinyHeapBox hot pop
- hak_super_lookup 7.9%frontからの Superslab 判定)
- tiny_heap_page_becomes_empty 5.9%empty 処理)
- __memset_avx2_unaligned_erms 4.0%(ユーザランド側の初期化)
- tiny_region_id_write_header 2.4%
- 所感: ベンチ前段の malloc/free/main が重いが、TinyHeapBox 側では pop / super_lookup / empty が目立つ。Phase50 では TinyHeapBoxpop/empty + super_lookup 周辺)の枝削減を優先候補にする。
# Phase47: Superslab OS ポリシーと OS 呼び出し可視化
- Superslab取得パス: LRU pop → 旧 cache pop → ss_os_acquire(mmap、必要なら MAP_POPULATE / prefault touch) の順。mmap 成功時に g_ss_mmap_count + SS_OS_STATS alloc を更新。
- Superslab解放パス: registry unregister → LRU push成功時は MADV_DONTNEED で lazy zero、今回 madvise カウント追加)→ 旧 cache push → どちらも満杯なら即 munmapSS_OS_STATS free を加算。ss_cache が容量縮小で munmap した場合も free をカウント。
- 新 ENV: `HAKMEM_SS_OS_STATS=1` で alloc/free/madvise を destructor 1 行でダンプ(`[SS_OS_STATS] alloc=… free=… madvise=…`。基準プロファイル161024B, ws=400, iters=1M, C7_SAFE, v2 OFFで 1 回回して OS 呼び出し回数を確認予定。
- SS_OS_STATS161024B, ws=400, iters=1M, C7_SAFE, v2 OFF: alloc=2 / free=3 / madvise=2 / mmap_total=2 → OS 呼び出しは少数。残る 6.6k pf は first-write 優位と見るのが妥当。
# Phase47: C6-heavy CPU ホットパスの初期観測v2 OFF, C7_SAFE
- ベンチ (ws=256, iters=20k, C6-heavy):
- ENV: `HAKMEM_BENCH_MIN_SIZE=257 HAKMEM_BENCH_MAX_SIZE=768 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_FRONT_CLASS_STATS=1 HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1`
- Throughput: `39,457,304 ops/s`
- FRONT_CLASS: cls6 alloc/free=5373、cls7 alloc=5692 free=4573
- HEAP_STATS cls7: fast=5691 slow=1、C7_PAGE_STATS prepare_calls=1
- perf stat (1M, ws=400, same ENV):
- Throughput: `41,295,709 ops/s`
- cycles=175,790,796 / instructions=368,496,560 (IPC≈2.10) / task-clock=42.36 ms (user 27.26 ms / sys 16.13 ms) / branch-misses=2,206,657
- perf record (cycles, 1M, ws=400):
- ホットスポットは匿名ページフォルト経路に集中(`__memset_avx2_unaligned_erms``handle_mm_fault``do_anonymous_page` が包含 60%超)。
- ユーザランド側では `free`/`malloc` が目立つがカーネルの first-touch が支配的。
- 所感: C6-heavy でも pf/sys が依然大きく、C6/Tiny フロントより先に first-touch/zero の扱いを考える必要があるprefault/warm 再利用の検討)。
# Phase50: Tiny ヘッダモード (full/light/off) A/Bfirst-touch 実験の一部)
- 目的: `tiny_region_id_write_header` のヘッダ書き込み/guard 初期化を軽量化することで page-fault / cycles がどこまで動くかを確認。
- 実装概要:
- ENV `HAKMEM_TINY_HEADER_MODE=full|light|off` を追加(未指定は full
- `full`: 従来どおり region_id ヘッダと guard/初期化を常に書き戻す。
- `light`: 既存ヘッダと一致する場合は再書き込みを避け、差分のみ最小限書く。
- `off`: 既存ヘッダが壊れている場合のみ最小限のヘッダを書き戻し、guard 呼び出しや追加初期化はスキップfree 整合性の最低限のみ保証)。旧 `HAKMEM_TINY_WRITE_HEADER=0` は off 相当として互換維持。
- `FIRST_TOUCH_PAGEFAULT_REDUCTION_PLAN.md` に 3 モードの設計と ENV を明記bench 専用 opt-in モードとして扱う)。
## Mixed 161024B (ws=400, iters=1M, PROFILE=C7_SAFE, v2 OFF, LARSON_FIX=1)
- 共通 ENV: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1`
- HEADER_MODE=full:
- Throughput: **42,399,649 ops/s**
- cycles: 176,079,783
- page-faults: 6,662
- task-clock: 42.16 msuser 25.1 ms / sys 17.8 ms
- HEADER_MODE=light:
- Throughput: **38,752,779 ops/s**
- cycles: 187,089,553
- page-faults: 6,661
- task-clock: 44.16 msuser 31.5 ms / sys 13.2 ms
- HEADER_MODE=off:
- Throughput: **39,330,655 ops/s**
- cycles: 183,795,801
- page-faults: 6,662
- task-clock: 43.43 msuser 29.3 ms / sys 15.2 ms
### 所感(ヘッダモード実験)
- page-fault 回数は full/light/off いずれも ≈6.66k とほぼ同一で、ヘッダ書き込み軽量化では first-touch page-fault は減らない。
- cycles/ops はむしろ full が最も良く、light/off はそれぞれ約 -9% / -7% 程度の性能低下。判定・分岐コストがヘッダ書き込み削減効果を上回っている。
- 結論として、現時点の実装では **運用デフォルトは full のままが最良**。light/off は bench 専用の実験モードとして残し、本番プロファイルでは使用しない前提とする。
# Phase52: Superslab HugePage 実験 (Mode A) 初期結果
- 目的: Superslab を HugePage (2MB) で確保する実験経路を追加し、page-fault / sys 時間がどこまで動くかを見る(研究専用)。
- 実装概要:
- ENV:
- `HAKMEM_SS_HUGEPAGE_EXPERIMENT=1` で HugePage 経路を opt-inデフォルト 0
- `HAKMEM_SS_HUGEPAGE_SIZE` でページサイズを指定(現状は "2M" 前提で実装)。
- `ss_os_acquire` 周辺に HugePage 試行を追加し、`MAP_HUGETLB | MAP_HUGE_2MB``mmap` を試し、失敗時は即通常の 1MB Superslab 経路にフォールバック。
- SS_OS_STATS に `huge_alloc` / `huge_fail` を追加し、`HAKMEM_SS_OS_STATS=1``[SS_OS_STATS] alloc=.. free=.. madvise=.. huge_alloc=.. huge_fail=..` を 1 行出力。
## Mixed 161024B (ws=400, iters=1M, PROFILE=C7_SAFE, v2 OFF, LARSON_FIX=1)
- 共通 ENV: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_SS_OS_STATS=1`
- HugePage OFF:
- Throughput: **41,464,379 ops/s**
- cycles: 180,862,510
- page-faults: 6,660
- SS_OS_STATS: `alloc=2 free=4 madvise=2 mmap_total=2 fallback_mmap=0 huge_alloc=0 huge_fail=0`
- HugePage ON (`HAKMEM_SS_HUGEPAGE_EXPERIMENT=1`, `HAKMEM_SS_HUGEPAGE_SIZE=2M`):
- Throughput: **41,372,856 ops/s**
- cycles: 177,305,948
- page-faults: 6,662
- SS_OS_STATS: `alloc=2 free=4 madvise=2 mmap_total=2 fallback_mmap=0 huge_alloc=0 huge_fail=0`
### 所感HugePage 実験初期結果)
- 現状の Superslab サイズ/条件では HugePage 経路が一度もヒットせず(`huge_alloc=0` / `huge_fail=0`)、通常 Superslab 経路のみが使用されている。
- そのため HugePage ON/OFF で throughput / cycles / page-fault はほぼ同一となり、初期実装の段階では効果は「ゼロに近い」。
- 2MB Superslab 専用クラスを設計するか、サイズ条件を緩めて HugePage を実際に使うかを決めないと、Mode A の効果は評価できない。
- 一方で pf はすでに ≈6.6k と小さく、たとえ HugePage でさらに削っても全体への寄与は限定的なため、現時点では **CPU ホットパス側の最適化を優先し、HugePage 実験は research-only の位置付けに留める** のが妥当と見える。

View File

@ -0,0 +1,18 @@
# C6-heavy Hotpath 分析 (Phase47, v2 OFF)
- プロファイル: `HAKMEM_BENCH_MIN_SIZE=257 HAKMEM_BENCH_MAX_SIZE=768 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1`
- ベンチ:
- `./bench_random_mixed_hakmem 20000 256 1``Throughput = 39,457,304 ops/s`
- FRONT_CLASS: cls6 alloc/free=5373、cls7 alloc=5692 free=4573
- perf stat (cycles,instructions,task-clock,branch-misses):
- `./bench_random_mixed_hakmem 1000000 400 1`
- cycles=175,790,796 / instructions=368,496,560 (IPC≈2.10)
- task-clock=42.36 ms (user 27.26 ms / sys 16.13 ms), branch-misses=2,206,657
- perf record (cycles, 1M, ws=400):
- 上位は匿名ページフォルト経路が支配的(`__memset_avx2_unaligned_erms``handle_mm_fault``do_anonymous_page``alloc_anon_folio` 系で包含 60%+)。
- ユーザランド側では `free`/`malloc` が目立つが、カーネル first-touch が主因。
## 次に削る箱(候補)
- 最重: first-touch/ゼロイング由来の匿名ページフォルト
- 候補策: class6 向けの温存ページ再利用 / prefault を増やし、ベンチの 1M/400 で pf/sys をさらに削る。
- Tiny v1 自体の命令数は二次的perf で目立たず。C6 を TinyHeap に載せるよりも、先に pf 削減箱を検討するのが妥当。

View File

@ -0,0 +1,34 @@
# TINY CPU Hotpath Userland Analysis (Phase49)
- プロファイル: Mixed 161024B, ws=400, iters=1,000,000
`HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1`
- コマンド: `perf stat -e cycles:u,instructions:u,branch-misses:u ./bench_random_mixed_hakmem 1000000 400 1`
`perf record -g -e cycles:u -o perf.data.mixed_u ./bench_random_mixed_hakmem 1000000 400 1`
## perf statuserlandのみ
- Throughput: **41,000,086 ops/s**
- cycles:u=126,239,279 / instructions:u=324,810,642 → IPC≈2.57
- branch-misses:u=1,186,675
- time=0.0438suser 0.0295s / sys 0.0143s
## perf recordcycles:u上位シンボルself%
- free: 24.3% — front入口/libc wrapper 部分
- malloc: 18.0% — 同上
- main: 15.3% — ベンチハーネス
- tiny_heap_page_pop.lto_priv.0: 8.8% — TinyHeapBox ホット pop
- hak_super_lookup.lto_priv.*: 7.9% — Superslab 判定front→TinyHeapBox 境界前)
- tiny_heap_page_becomes_empty.constprop.0: 5.9% — empty 遷移処理
- __memset_avx2_unaligned_erms: 4.0% — ユーザランド初期化first-touch
- tiny_region_id_write_header: 2.4% — header 書き込み
- その他: __pthread_self / hak_free_at / tiny_heap_meta_flush_page などが1〜2%台
## 所感
- ベンチ自身の malloc/free/main が大きいが、allocator 側では **tiny_heap_page_pop / hak_super_lookup / tiny_heap_page_becomes_empty** が目立つ。
- userland側でも memset が残っており、first-touch 削減(ヘッダ書き込み削減や初期化遅延)余地がある。
## Phase50 で削るターゲット箱(提案)
- **TinyHeapBoxC7/C6の pop/empty + hak_super_lookup 前段**
- super_lookup 依存の範囲チェックを軽量化 or キャッシュ化。
- pop/empty 内の分岐を整理し、C7 SAFE の理想パスcurrent_page固定に寄せる。
- header write / memset を最小化する実験スイッチを検討。

View File

@ -10,6 +10,32 @@ TinyHeapBox Design (C7 先行載せ替え)
の 1 箇所に集約し、ホットパスは TinyHeap 内で完結させる。
- A/B: `HAKMEM_TINY_HEAP_BOX=0` → 従来 Unified front、`=1` → TinyHeap front (いまは C7 のみ)。
TinyHeap Profile (ENV ショートカット)
--------------------------------------
- `HAKMEM_TINY_HEAP_PROFILE` を新設し、プロファイルで TinyHeap のデフォルト構成を切り替え可能にしたENV が未指定のときのデフォルトを決める箱)。
- LEGACY (デフォルト): TinyHeap OFFclass mask=0x00, meta_mode=0
- C7_SAFE: TinyHeap ON, class mask=0x80 (C7 のみ), C7 meta_mode=1SAFE`HAKMEM_TINY_HEAP_BOX` も自動 ON。C7_HOT は別途 1 にする。
- C7_ULTRA_BENCH: class mask=0x80, meta_mode=2ULTRA, bench専用`HAKMEM_TINY_HEAP_BOX` も ON。
- CUSTOM: `HAKMEM_TINY_HEAP_CLASSES` / `HAKMEM_TINY_C7_META_MODE` を直接指定する従来挙動。
- 優先順位:
1. `HAKMEM_TINY_HEAP_BOX=0` なら TinyHeap 無効。
2. `HAKMEM_TINY_HEAP_CLASSES` があればそのビットマスクを最優先。
3. 無ければ `HAKMEM_TINY_HEAP_PROFILE` からデフォルト mask/meta_mode を決める。
4. `HAKMEM_TINY_C7_META_MODE` があれば C7 meta_mode はそれを優先(無ければ profile→旧 `HAKMEM_TINY_C7_META_LIGHT` の順)。
- 推奨フラグ例:
- C7-only 比較: `HAKMEM_BENCH_C7_ONLY=1 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_LARSON_FIX=1`
- Mixed 161024B: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1`C6 TinyHeap はデフォルト OFF のまま)
標準プロファイル (v1 締め)
--------------------------
- 推奨候補:
- LEGACY … TinyHeap 全無効(基準)。
- C7_SAFE … class mask=0x80, meta_mode=1SAFEに加え `HAKMEM_TINY_STATS_BOX=1 HAKMEM_TINY_STATS_BATCH=0` をセットするのが標準。C6 は OFF。
- bench/実験専用:
- C7_ULTRA_BENCHmeta_mode=2、C6 TinyHeap マスク (0x40/0xC0)、`HAKMEM_TINY_STATS_BATCH=1`
- デフォルト運用: PROFILE=LEGACY か PROFILE=C7_SAFE を手で選択し、C6 は明示しない限り OFF。詳細の次ステップ案は `docs/analysis/TINY_NEXT_STEPS.md` に記載。
- Phase 20 追加: C6 Hot front を C7 と対称に導入ENV `HAKMEM_TINY_C6_HOT=1` + class mask で有効化。Front を薄くしても C6 TinyHeap は依然回帰が大きい。C6 は v1 では bench/実験用の位置づけに凍結。
主要構造 (core/box/tiny_heap_box.h)
-----------------------------------
- `tiny_heap_page_t`: ページ内 freelist/used/capacity と Superslab メタ (ss/meta/slab_idx/base) を保持。
@ -37,6 +63,7 @@ Front Gate (core/front/malloc_tiny_fast.h)
------------------------------------------
- alloc: class_idx==7 & size==1024 かつ TinyHeapBox ON で `tiny_c7_alloc_fast()` に直行。それ以外は Unified Cache front。
- free : Larson owner 判定に関係なく、TinyHeapBox ON + class7 なら meta 渡し free (`tiny_c7_free_fast_with_meta`) を優先。ss_fast_lookup 失敗時は `tiny_c7_free_fast()` で安全側。
- Phase16: Route Snapshot Box (`tiny_route_env_box.h`) を追加し、起動時に `g_tiny_route_class[cls]` に TinyHeap/Legacy を確定。`malloc_tiny_fast` / `free_tiny_fast` は「size→class→route LUT→heap or legacy」の 1 分岐構造に整理。`HAKMEM_TINY_FRONT_CLASS_STATS(_DUMP)=1` で front クラス分布も取得可能。
C7 は TLS SLL を使わないTinyHeap front ON 時)
-----------------------------------------------
@ -128,8 +155,78 @@ Phase 9: Tiny lane 判定の整理TinyHeap を「成功」とみなす)
- Mixed 161024B: OFF≈47.6M / C7のみ TinyHeap≈36.9M / C6+C7≈30.3M(警告なし)。
- 今後: TinyHeap route の性能はまだ OFF より下がるケースがあるため、slow_prepare/hit 率の改善や C6 向け最適化を別フェーズで実施する。
Phase ULTRA: C7 meta モードの三段化bench 専用 ULTRA 追加)
------------------------------------------------------------
- ENV `HAKMEM_TINY_C7_META_MODE` を追加0:OFF, 1:SAFE meta-light=従来の delta+閾値 flush/clamp, 2:ULTRA。従来の `HAKMEM_TINY_C7_META_LIGHT` は mode 未指定時の後方互換ゲートとして残し、mode を指定しない場合は SAFE=1 相当。
- ULTRA (mode=2) は C7-only ベンチ専用。per-alloc で meta->used / ss_active_* を一切更新せず、delta/flush もスキップする。2024-xx-xx 時点で pop/push の meta->freelist/carved atomic も省略してオーバーヘッドを削減Box 境界は維持するが Superslab/Tier の統計整合性は保証しない)。
- SAFE (mode=1) はこれまでのページ境界 flush + 閾値 flush + attach clamp を維持し、本番で使うならこちら。
- ベンチ (C7-only 20k/ws=64, Release, HEAP_BOX=1, HEAP_CLASSES=0x80, HOT=1, LARSON_FIX=1):
- mode=0 (meta-light OFF): ≈38.7M ops/salloc_fast_current=10052 / alloc_slow_prepare=7681
- mode=1 (SAFE): ≈34.1M ops/salloc_fast_current=5837 / alloc_slow_prepare=5179
- mode=2 (ULTRA, bench): ≈41.6M ops/salloc_fast_current=5948 / alloc_slow_prepare=5068
- C7 current_page 固定化ULTRA 専用):
- ULTRA で tiny_heap_page_becomes_empty / mark_full の unlink を避け、free で常に current_page に据え置くように調整。
- prepare は current_page が生きていれば即 return する軽量パスに変更し、C7-only 20k/ws=64 (mode=2) で ops≈52.0M / alloc_fast_current=11015 / alloc_slow_prepare=1 / prepare_calls=1 まで改善。
- SAFE への逆輸入可否は別途検討ULTRA は bench 限定、本番は OFF/SAFE を推奨)。
- C7 current_page 可視化: `g_c7_page_stats`prepare_calls / prepare_with_current_null / prepare_from_partial / current_set_from_free / current_dropped_to_partial`HAKMEM_TINY_C7_HEAP_STATS=1` で出力。現状 C7-only ULTRA では `prepare_with_current_null=prepare_calls` で free 側が current を保持できていないことが判明(要ポリシー見直し)。
- 本番ドキュメントでは ULTRA を必ず OFFmode=0/1にすること。ULTRA で得た知見は安全側に逆輸入する前提。
Phase 12: SAFE (mode=1) への current_page 逆輸入
-------------------------------------------------
- SAFE でも C7 current_page を極力保持するように変更:
- free: used>0 かつ free_list がある page は current に据え直し、empty でも delta を flush したうえで detach/publish せず current を保持meta/active 整合性は維持)。
- mark_full: C7 meta_light が current を指す場合は unlink せず据え置き。
- prepare_page: C7 meta_light で current に空きがあれば即 returnrefill へ降りない)。
- ベンチ (C7-only, ws=64, HEAP_BOX=1, HEAP_CLASSES=0x80, HOT=1, LARSON_FIX=1):
- SAFE mode=1 (meta-light) 20k: ≈46.6M ops/s、alloc_fast_current=11015 / alloc_slow_prepare=1 / free_fast_local=8726、C7_PAGE_STATS: prepare_calls=1。
- SAFE 長時間: 100k≈46.7M、200k≈44.99M。`HAKMEM_TINY_C7_DELTA_DEBUG=1` でも `[C7_DELTA_SUMMARY] nonzero_pages=0 used_delta_sum=0 active_delta_sum=0` を確認。
- ULTRA(mode=2) は bench 専用のまま。SAFE は meta/active の整合性を保ちつつ slow_prepare をほぼゼロに抑える構成になった。
今後の拡張ステップ
------------------
- C5〜C6 を TinyHeapBox に移す際は `tiny_heap_alloc_class_fast()` を流用し、Box 境界 (ページ補給/返却) の 1 箇所化を維持する。
- `TINY_HEAP_MAX_PAGES_PER_CLASS` の調整と stats/Tier 更新の扱いは次フェーズで検証。
- TLS SLL は今後 C0〜C6 のための箱として維持し、C7 は TinyHeapBox↔Superslab/Tier/Guard の二層構造に限定する。
Phase 13: クラス汎用 TinyHeap stats + C6/C7 混在ベンチ
----------------------------------------------------
- Stats をクラス配列 `TinyHeapClassStats g_tiny_heap_stats[TINY_NUM_CLASSES]` に拡張。ENV は `HAKMEM_TINY_HEAP_STATS` / `_DUMP`(従来の `_C7_` も互換。fast/slow/fallback/prepare_fail/fail をクラスごとに記録できる。
- Mixed 161024B (iters=20k, ws=256, LARSON_FIX=1):
- TinyHeap OFF: ≈43.7M ops/s。
- C7 SAFE のみ TinyHeap (`HEAP_BOX=1 HEAP_CLASSES=0x80 META_MODE=1 HOT=1`): ≈44.9M ops/s、`HEAP_STATS[7] fast=5691 slow_prepare=1`
- C6+C7 TinyHeap (`HEAP_CLASSES=0xC0` 同条件): ≈39.3M ops/s、`HEAP_STATS[6] fast=2744 slow=1`, `HEAP_STATS[7] fast=5691 slow=1`
- C6 偏重 (min=257 max=768, iters=20k, ws=256):
- TinyHeap OFF: ≈43.8M ops/s。
- C6 TinyHeap のみ (`HEAP_CLASSES=0x40`, C7 legacy): ≈38.5M ops/s、`HEAP_STATS[6] fast=5372 slow=1`
- C6+C7 TinyHeap (`HEAP_CLASSES=0xC0`, C7 SAFE): ≈40.6M ops/s、`HEAP_STATS[6] fast=5372 slow=1`, `HEAP_STATS[7] fast=5691 slow=1`
- 示唆:
- C7 SAFE は mixed でも悪化せず、C7-only では legacy 超え → デフォルトで TinyHeap に載せる候補。
- C6 は slow_prepare はほぼ 0 でも経路オーバーヘッドで throughput が落ちるため、当面は bench/実験用 (HEAP_CLASSES=0x40/0xC0) に留める。C6 を本格移行するなら current_page ポリシーや meta-light 相当の最適化が必要。
- 推奨例: 本番寄せ C7 = `HEAP_BOX=1 HEAP_CLASSES=0x80 META_MODE=1`、C7-only bench = `META_MODE=2`ULTRA, bench 専用。C6 TinyHeap はデフォルト OFF のまま。
Phase 18: C6 SAFE 再計測とメタモード足場(挙動は安全寄せのまま)
----------------------------------------------------------------
- ENV `HAKMEM_TINY_C6_META_MODE` を追加0/1。現状は整合優先のため behavior mode=0 扱いC6 は meta/active を per-alloc 更新、delta/flush は未使用。C6 を TinyHeap に載せるときは `HAKMEM_TINY_HEAP_BOX=1 HAKMEM_TINY_HEAP_CLASSES=0x40/0xC0` を明示。
- ベンチ (Release, iters=20k, ws=256, LARSON_FIX=1):
- C6 偏重 (257768B):
- LEGACY (TinyHeap OFF): ≈44.28M ops/s。
- C6 TinyHeap mask=0x40, META_MODE=0: ≈38.81M ops/scls6 fast=5372 / slow_prepare=1
- C6 TinyHeap mask=0x40, META_MODE=1: ≈38.59M ops/s同じく slow_prepare≒1
- C6+C7 TinyHeap mask=0xC0, C6 META=1 / C7 META=1: ≈39.94M ops/scls6 fast=5372/slow=1, cls7 fast=5691/slow=1
- Mixed 161024B:
- LEGACY: ≈44.27M ops/s。
- PROFILE=C7_SAFE (mask=0x80, C7 META=1): ≈43.64M ops/s。
- C6 TinyHeap mask=0x40, META_MODE=0: ≈38.48M ops/scls6 fast=2744/slow=1
- C6 TinyHeap mask=0x40, META_MODE=1: ≈38.66M ops/scls6 fast=2744/slow=1
- C6+C7 TinyHeap mask=0xC0, C6 META=1 / C7 META=1: ≈39.49M ops/scls6 fast=2744/slow=1, cls7 fast=5691/slow=1
- メモ: C6 は slow_prepare がほぼゼロでも回帰しているため、次は meta-light の安全な適用か Gate/Route 側の命令削減を検討。現状の C6 SAFE は「挙動は mode 0 相当(安全重視)」で、ベンチ比較用のスイッチとして位置付け。
Phase 17: C7 フロント超直線化Gate→Heap を 1 本に)
---------------------------------------------------
- Route Snapshot ベースのヘルパ `tiny_c7_front_uses_heap()` を追加し、class7 が TinyHeap route かどうかを 1 LUT で判定。
- `malloc_tiny_fast` 冒頭に C7 専用パスを追加: `size==1024 && tiny_c7_front_uses_heap()` のとき class/LUT/route を飛ばして `tiny_c7_alloc_fast` へ直行、miss 時だけ `tiny_cold_refill_and_alloc(7)` に静かにフォールバック。他クラスは従来の route LUT 経由を維持。
- `free_tiny_fast` も class7 が Heap route のときに先に判定し、Larson owner 一致後は `tiny_c7_free_fast_with_meta` へ直行route はスナップショットから 1 回だけ読む。C7 以外は既存の Larson/SLL/UC 経路を継続。
- ベンチ (Release, iters=20k, LARSON_FIX=1):
- C7-only ws=64: PROFILE=LEGACY ≈37.1M / C7_SAFE ≈38.2M / C7_ULTRA_BENCH ≈45.3M ops/s。
- Mixed 161024B ws=256: PROFILE=LEGACY ≈40.3M / C7_SAFE ≈40.7M ops/s回帰を ~-1M まで圧縮)。
- 次の選択肢メモ: (A) C6 TinyHeap を C7 SAFE 流current 固定meta-light SAFEに寄せて再評価するか、(B) Tiny front/gate/UC の命令数削減を perf で詰めるかを検討。

View File

@ -6,6 +6,13 @@ TinyHeap v2 Design (入口メモ)
- C7 専用で「1 枚 lease + current/freelist を v2 で握る」状態。ページ供給・meta/ss_active/Remote/Stats はすべて v1 TinyHeap/C7 SAFE に委譲。
- A/B でいつでも v1 に戻せる(`HAKMEM_TINY_HOTHEAP_V2` / `HAKMEM_TINY_HOTHEAP_CLASSES`)。性能はまだ v1 と同等を維持するのが目的。
Phase33: C7 v2 ON/OFF A/B現状評価
-----------------------------------
- 条件: C7 SAFE (`PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_LARSON_FIX=1`), class mask 0x80, HEAP_STATS=ON。
- C7-only (ws=64, iters=20k): v2 OFF **39.42M ops/s** / v2 ON **43.55M ops/s**cls7 fast=11015 / slow=1、v2 alloc/free カウンタ増加)。
- Mixed 161024B (ws=256, iters=20k): v2 OFF **40.44M ops/s** / v2 ON **36.58M ops/s**cls7 fast=5691 / slow=1、v2 カウンタ増加)。
- 現状の v2 は current/freelist を自前化しつつ、pop/free は v1 の `tiny_heap_page_pop/free_local` を呼ぶラッパ。C7-only はわずかにプラスだが、Mixed では lease 判定や v1 呼び出し重複のオーバーヘッドが目立つ。Phase34 で枝/ロード削減を検討。
ゴール
-----
- mimalloc の heap→page→block に近い形で C5/C6/C7 を 1 つの TinyHotHeap に統合し、Gate/UC/TLS-SLL をさらに薄くする。
@ -93,3 +100,236 @@ Phase33 以降の選択肢
--------------------
- A) v2 で current_pagefreelist の多ページ対応・ページ返却まで拡張し、Superslab への触れ方を v2 専用に寄せるv1 はページ供給だけの Box にする)。
- B) 当面 v2 は C7 限定のラッパ単一ページ管理のまま据え置き、mid サイズや他クラスの箱に時間を回す。
Phase34: C7-only 専用モードに一旦限定& v2 用 stats 追加
------------------------------------------------------
- 方針: `HAKMEM_TINY_HOTHEAP_V2` は当面 **C7-only ベンチ/実験専用**。Mixed 161024B では v2 OFF 推奨v1 C7_SAFE を使用)。
- v2 専用 statsENV: `HAKMEM_TINY_HOTHEAP_V2_STATS=1`)を追加:
- `alloc_calls / alloc_fast / alloc_lease / alloc_fb_v1`
- `free_calls / free_fast / free_fb_v1`
- destructor で `[HOTHEAP_V2_C7_STATS]` を 1 行 dump。
- これで Mixed 時に「v2 がどこまでヒットしているか」「どれだけ v1 へフォールバックしているか」を切り分ける予定。
- 次フェーズ候補:
- パターンA: Mixed でも v2 のヒット率が高い → 枝/チェックの純コスト削減hot path flattenを検討。
- パターンB: Mixed で v1 fallback が多い → v2 を C7-heavy 専用に割り切る or C7 判定をさらに絞る。
Phase35: v2 をいったん棚上げ(標準は v1+C7_SAFE に集中)
---------------------------------------------------------
- 観測結果: C7-only/Mixed いずれも v2 ON で大幅に劣化alloc_fast がほぼ当たらず、lease→v1 fallback が支配)。
- 運用方針:
- `HAKMEM_TINY_HOTHEAP_V2=0` を標準C7_SAFE v1 がデフォルト)。
- v2 は C7-only の研究用に **明示的に ON** したときだけ使用。Mixed では OFF 推奨。
- 設計メモ:
- 現行 v2 は v1 へのフォールバック前提で、Hot path の枝コストと fallback 多発が主因で負けている。
- 続けるなら current/freelist を完全に自前管理し、Superslab との境界だけ v2 専用に切る再設計が必要。
Phase36+: TinyHotHeap v2 を「Hot Box」として再定義する
====================================================
背景(棚上げからの再出発)
--------------------------
- Phase35 までの v2 は「v1 TinyHeap/C7 SAFE の上に乗るラッパ」であり、
- ページ供給・meta/ss_active/Remote/Stats はすべて v1 に委譲
- current/freelist も最終的には v1 の API を介して触る構造
だったため、**Mixed では構造的に v1 に勝てない** 状態だった。
- Box Theory 的に見ると、
- v2 の Hot 部と v1 TinyHeap の境界が曖昧(箱が二重になっている)
- Cold BoxSuperslab/Tier/Statsとの接点も v1/v2 の両方に分散
しており、「境界 1 箇所」の原則から外れている。
- そこで Phase36 以降は:
> **TinyHeap v2 自体を per-thread Hot Box として再定義し、
> Superslab / Tier / Remote / Stats / Learning をすべて外側の Cold Box に落とす**
という方針に切り替える。
3 つの設計案A/B/C
--------------------
ここからの v2 には、Tiny 層をどこまで巻き取るかで 3 案ある。
1. A案: C5〜C7 を 1 つの TinyHotHeap に統合
- `TinyHeapCtx`per-thread`TinyClassHeap`per-classを定義し、
current/partial/full/page freelist を 1 箱にまとめる標準案。
- Superslab/Warm/Tier/Guard との境界は
- alloc 側: `tiny_heap_refill_slow(th, ci)`
- free 側: `tiny_heap_page_retire_slow(th, page)`
だけに集約する。
2. B案: C7 超ホットレーン + C5/C6 セミホットレーン
- A案の上に「C7 専用 fast lane (`c7_fastlist + c7_current`)」を乗せて、
mimalloc クラスの C7-only 性能を狙う二車線構造。
3. C案: mimalloc 風 Segment+Page+Block をフル導入
- Superslab/Tier/Guard を 1 つの Segment Box として再定義し、
TinyHotHeap v2 の下に「Segment→Page→Block」の階層を敷き直す v3 相当案。
このドキュメントでは **A案TinyHotHeap v2 = Hot Boxを第一候補** とし、
実装を進めやすい形に具体化する。B/C 案は A が安定したあとに検討する。
TinyHotHeap v2 の箱構造A案
------------------------------
Box Theory 観点での分割は次の通り:
- Hot Box: TinyHotHeapBox v2per-thread
- 中身: `TinyHeapCtx` / `TinyClassHeap` / `TinyPageMeta`
- 役割: heap→page→block のみを扱い、**Tiny 層の命令数を支配する唯一のホット層**。
- Cold Box: Superslab/Segment + Tier + Guard + Remote
- 役割: 大きなチャンク管理、Tier/HOT/DRAINING/FREE 状態管理、Remote Queue、Guard/Budget。
- Hot からは `refill_slow` / `page_retire_slow` でしか触らない。
- Policy Box: TinyPolicySnapshot
- 役割: クラスごとの `heap_enabled` / `max_partial_pages` / `warm_cap` などを保持。
- Hot は「現在のスナップショットを読むだけ」で、学習の有無を意識しない。
- Stats Box / Learning Box:
- 役割: page 単位の delta を受け取り、Cold 側で集計・可視化・学習を行う。
- Hot は「page を refill/retire するとき」にだけ delta を渡す。
Hot Box のデータ構造(案)
--------------------------
```c
// ページ内メタデータ(ホット側が見る最小限)
typedef struct TinyPageMeta {
void* free_list; // ページ内の block 単位 LIFO freelist
uint16_t used; // 使用中 block 数
uint16_t capacity; // page あたりの総 block 数
uint16_t class_idx; // Tiny class (C5〜C7)
uint16_t flags; // HOT/PARTIAL/FULL など
void* slab_ref; // Superslab/Segment 側への back pointerCold Box 用)
} TinyPageMeta;
// クラスごとの Hot heap 状態
typedef struct TinyClassHeap {
TinyPageMeta* current; // 直近で使用中の page
TinyPageMeta* partial_head; // 空きあり page のリスト
TinyPageMeta* full_head; // full pageレアイベントのみ参照
} TinyClassHeap;
// TLS ごとの TinyHotHeap コンテキスト
typedef struct TinyHeapCtx {
TinyClassHeap cls[8]; // とりあえず C0〜C7。v2 では C5〜C7 を優先して有効化
} TinyHeapCtx;
```
Hot パス APIalloc/free
-------------------------
### allocHot
```c
void* tiny_heap_alloc_fast(TinyHeapCtx* th, uint32_t ci) {
TinyClassHeap* h = &th->cls[ci];
TinyPageMeta* p = h->current;
// 1. current page から pop
if (likely(p && p->free_list)) {
void* block = p->free_list;
p->free_list = *(void**)block;
p->used++;
return block;
}
// 2. partial list から page を 1 枚取って current に昇格
if (h->partial_head) {
p = h->partial_head;
h->partial_head = p->next; // intrusive list 想定
h->current = p;
// 取り直した current から改めて pop
...
}
// 3. ここで初めて Cold Box に降りる
return tiny_heap_refill_slow(th, ci);
}
```
### freeHot
```c
void tiny_heap_free_fast(TinyHeapCtx* th, void* ptr) {
TinyPageMeta* p = tiny_page_of(ptr); // base から逆算 or side meta
// ページ内 freelist に push
*(void**)ptr = p->free_list;
p->free_list = ptr;
p->used--;
if (unlikely(p->used == 0)) {
// page 完全 free → Cold Box に返却
tiny_heap_page_retire_slow(th, p);
}
}
```
Hot→Cold 境界1 箇所ルール)
------------------------------
- `tiny_heap_refill_slow(th, ci)`
- Warm/Shared/Superslab/Segment/Tier/Guard に対して
「class ci 用の page を 1〜数枚ください」と要求する唯一の関数。
- 返ってきた page 群を `current` または `partial_head` に接続するだけで、
meta->used / ss_active_* / Tier 状態は Cold Box 側が責任を持つ。
- `tiny_heap_page_retire_slow(th, p)`
- `p->used == 0` のときだけ呼ぶ。
- page の返却/再利用Warm Pool への push や Superslab への返却)と、
Cold Stats/Guard の更新をまとめて処理する。
この 2 関数だけが Hot→Cold の境界となるため、
Box Theory の「境界 1 箇所」に近い構造でデバッグ/A/B を行える。
Stats / Learning の配置
----------------------
- Hot Box:
- `TinyPageMeta` に軽量なローカルカウンタのみを持たせる(例: `uint16_t local_allocs;` など)。
- `tiny_heap_refill_slow` / `tiny_heap_page_retire_slow` のタイミングで
page 単位の delta を Cold Stats Box に flush する。
- Cold Stats Box:
- Superslab/Segment/class 単位の累積 stats を保持。
- Learning Box からのみ読まれ、Hot Box は直接読まない。
- Learning Box:
- 一定周期で Stats から `TinyPolicySnapshot` を更新し、
`heap_enabled[class]` / `max_partial_pages[class]` / `warm_cap[class]` を調整する。
- Hot Box は `ci` 決定後に snapshot を読むだけで、
Learning の ON/OFF によって命令数が変わらないようにする。
ENV / Gate / A/B 方針v2 再開時のルール)
-----------------------------------------
- ENV:
- `HAKMEM_TINY_HOTHEAP_V2` … v2 Hot Box を有効化(デフォルト 0
- `HAKMEM_TINY_HOTHEAP_CLASSES` … v2 適用クラスの bitmask。初期は C7-only (=0x80)、
様子を見ながら 0xC0(C6+C7)→0xE0(C5〜C7) へ広げる。
- Route Snapshot:
- `g_tiny_route_class[ci]``TINY_ROUTE_HOTHEAP_V2 / TINY_ROUTE_HEAP_V1 / TINY_ROUTE_LEGACY` を設定。
- Gate は `size→class→route` を 1 LUT + 1 分岐で決める薄い front を維持する。
- A/B:
- C7-only: `HAKMEM_TINY_HOTHEAP_V2=1 HAKMEM_TINY_HOTHEAP_CLASSES=0x80` で v2 経路に切り替え、
`=0` で v1 C7_SAFE に戻せるようにする。
- Mixed 161024B: 当面は v1 C7_SAFE を基準とし、C7-only が安定してから C6/C5 を昇格させる。
将来の C 案Segment+Page+Blockへの橋渡し
------------------------------------------
- 上記 A 案TinyHotHeap v2 = Hot Boxは、
mimalloc 風の Segment+Page+Block 構造C 案)の **Hot 部分** とほぼ一致している。
- 今後 C 案へ進める場合も、
- Hot Box の API (`tiny_heap_alloc_fast/free_fast/refill_slow/page_retire_slow`) は変えず、
- Cold Box 側で Superslab/Tier/Guard を「Segment Box」として整理する
ことで、段階的に v3 へ移行できる。
- このため、Phase36 以降の v2 実装は **「C 案の Hot 部だけを先に完成させる」** 方針で進める。
次にやること(実装タスクの箱化)
--------------------------------
- docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md に、
実装手順C7-only → C5〜C7 への拡張)と関数名/API の具体案をまとめる。
- CURRENT_TASK.md に Phase36 として「TinyHotHeap v2 再定義Hot Box 化)」を追加し、
v2 の現状ステータスと今後のタスク窓口をこのドキュメントに集約する。
Phase36 (C7-only Hot Box 暫定実装)
----------------------------------
- 状態: v2 は C7-only 実験箱のまま。デフォルトは `HAKMEM_TINY_HOTHEAP_V2=0`v1 C7_SAFE が標準)。
- 実装ポイント:
- TLS 取得で全 class の storage_page を resetstride 初期化。page node は storage を優先し、足りなければ calloc。
- Hot パス: `tiny_hotheap_v2_alloc/free` が current_page → partial → refill を処理し、lease した v1 page を pop/push して meta/ss を同期。used==0 は retire_slow でリセット。
- Cold 境界: `tiny_hotheap_v2_refill_slow``tiny_heap_c7_lease_page_for_v2()` 経由で 1 枚借りる)と `tiny_hotheap_v2_page_retire_slow`返却resetの 2 箇所のみ。Superslab/Tier/Stats は v1 が保持。
- Route/Gate: Route Snapshot に `TINY_ROUTE_HOTHEAP_V2` を設定し、C7 直線パス/汎用パスとも route==v2 のときだけ HotHeap v2 を呼ぶ。その他は v1 → legacy slow へフォールバック。
- 今後: v2 は C7-only bench 用の A/B 前提。Mixed では v2 OFF 推奨。より本格的に進めるなら、v1 依存の lease/pop/push を減らし、Cold Stats Box 経由の更新に寄せる必要がある。

View File

@ -0,0 +1,46 @@
Tiny Next Steps (Phase 19 メモ)
===============================
事実スナップショット
--------------------
- 標準プロファイル (v1 締め)
- LEGACY … TinyHeap 全無効。
- C7_SAFE … class mask=0x80, meta_mode=1 に加え `HAKMEM_TINY_STATS_BOX=1 HAKMEM_TINY_STATS_BATCH=0` をセット。C6 は OFF。
- Bench/実験 … C7_ULTRA_BENCH、C6 TinyHeap マスク (0x40/0xC0)、`HAKMEM_TINY_STATS_BATCH=1` は bench 専用。
- プロファイルと性能
- C7_SAFE: C7-only 20k/ws64 ≈46.6M ops/s、Mixed 161024B は LEGACY と ±1M 以内軽いマイナス〜誤差。C7-heavy 向けの本番寄りプロファイル。
- C7_ULTRA_BENCH: C7-only 20k/ws64 ≈52M ops/sbench 専用、Superslab/Tier 整合は緩む)。
- C6 TinyHeap: C6-heavy (min=257/max=768) LEGACY≈44.3M → TinyHeap≈38.6M。Mixed 161024B でも ≈38.5M〜39.5M と明確なマイナス。slow_prepare は ≒1 でも遅く、経路オーバーヘッドが支配的。
- Cold Stats Box: STATS_BOX=1 は C7 SAFE で同等〜わずかプラス、BATCH=1 は C7-only/Mixed とも大幅マイナスbench 専用扱い)。
- クラス分布の目安HEAP_STATS, ws=256/iters=20k
- Mixed 161024B + C7 SAFE: cls7 fast≈5691 / slow≈1。C6 を載せた場合でも cls6 fast≈2744 / slow≈1 と slow_prepare は低い。
- C6-heavy: cls6 fast≈5372 / slow≈1。slow_prepare が少なくても throughput は低下。
- 結論(現状)
- C7 SAFE は C7-only で明確なプラス、Mixed でも許容レンジ → 推奨プロファイル候補。
- C6 TinyHeap は現状ベンチ/実験専用。デフォルトでは OFFmask=0x80 のまま)。
次の箱候補2 本に絞る)
-----------------------
1) C6 TinyHeap を C7 SAFE 流に攻める箱
- やること: current 固定・slow_prepare 抑制、meta-light/delta/閾値 flush の安全版を C6 に移植。Superslab/Tier の整合を Fail-Fast で確認しながら命令を削る。
- リスク: 実装が複雑化するため delta debug/assert を増やす必要あり。効果が出なければ C6 TinyHeap は Bench 専用のまま。
2) Tiny front をさらに薄くする箱
- 方向: Route Snapshot の上に C6/C7 用の直線 frontC7 HotPipeline 相当を広げ、Gate/UC/TLS SLL 経路の命令数を削る。
- 位置づけ: C6 TinyHeap 攻めとほぼ同じ課題の裏表。C6 current 固定+軽量 meta と合わせて検討。
3) Cold Tiny Stats Box を詰める箱
- 役割: C7 SAFE で始めた page 内 delta/flush を「Cold Stats Box」に委譲し、meta->used / ss_active_* の更新をホットパスから切り離す。
- 進捗: STATS_BOX を導入済み。BATCH=0 は性能同等、BATCH=1 は C7-only/Mixed とも大きく劣化bench 専用)。`HAKMEM_TINY_STATS_BATCH` は当面 OFF 推奨。
- 次の一手: BATCH=0 を前提に C5/C6 など他クラスへの適用可否を検討するか、別の箱front/UC など)を優先するかを再評価。
当面の運用ルール
----------------
- デフォルト: PROFILE=LEGACY か PROFILE=C7_SAFE を手動選択。C7_SAFE 時は `HAKMEM_TINY_STATS_BOX=1 HAKMEM_TINY_STATS_BATCH=0` をセット。C6 TinyHeap は明示しない限り OFF。
- mimalloc 比較フラグ:
- C7-only → `HAKMEM_BENCH_C7_ONLY=1 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_STATS_BOX=1 HAKMEM_TINY_STATS_BATCH=0 HAKMEM_TINY_LARSON_FIX=1`ULTRA は研究用)。
- Mixed 161024B → `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_LARSON_FIX=1` で PROFILE=LEGACY と PROFILE=C7_SAFE を並べて比較C6 は載せない)。
- C6 を触るときは必ず HEAP_CLASSES で class6 を明示し、`HAKMEM_TINY_HEAP_STATS[_DUMP]=1` で fast/slow を一緒に記録する。C6 は bench 専用のまま。
- HotHeap v2 (C7 専用) の扱い:
- 現状は C7-only でも v1 より遅く、Mixed では大きく回帰。標準は `HAKMEM_TINY_HOTHEAP_V2=0`v1 C7_SAFE を使用)。
- 研究/実験で使う場合のみ `HAKMEM_TINY_HOTHEAP_V2=1 HAKMEM_TINY_HOTHEAP_CLASSES=0x80` を明示し、`HAKMEM_TINY_HOTHEAP_V2_STATS=1` で fallback/fast 比率を観測する。結果が悪くてもよいベンチ専用モードとする。

View File

@ -0,0 +1,398 @@
# TinyHotHeap v2 Implementation Guide (A案 → C案の入口)
日付: 2025-12-05
対象: 実装担当 AI / 開発者向け指示書
関連ドキュメント:
- `docs/analysis/TINY_HEAP_V2_DESIGN.md` (Phase36+ 設計 / A/B/C 案・箱構造)
- `docs/analysis/TINY_HEAP_BOX_DESIGN.md` (v1 TinyHeapBox / C7 HotBox)
- `docs/analysis/C7_HOTBOX_DESIGN.md` (既存 C7 HotBox 設計)
- `CURRENT_TASK.md` (Phase36 状況メモ)
- ステータス: Phase36 で C7-only の Hot 部current_page+freelistを v2 で自前管理する暫定実装を投入済み。Superslab/Tier/Stats は v1 に委譲する lease/refill/retire 境界で A/B できる状態。
---
## 0. ゴールと非ゴール
- ゴール
- TinyHotHeap v2 を **per-thread Hot Box** として実装し、C7-only から段階的に C6/C5 へ拡張できる骨格を作る。
- Hot Box 側は **heap→page→block のみ** を扱い、Superslab/Tier/Remote/Stats/Learning との境界は
- alloc 側: `tiny_hotheap_v2_refill_slow(...)`
- free 側: `tiny_hotheap_v2_page_retire_slow(...)`
の 1 箇所に集約するBox Theory: 境界 1 箇所)。
- A/B: `HAKMEM_TINY_HOTHEAP_V2` / `HAKMEM_TINY_HOTHEAP_CLASSES` / Route Snapshot により、
C7-only で v1 C7_SAFE と v2 Hot Box を即時切り替え可能にする。
- 非ゴール(このガイドの範囲外)
- Superslab/Tier/Guard/Remote のフル再設計C 案の Segment+Page+Block までは行かない)。
- C0〜C4 や mid/large サイズ帯の設計変更。
- 学習層 (ACE/ELO) の仕様変更。Hot path は従来どおり Snapshot を読むだけとする。
---
## 1. 箱の対応関係(概念 → 既存型へのマッピング)
設計ドキュメントでは便宜的に:
- `TinyPageMeta` / `TinyClassHeap` / `TinyHeapCtx`
という名称で説明しているが、実装では既に存在する v2 skeleton を利用する:
- ファイル: `core/box/tiny_hotheap_v2_box.h`
- `tiny_hotheap_page_v2` … 概念上の `TinyPageMeta`
- `tiny_hotheap_class_v2` … 概念上の `TinyClassHeap`
- `tiny_hotheap_ctx_v2` … 概念上の `TinyHeapCtx`
実装では **この既存ヘッダを Hot Box の中核** とし、
他のファイルからは基本的に `tiny_hotheap_v2_*` API 経由でしか触らない。
---
## 2. Hot Box 実装ステップ
### Step 2-1: 型の見直しと拡張 (core/box/tiny_hotheap_v2_box.h)
目的: `tiny_hotheap_page_v2` / `tiny_hotheap_class_v2` / `tiny_hotheap_ctx_v2`
Phase36 設計の A 案Hot Boxに揃える。
作業指示:
- `tiny_hotheap_page_v2`
- すでに `freelist/used/capacity/base/meta/ss/slab_idx/next` を持っているので、
**ページ内 freelist と Cold 側リンク用の最小限メタ** として継続利用する。
- 必要であれば `flags`HOT/PARTIAL/FULLなどの軽量フラグを追加してよい。
- `lease_page` フィールドは「v1 page への橋渡し用」なので、
Phase1 では残しておき、将来的には A 案が安定した時点で削除/無効化を検討する。
- `tiny_hotheap_class_v2`
- `current_page` / `partial_pages` / `full_pages` が A 案の `TinyClassHeap` に相当する。
- `storage_page` は Phase32 の「C7 1 枚 lease」専用だが、Phase1 では
- C7-only: storage を current_page として使う
- 複数ページ化後: storage は「最初の page」または「固定 1 枚キャッシュ」として扱う
形で再利用してよい(完全削除は後のフェーズでも可)。
- `stride` はクラスごとの block サイズ(バイト数)として維持。
- `tiny_hotheap_ctx_v2`
- `cls[TINY_HOTHEAP_MAX_CLASSES]` の配列で全 Tiny class をカバー済み。
Phase1 では **C7-only を有効** にし、C6/C5 は未使用のままでよい。
このステップでは **まだ関数実装をいじらず、型だけを A 案に整合** させる。
### Step 2-2: TLS 取得 API の実装 (core/hakmem_tiny.c など)
目的: Hot Box の入口を 1 箇所に揃える。
作業指示:
- `tiny_hotheap_v2_tls_get()` の実装:
- `g_tiny_hotheap_ctx_v2``__thread`)を lazily `malloc`/`calloc` し、`memset` でゼロ初期化。
- 初期化時に `tiny_hotheap_v2_page_reset(&cls[i].storage_page)` など、
最低限のリセットを行うBox 内に初期化を閉じ込める)。
- 返り値は `tiny_hotheap_ctx_v2*`。以降、Hot Box の関数は **必ず ctx を通して状態にアクセス** する。
### Step 2-3: alloc Hot パスの実装 (tiny_hotheap_v2_box.h)
目的: C7-only の `tiny_hotheap_v2_alloc()`**page 内 freelist + current/partial** だけで完結させる。
推奨構造:
- `static inline void* tiny_hotheap_v2_alloc_fast(tiny_hotheap_ctx_v2* ctx, uint8_t class_idx);`
- `void* tiny_hotheap_v2_alloc(uint8_t class_idx);` は Gate から呼ばれる薄いラッパとして、
- `ctx = tiny_hotheap_v2_tls_get();`
- `return tiny_hotheap_v2_alloc_fast(ctx, class_idx);`
だけを行う。
Hot パスのロジックC7 専用版):
- `tiny_hotheap_class_v2* hc = &ctx->cls[class_idx];`
- 優先順位:
1. `hc->current_page` があり、その `freelist` が非 NULL → 1 block pop して `used++`
2. `hc->partial_pages` リストがあれば 1 枚取り出し、`current_page` に昇格させてから pop。
3. どちらも空なら `tiny_hotheap_v2_refill_slow(ctx, class_idx)` へ降りる。
注意:
- Phase1 では **C7-only** を対象とし、`class_idx == 7` 以外は即座に v1 経路へフォールバックしてよい。
- `tiny_hotheap_v2_refill_slow` は次節の Cold Box との境界関数として後から実装する。
### Step 2-4: free Hot パスの実装 (tiny_hotheap_v2_box.h)
目的: C7-only の `tiny_hotheap_v2_free()` を page 内 freelist push + empty 判定だけにする。
推奨構造:
- `static inline void tiny_hotheap_v2_free_fast(tiny_hotheap_ctx_v2* ctx, uint8_t class_idx, void* p, struct TinySlabMeta* meta);`
- `void tiny_hotheap_v2_free(uint8_t class_idx, void* p, void* meta);` は Gate 用ラッパとして、
- `ctx = tiny_hotheap_v2_tls_get();`
- `tiny_hotheap_v2_free_fast(ctx, class_idx, p, (struct TinySlabMeta*)meta);`
のみ行う。
Hot パスのロジックC7 専用版):
- `tiny_hotheap_page_v2* page = tiny_hotheap_v2_page_of(ctx, class_idx, p, meta);`
- Phase1 では meta/base/slab_idx から page を特定するヘルパを 1 箱に閉じ込める(実装場所はこのヘッダ or 専用 C ファイル)。
- `*(void**)p = page->freelist; page->freelist = p; page->used--;`
- `page->used == 0` になったら `tiny_hotheap_v2_page_retire_slow(ctx, class_idx, page);` を呼ぶ。
備考:
- Phase1 では **Remote Queue / cross-thread free は従来経路にフォールバック** してよい。
具体的には:
- 所有スレッド以外からの free であれば v1 の remote 経路を即呼び出す。
- 所有スレッド判定には既存の TLS/owner 情報を使うOwnership Box を汚さない)。
---
## 3. Cold Box 境界実装ステップ
### Step 3-1: refill_slowv1 TinyHeap / Superslab からのページ供給)
目的: Hot Box から見て **唯一の「ページ供給」入口** を 1 箇所に集約する。
作業指示:
- 関数案(実装ファイルは `core/hakmem_tiny.c` か、専用の `core/box/tiny_hotheap_v2_cold_box.h` でもよい):
```c
tiny_hotheap_page_v2*
tiny_hotheap_v2_refill_slow(tiny_hotheap_ctx_v2* ctx, uint8_t class_idx);
```
- Phase1 では実装を **既存 v1 TinyHeap/C7 SAFE に橋渡しするだけ** に留める:
- `tiny_heap_c7_lease_page_for_v2()` など、既存の C7 lease API を利用して 1 枚 page をもらう。
- 取得した `TinySlabMeta*` / `SuperSlab*` / `base` / `capacity``tiny_hotheap_page_v2` にコピーし、`current_page` or `partial_pages` に追加。
- freelist の初期化は **Hot Box 側で実施** し、以降の pop/push は v2 が握るv1 に再委譲しない)。
### Step 3-2: page_retire_slow全 free になったページの返却)
目的: ページが empty になった瞬間だけ Cold Box に touch する。
作業指示:
- 関数案:
```c
void
tiny_hotheap_v2_page_retire_slow(tiny_hotheap_ctx_v2* ctx,
uint8_t class_idx,
tiny_hotheap_page_v2* page);
```
- Phase1 の挙動:
- page を `current_page` / `partial_pages` / `full_pages` から unlink。
- v1 側に「ページ free」イベントとして渡す:
- Superslab / Warm Pool / Tier / Guard の扱いは v1 に任せるv2 は触らない)。
- Cold Stats Box があれば、ここで delta を flush する。
- 将来フェーズ:
- Warm Pool / Superslab への返却ロジックを v2 専用に寄せることで、
C 案Segment+Page+Blockへの移行パスとして利用する。
---
## 4. Gate / Route / ENV の配線ステップ
### Step 4-1: Route Snapshot への v2 統合
作業指示:
- ファイル: `core/box/tiny_route_box.h` 付近(`g_tiny_route_class[]` の決定ロジック)。
- ENV:
- `HAKMEM_TINY_HOTHEAP_V2` / `HAKMEM_TINY_HOTHEAP_CLASSES` を読み、
- bit7 が立っていれば `g_tiny_route_class[7] = TINY_ROUTE_HOTHEAP_V2;`
- それ以外は従来通り `TINY_ROUTE_HEAP_V1` / `TINY_ROUTE_LEGACY`
を設定する。
- これにより front 側は `class_idx` 決定後に
- `route = g_tiny_route_class[class_idx];`
- `switch (route) { HOTHEAP_V2 / HEAP_V1 / LEGACY }`
だけで経路を決定できる。
### Step 4-2: front からの呼び出しmalloc_tiny_fast / free_tiny_fast
作業指示:
- ファイル: `core/hakmem_tiny.c` + 各種 `tiny_alloc_fast*.inc.h` / `tiny_free_fast_v2.inc.h`
- C7-only モードの Gate で:
- `route == TINY_ROUTE_HOTHEAP_V2` かつ `size` が C7 クラスにマップされる場合にのみ
`tiny_hotheap_v2_alloc(7)` / `tiny_hotheap_v2_free(7, p, meta)` を呼ぶ。
- それ以外は従来通り v1 TinyHeap / legacy 経路へフォールバックする。
- Free 側では:
- C7 ptr かつ Route=HOTHEAP_V2 かつ owner==self の場合のみ `tiny_hotheap_v2_free` を試す。
- 条件に当てはまらない場合は Remote Queue / v1 free 経路にすぐ落とす。
---
## 5. 可視化・Fail-Fast・A/B の運用
### Step 5-1: v2 専用 Stats の追加(任意だが推奨)
作業指示:
- 既存の `tiny_stats_box` / `tiny_class_stats_box` に倣って、
- `alloc_calls / alloc_fast / alloc_slow_refill / alloc_fb_v1`
- `free_calls / free_fast / free_fb_v1`
のような v2 専用カウンタを追加。
- ENV:
- `HAKMEM_TINY_HOTHEAP_V2_STATS=1` のときだけ destructor で 1 行ログを出す。
### Step 5-2: Fail-Fast ポリシー
作業指示:
- 範囲外 ptr / page_of 失敗 / meta 不整合など、**Hot Box の前提が壊れた場合は即 abort** する。
- v2 実装中は「誤魔化しのフォールバック」を避け、
問題が出たら Fail-Fast で原因を特定するAGENTS.md の方針通り)。
### Step 5-3: 推奨ベンチコマンド(実装後に利用)
実装完了後、以下のようなプロファイルで A/B を行う(ここでは指示のみ、実行は別フェーズ):
- C7-only:
- v1 基準: `HAKMEM_BENCH_C7_ONLY=1 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=0 ...`
- v2: `HAKMEM_BENCH_C7_ONLY=1 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_HOTHEAP_V2=1 HAKMEM_TINY_HOTHEAP_CLASSES=0x80 ...`
- Mixed 161024B:
- 当面は v1 C7_SAFE を基準に残し、v2 は C7-only が十分安定してから評価する。
---
## 6. 実装順序のまとめ(チェックリスト)
- [ ] 型整理: `tiny_hotheap_page_v2` / `tiny_hotheap_class_v2` / `tiny_hotheap_ctx_v2` を Phase36 A 案に整合させる。
- [ ] TLS 初期化: `tiny_hotheap_v2_tls_get()` を実装し、Hot Box 内に状態を閉じ込める。
- [ ] alloc Hot パス: `tiny_hotheap_v2_alloc_fast()` / `tiny_hotheap_v2_alloc()` を C7-only で実装。
- [ ] free Hot パス: `tiny_hotheap_v2_free_fast()` / `tiny_hotheap_v2_free()` を C7-only で実装。
- [ ] Cold 境界: `tiny_hotheap_v2_refill_slow()` / `tiny_hotheap_v2_page_retire_slow()` を v1 TinyHeap への橋渡しとして実装。
- [ ] Route/Gate: `tiny_route_box` と front を更新し、C7-only で v1/v2/legacy を A/B 切り替え可能にする。
- [ ] Stats/Fail-Fast: v2 専用 stats と assert/abort を追加し、問題発生時にすぐ気付けるようにする。
このガイドは「箱」と「境界」の位置だけを固定するものであり、
細かな最適化C7 超ホットレーン / C6/C5 への拡張 / C 案への橋渡し)は Phase36 以降のサブフェーズで順次詰める前提とする。
---
## 7. Phase37: C7 current_page ポリシー修正指示書
ベンチ結果サマリ(現状 v2 の問題)
----------------------------------
Release, PROFILE=C7_SAFE での観測:
- C7-only (ws=64, iters=20k)
- v2 OFF: 39.64M ops/s, `HEAP_STATS[7] fast=11015 slow=1`
- v2 ON : 26.89M ops/s, `HEAP_STATS[7] fast=97 slow=32758`(ほぼ全て slow_prepare
- Mixed 161024B (ws=256, iters=20k)
- v2 OFF: 38.78M ops/s, `fast=5691 slow=1`
- v2 ON : 33.26M ops/s, `fast=141 slow=16654`
結論:
- v2 ON では `current_page` がほぼ活きておらず、**毎回 slow_prepare に落ちている**。
- 現状の v2 を運用で使うのは危険なため、当面のデフォルトは **v2 OFF (C7_SAFE + HOTHEAP_V2=0)** を維持する。
この Phase37 の目的
-------------------
- 目的:
- C7-only で **`current_page` をきちんと保持・再利用し、slow_prepare をほぼゼロに抑える**。
- v1 C7 SAFE / TinyHeapBox の current_page ポリシーを v2 Hot Box に移植する。
- 非ゴール:
- C6/C5 への展開。
- Cold Box (Superslab/Tier/Remote/Stats) の構造変更。
ざっくりタスク一覧
------------------
- [ ] v2 専用 current_page デバッグ統計の追加
- [ ] refill_slow → current_page セット手順の見直し
- [ ] free 側で current_page を再利用するポリシーの導入
- [ ] retire 条件の見直しempty でもすぐ返さない)
- [ ] v1 C7 SAFE current_page ポリシーとの比較・差分吸収
- [ ] C7-only / Mixed ベンチで slow_prepare を再確認
以下、それぞれの具体指示。
Step 7-1: current_page デバッグ統計の追加
----------------------------------------
目的:
- v2 が「どこで current_page を失って slow_prepare に落ちているか」を**1 箇所で見える化**する。
作業指示:
- 参考: `docs/analysis/TINY_HEAP_BOX_DESIGN.md` にある C7 current_page stats`prepare_calls` など)。
- 新規 struct を追加(場所は `tiny_hotheap_v2_box.h` か、専用 stats ファイルでも良い):
- `uint64_t prepare_calls;`
- `uint64_t prepare_with_current_null;`
- `uint64_t prepare_from_partial;`
- `uint64_t free_made_current;`
- `uint64_t page_retired;`
- ENV `HAKMEM_TINY_HOTHEAP_V2_STATS=1` のときのみカウンタを更新・dump する。
- カウントポイント:
- `tiny_hotheap_v2_alloc_fast` 内:
- entry ごとに `prepare_calls++`
- `hc->current_page==NULL` のとき `prepare_with_current_null++`
- partial から current に昇格したとき `prepare_from_partial++`
- `tiny_hotheap_v2_free_fast` 内:
- free で page が current になる/保持される場合 `free_made_current++`
- `tiny_hotheap_v2_page_retire_slow` 内:
- `page_retired++`
Step 7-2: refill_slow → current_page セットの修正
-----------------------------------------------
目的:
- refill 後に必ず「C7 用の current_page が 1 枚セットされる」ようにする。
作業指示:
- `tiny_hotheap_v2_refill_slow` の振る舞いを以下のように揃える:
1. C7 用 lease API`tiny_heap_c7_lease_page_for_v2()` など)から 1 枚 page を取得。
2. `tiny_hotheap_page_v2` へ:
- `base / capacity / meta / ss / slab_idx / stride` をコピー。
- freelist を page 内 carving で初期化pop は v2 が握る)。
3. `tiny_hotheap_class_v2* hc = &ctx->cls[7];` に対して:
- `hc->current_page` が NULL なら **必ずここで current_page にセット**
- 既に current がある場合は `hc->partial_pages` に push。
- refill 後に `hc->current_page` が NULL のままにならないよう、Fail-Fast assert を入れても良い(デバッグ時)。
Step 7-3: free 側で current_page を維持・再利用する
-----------------------------------------------
目的:
- C7-only では「同じ page 内で alloc/free が揺れる」ケースが多いため、
free 側で current_page をなるべく保持・再利用する。
作業指示:
- `tiny_hotheap_v2_free_fast` で、以下のようなポリシーを入れるv1 TinyHeapBox SAFE と同等イメージ):
- `page->used > 0` かつ `page->freelist != NULL` の page は **current_page 候補**
- `hc->current_page` が NULL の場合は、free した page を current に据え直す。
- `hc->current_page` が他の page を指していても、C7-only フェーズでは「free された page に空きがあるなら current に切り替える」実験をして良い(後で safe なポリシーに絞る)。
- これにより、C7-only ベンチで `prepare_calls` に対して `prepare_with_current_null` がほぼ 0 に近づくことを期待する。
Step 7-4: retire 条件の見直し
-----------------------------
目的:
- v2 ON で empty page を早々に Cold Box に返しすぎると、current_page を毎回失って slow_prepare 連発になる。
作業指示:
- `tiny_hotheap_v2_page_retire_slow` を呼ぶ条件を見直す:
- Phase37 では「C7-only ベンチでは page が empty になってもすぐには返さない」実験を許容する。
- 具体案:
- `page->used == 0` でも、まず `hc->partial_pages` の一員として残す(一定枚数を超えたら返却)。
- C7-only フェーズでは `max_partial_pages` を 1〜2 に固定して試す。
- 本番運用向けには、後続フェーズで安全側にチューニングし直す前提で、「今は slow_prepare を殺すことを最優先」にしてよい。
Step 7-5: v1 C7 SAFE current_page ポリシーとの比較
-----------------------------------------------
目的:
- 「すでにうまく動いている v1 C7 SAFE / TinyHeapBox の current_page 設計」を v2 に再利用する。
作業指示:
- 参照:
- `docs/analysis/TINY_HEAP_BOX_DESIGN.md`
- `docs/analysis/C7_HOTBOX_DESIGN.md`
- この2つから:
- alloc 時: `prepare_calls=1` に抑えるための current 固定ロジック
- free 時: empty page の扱いULTRA/SAFE の違い)
を抜き出し、v2 Hot Box の `alloc_fast` / `free_fast` / `page_retire_slow` に反映する。
- 差分:
- v1 TinyHeapBox は meta/ss_active 更新も Hot 側が握っていたが、v2 では Cold Stats Box に寄せる方針。
- そのため meta/active の更新部分だけは v1 と同じにせず、**current_page に関わるポリシー部分だけをコピー** する。
Step 7-6: 再ベンチと判定基準
---------------------------
作業指示:
- C7-only (ws=64, iters=20k, PROFILE=C7_SAFE) で:
- v2 OFF: 39〜40M ops/s既存ベースライン
- v2 ON : 目標として **少なくとも v2 OFF と同等 ±5% 以内** を目指す。
- `HEAP_STATS[7] fast≈11015 slow≈1` に戻ることを確認。
- v2 current_page stats では:
- `prepare_calls` に対して `prepare_with_current_null` が ≪1% 程度
- Mixed 161024B (ws=256, iters=20k) では:
- v2 OFF の 38〜39M ops/s に対して **±5% 以内** を許容範囲とする。
- C7-only が十分安定・同等以上であれば、Mixed の調整は後続フェーズで良い。
判定:
- 上記基準を満たせば「Phase37: current_page ポリシー修正完了」とし、
次フェーズで C6/C5 への拡張や C7 超ホットレーンB案を検討できる。
- 満たせない場合は v2 を引き続き **デフォルト OFF / 研究用箱** としたまま、
current_page / retire ポリシーの再修正に戻る。

View File

@ -34,15 +34,16 @@ hakmem.o: core/hakmem.c core/hakmem.h core/hakmem_build_flags.h \
core/box/../hakmem_tiny_superslab.h \
core/box/../superslab/superslab_inline.h core/box/../tiny_tls.h \
core/box/../hakmem_tiny_superslab.h core/box/../tiny_box_geometry.h \
core/box/tiny_c7_hotbox.h core/box/mid_large_config_box.h \
core/box/../hakmem_config.h core/box/../hakmem_features.h \
core/box/hak_free_api.inc.h core/hakmem_tiny_superslab.h \
core/box/../hakmem_trace_master.h core/box/front_gate_v2.h \
core/box/external_guard_box.h core/box/../hakmem_stats_master.h \
core/box/ss_slab_meta_box.h core/box/../superslab/superslab_types.h \
core/box/slab_freelist_atomic.h core/box/fg_tiny_gate_box.h \
core/box/tiny_free_gate_box.h core/box/ptr_type_box.h \
core/box/ptr_conversion_box.h core/box/tiny_ptr_bridge_box.h \
core/box/tiny_stats_box.h core/box/tiny_c7_hotbox.h \
core/box/mid_large_config_box.h core/box/../hakmem_config.h \
core/box/../hakmem_features.h core/box/hak_free_api.inc.h \
core/hakmem_tiny_superslab.h core/box/../hakmem_trace_master.h \
core/box/front_gate_v2.h core/box/external_guard_box.h \
core/box/../hakmem_stats_master.h core/box/ss_slab_meta_box.h \
core/box/../superslab/superslab_types.h core/box/slab_freelist_atomic.h \
core/box/fg_tiny_gate_box.h core/box/tiny_free_gate_box.h \
core/box/ptr_type_box.h core/box/ptr_conversion_box.h \
core/box/tiny_ptr_bridge_box.h \
core/box/../hakmem_tiny_superslab_internal.h \
core/box/../hakmem_build_flags.h core/box/../box/ss_hot_cold_box.h \
core/box/../box/../superslab/superslab_types.h \
@ -91,6 +92,9 @@ hakmem.o: core/hakmem.c core/hakmem.h core/hakmem_build_flags.h \
core/box/../front/../box/../front/tiny_unified_cache.h \
core/box/../front/../box/tiny_layout_box.h \
core/box/../front/../box/tiny_front_cold_box.h \
core/box/../front/../box/tiny_hotheap_v2_box.h \
core/box/../front/../box/tiny_route_env_box.h \
core/box/../front/../box/tiny_front_stats_box.h \
core/box/tiny_alloc_gate_box.h core/box/tiny_route_box.h \
core/box/tiny_front_config_box.h core/box/wrapper_env_box.h \
core/box/../hakmem_internal.h
@ -173,6 +177,7 @@ core/box/../superslab/superslab_inline.h:
core/box/../tiny_tls.h:
core/box/../hakmem_tiny_superslab.h:
core/box/../tiny_box_geometry.h:
core/box/tiny_stats_box.h:
core/box/tiny_c7_hotbox.h:
core/box/mid_large_config_box.h:
core/box/../hakmem_config.h:
@ -258,6 +263,9 @@ core/box/../front/../box/../tiny_region_id.h:
core/box/../front/../box/../front/tiny_unified_cache.h:
core/box/../front/../box/tiny_layout_box.h:
core/box/../front/../box/tiny_front_cold_box.h:
core/box/../front/../box/tiny_hotheap_v2_box.h:
core/box/../front/../box/tiny_route_env_box.h:
core/box/../front/../box/tiny_front_stats_box.h:
core/box/tiny_alloc_gate_box.h:
core/box/tiny_route_box.h:
core/box/tiny_front_config_box.h:

BIN
perf.data.c6heavy Normal file

Binary file not shown.

BIN
perf.data.mid_u Normal file

Binary file not shown.

BIN
perf.data.mixed_u Normal file

Binary file not shown.

BIN
perf.data.pf Normal file

Binary file not shown.