diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index a59e9619..d9c0e03f 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -1,12 +1,17 @@ ## HAKMEM 状況メモ (2025-12-05 更新 / C7 Warm/TLS Bind 反映) +### Hotfix: madvise(ENOMEM) を握りつぶし、以降の madvise を停止(Superslab OS Box) +- 変更: `ss_os_madvise_guarded()` を追加し、madvise が ENOMEM を返したら `g_ss_madvise_disabled=1` にして以降の madvise をスキップ。EINVAL だけは従来どおり STRICT=1 で Fail-Fast(ENV `HAKMEM_SS_MADVISE_STRICT` で緩和可)。 +- stats: `[SS_OS_STATS]` に `madvise_enomem/madvise_other/madvise_disabled` を追加。HAKMEM_SS_OS_STATS=1 で確認可能。 +- ねらい: vm.max_map_count 到達時の大量 ENOMEM で VMA がさらに分割されるのを防ぎ、アロケータ自体は走り続ける。 + ### Phase80: mid/smallmid Pool v1 flatten(C6-heavy) - 目的: mid/smallmid の pool v1 ホットパスを薄くし、C6-heavy ベンチで +5〜10% 程度の底上げを狙う。 - 実装: `core/hakmem_pool.c` に v1 専用のフラット化経路(`hak_pool_try_alloc_v1_flat` / `hak_pool_free_v1_flat`)を追加し、TLS ring/lo hit 時は即 return・その他は従来の `_v1_impl` へフォールバックする Box に分離。ENV `HAKMEM_POOL_V1_FLATTEN_ENABLED`(デフォルト0)と `HAKMEM_POOL_V1_FLATTEN_STATS` でオンオフと統計を制御。 - A/B(C6-heavy, ws=400, iters=1M, `HAKMEM_BENCH_MIN_SIZE=257` / `MAX_SIZE=768`, `POOL_V2_ENABLED=0`, Tiny/Small v2/v3 は従来どおり): - flatten OFF (`POOL_V1_FLATTEN_ENABLED=0`): Throughput ≈ **23.12M ops/s**、`[POOL_V1_FLAT] alloc_tls_hit=0 alloc_fb=0 free_tls_hit=0 free_fb=0`。 - - flatten ON (`POOL_V1_FLATTEN_ENABLED=1`): Throughput ≈ **25.50M ops/s**(約 +10%)、`alloc_tls_hit=499,870 alloc_fb=230 free_tls_hit=460,450 free_fb=39,649`。 -- 所感: 自スレッド TLS fast path を太らせるだけで目標どおり +10% 程度の改善が得られた。まだ free_fb がそこそこ残っているため、次に詰めるなら page_of / 自スレ判定の精度を上げて free_fb を削るフェーズ(Pool v1 flatten Phase2)を検討する。運用デフォルトは引き続き `POOL_V1_FLATTEN_ENABLED=0`(安全側)とし、bench/実験時のみ opt-in。 +- flatten ON (`POOL_V1_FLATTEN_ENABLED=1`): Throughput ≈ **25.50M ops/s**(約 +10%)、`alloc_tls_hit=499,870 alloc_fb=230 free_tls_hit=460,450 free_fb=39,649`。 +- 所感: 自スレッド TLS fast path を太らせるだけで目標どおり +10% 程度の改善が得られた。まだ free_fb がそこそこ残っているため、次に詰めるなら page_of / 自スレ判定の精度を上げて free_fb を削るフェーズ(Pool v1 flatten Phase2)を検討する。運用デフォルトは引き続き `POOL_V1_FLATTEN_ENABLED=0`(安全側)とし、bench/実験時のみ opt-in。**C7_SAFE プロファイル時は安全側で強制 OFF**(クラッシュ回避のため)。 ### Phase81: Pool v1 flatten Phase2(free_fb 内訳の可視化) - 変更: flatten stats に free fallback の理由別カウンタを追加(page_null / not_mine / other)。`hak_pool_free_v1_flat` で mid_desc 取得失敗 → page_null、owner 不一致等 → not_mine、その他 → other として集計。 diff --git a/Makefile b/Makefile index eaa4fc3b..6a0991a8 100644 --- a/Makefile +++ b/Makefile @@ -428,7 +428,7 @@ test-box-refactor: box-refactor ./larson_hakmem 10 8 128 1024 1 12345 4 # Phase 4: Tiny Pool benchmarks (properly linked with hakmem) -TINY_BENCH_OBJS_BASE = hakmem.o hakmem_config.o hakmem_tiny_config.o hakmem_ucb1.o hakmem_bigcache.o hakmem_pool.o hakmem_l25_pool.o hakmem_site_rules.o hakmem_tiny.o core/box/ss_allocation_box.o superslab_stats.o superslab_cache.o superslab_ace.o superslab_slab.o superslab_backend.o core/superslab_head_stub.o hakmem_smallmid.o core/box/superslab_expansion_box.o core/box/integrity_box.o core/box/mailbox_box.o core/box/front_gate_box.o core/box/front_gate_classifier.o core/box/free_publish_box.o core/box/capacity_box.o core/box/carve_push_box.o core/box/prewarm_box.o core/box/ss_hot_prewarm_box.o core/box/front_metrics_box.o core/box/bench_fast_box.o core/box/ss_addr_map_box.o core/box/slab_recycling_box.o core/box/pagefault_telemetry_box.o core/box/tiny_sizeclass_hist_box.o core/box/tiny_env_box.o core/box/tiny_route_box.o core/box/tiny_page_box.o core/box/tiny_class_policy_box.o core/box/tiny_class_stats_box.o core/box/tiny_policy_learner_box.o core/box/ss_budget_box.o core/box/tiny_mem_stats_box.o core/box/c7_meta_used_counter_box.o core/box/wrapper_env_box.o core/box/ptr_trace_box.o core/box/link_missing_stubs.o core/box/super_reg_box.o core/box/shared_pool_box.o core/box/remote_side_box.o core/page_arena.o core/front/tiny_unified_cache.o tiny_sticky.o tiny_remote.o tiny_publish.o tiny_debug_ring.o hakmem_tiny_magazine.o hakmem_tiny_stats.o hakmem_tiny_sfc.o hakmem_tiny_query.o hakmem_tiny_rss.o hakmem_tiny_registry.o hakmem_tiny_remote_target.o hakmem_tiny_bg_spill.o tiny_adaptive_sizing.o hakmem_super_registry.o hakmem_shared_pool.o hakmem_shared_pool_acquire.o hakmem_shared_pool_release.o hakmem_elo.o hakmem_batch.o hakmem_p2.o hakmem_sizeclass_dist.o hakmem_evo.o hakmem_debug.o hakmem_sys.o hakmem_whale.o hakmem_policy.o hakmem_ace.o hakmem_ace_stats.o hakmem_prof.o hakmem_learner.o hakmem_size_hist.o hakmem_learn_log.o hakmem_syscall.o hakmem_ace_metrics.o hakmem_ace_ucb1.o hakmem_ace_controller.o tiny_fastcache.o core/tiny_alloc_fast_push.o core/link_stubs.o core/tiny_failfast.o +TINY_BENCH_OBJS_BASE = hakmem.o hakmem_config.o hakmem_tiny_config.o hakmem_ucb1.o hakmem_bigcache.o hakmem_pool.o hakmem_l25_pool.o hakmem_site_rules.o hakmem_tiny.o core/box/ss_allocation_box.o superslab_stats.o superslab_cache.o superslab_ace.o superslab_slab.o superslab_backend.o core/superslab_head_stub.o hakmem_smallmid.o core/box/superslab_expansion_box.o core/box/integrity_box.o core/box/mailbox_box.o core/box/front_gate_box.o core/box/front_gate_classifier.o core/box/free_publish_box.o core/box/capacity_box.o core/box/carve_push_box.o core/box/prewarm_box.o core/box/ss_hot_prewarm_box.o core/box/front_metrics_box.o core/box/bench_fast_box.o core/box/ss_addr_map_box.o core/box/slab_recycling_box.o core/box/pagefault_telemetry_box.o core/box/tiny_sizeclass_hist_box.o core/box/tiny_env_box.o core/box/tiny_route_box.o core/box/tiny_page_box.o core/box/tiny_class_policy_box.o core/box/tiny_class_stats_box.o core/box/tiny_policy_learner_box.o core/box/ss_budget_box.o core/box/tiny_mem_stats_box.o core/box/c7_meta_used_counter_box.o core/box/wrapper_env_box.o core/box/ptr_trace_box.o core/box/link_missing_stubs.o core/box/super_reg_box.o core/box/shared_pool_box.o core/box/remote_side_box.o core/page_arena.o core/front/tiny_unified_cache.o tiny_sticky.o tiny_remote.o tiny_publish.o tiny_debug_ring.o hakmem_tiny_magazine.o hakmem_tiny_stats.o hakmem_tiny_sfc.o hakmem_tiny_query.o hakmem_tiny_rss.o hakmem_tiny_registry.o hakmem_tiny_remote_target.o hakmem_tiny_bg_spill.o tiny_adaptive_sizing.o hakmem_super_registry.o hakmem_shared_pool.o hakmem_shared_pool_acquire.o hakmem_shared_pool_release.o hakmem_elo.o hakmem_batch.o hakmem_p2.o hakmem_sizeclass_dist.o hakmem_evo.o hakmem_debug.o hakmem_sys.o hakmem_whale.o hakmem_policy.o hakmem_ace.o hakmem_ace_stats.o hakmem_prof.o hakmem_learner.o hakmem_size_hist.o hakmem_learn_log.o hakmem_syscall.o hakmem_ace_metrics.o hakmem_ace_ucb1.o hakmem_ace_controller.o tiny_fastcache.o core/tiny_alloc_fast_push.o core/link_stubs.o core/tiny_failfast.o core/smallobject_hotbox_v3.o TINY_BENCH_OBJS = $(TINY_BENCH_OBJS_BASE) ifeq ($(POOL_TLS_PHASE1),1) TINY_BENCH_OBJS += pool_tls.o pool_refill.o core/pool_tls_arena.o pool_tls_registry.o pool_tls_remote.o diff --git a/core/box/pool_api.inc.h b/core/box/pool_api.inc.h index 8571518d..da5c11ee 100644 --- a/core/box/pool_api.inc.h +++ b/core/box/pool_api.inc.h @@ -4,6 +4,7 @@ #include "pagefault_telemetry_box.h" // Box PageFaultTelemetry (PF_BUCKET_MID) #include "box/pool_hotbox_v2_box.h" +#include "box/tiny_heap_env_box.h" // TinyHeap profile (C7_SAFE では flatten を無効化) // Pool v2 is experimental. Default OFF (use legacy v1 path). static inline int hak_pool_v2_enabled(void) { @@ -40,6 +41,12 @@ static inline int hak_pool_v2_tls_fast_enabled(void) { static inline int hak_pool_v1_flatten_enabled(void) { static int g = -1; if (__builtin_expect(g == -1, 0)) { + // C7_SAFE/C7_ULTRA_BENCH プロファイルでは、安全側で強制 OFF + int mode = tiny_heap_profile_mode(); + if (mode == TINY_HEAP_PROFILE_C7_SAFE || mode == TINY_HEAP_PROFILE_C7_ULTRA_BENCH) { + g = 0; + return g; + } const char* e = getenv("HAKMEM_POOL_V1_FLATTEN_ENABLED"); g = (e && *e && *e != '0') ? 1 : 0; } diff --git a/core/box/pool_hotbox_v2_box.h b/core/box/pool_hotbox_v2_box.h new file mode 100644 index 00000000..4bffd163 --- /dev/null +++ b/core/box/pool_hotbox_v2_box.h @@ -0,0 +1,86 @@ +// pool_hotbox_v2_box.h — Experimental PoolHotBox v2 (hot path scaffold) +#ifndef POOL_HOTBOX_V2_BOX_H +#define POOL_HOTBOX_V2_BOX_H + +#include +#include +#include + +#include "hakmem_pool.h" // for POOL_NUM_CLASSES and size helpers + +// ENV gates (bench/実験専用): +// HAKMEM_POOL_V2_ENABLED : overall ON/OFF (default OFF) +// HAKMEM_POOL_V2_CLASSES : bitmask, bit i=1 → class i を HotBox v2 に載せる +// HAKMEM_POOL_V2_STATS : stats dump ON/OFF + +typedef struct PoolHotBoxV2Stats { + _Atomic uint64_t alloc_calls; + _Atomic uint64_t alloc_fast; + _Atomic uint64_t alloc_refill; + _Atomic uint64_t alloc_refill_fail; + _Atomic uint64_t alloc_fallback_v1; + _Atomic uint64_t free_calls; + _Atomic uint64_t free_fast; + _Atomic uint64_t free_fallback_v1; + _Atomic uint64_t page_of_fail_header_missing; + _Atomic uint64_t page_of_fail_out_of_range; + _Atomic uint64_t page_of_fail_misaligned; + _Atomic uint64_t page_of_fail_unknown; +} PoolHotBoxV2Stats; + +// Simple page/class structs for future HotBox v2 implementation. +typedef struct pool_page_v2 { + void* freelist; + uint32_t used; + uint32_t capacity; + uint32_t block_size; + uint32_t class_idx; + void* base; + void* slab_ref; + struct pool_page_v2* next; +} pool_page_v2; + +typedef struct pool_class_v2 { + pool_page_v2* current; + pool_page_v2* partial; + uint16_t max_partial_pages; + uint16_t partial_count; + uint32_t block_size; +} pool_class_v2; + +typedef struct pool_ctx_v2 { + pool_class_v2 cls[POOL_NUM_CLASSES]; +} pool_ctx_v2; + +typedef struct PoolColdIface { + void* (*refill_page)(void* cold_ctx, + uint32_t class_idx, + uint32_t* out_block_size, + uint32_t* out_capacity, + void** out_slab_ref); + void (*retire_page)(void* cold_ctx, + uint32_t class_idx, + void* slab_ref, + void* base); +} PoolColdIface; + +// ENV helpers +int pool_hotbox_v2_class_enabled(int class_idx); +int pool_hotbox_v2_stats_enabled(void); + +// TLS/context helpers +pool_ctx_v2* pool_v2_tls_get(void); + +// Hot path (currently stubbed to always fall back to v1; structure only) +void* pool_hotbox_v2_alloc(uint32_t class_idx, size_t size, uintptr_t site_id); +int pool_hotbox_v2_free(uint32_t class_idx, void* raw_block); + +// Stats helpers +void pool_hotbox_v2_record_free_call(uint32_t class_idx); +void pool_hotbox_v2_record_alloc_fallback(uint32_t class_idx); +void pool_hotbox_v2_record_free_fallback(uint32_t class_idx); + +// Stats export (destructor in hakmem_pool.c) +extern PoolHotBoxV2Stats g_pool_hotbox_v2_stats[POOL_NUM_CLASSES]; + +#endif // POOL_HOTBOX_V2_BOX_H diff --git a/core/box/pool_hotbox_v2_header_box.h b/core/box/pool_hotbox_v2_header_box.h new file mode 100644 index 00000000..fa6a5907 --- /dev/null +++ b/core/box/pool_hotbox_v2_header_box.h @@ -0,0 +1,33 @@ +// pool_hotbox_v2_header_box.h +// Small helpers for embedding/reading the v2 pool page pointer in the page header. +#pragma once + +#include + +// Mask a pointer down to the page base (POOL_PAGE_SIZE is a power of two). +static inline void* pool_hotbox_v2_page_base(void* ptr, size_t page_size) { + return (void*)((uintptr_t)ptr & ~((uintptr_t)page_size - 1)); +} + +// Store the PoolHotBox v2 page pointer into the page header. +// Caller must ensure base is page_size aligned and non-NULL. +static inline void pool_hotbox_v2_header_store(void* page_base, void* page_ptr) { + if (!page_base) return; + void** hdr = (void**)page_base; + *hdr = page_ptr; +} + +// Clear the page header pointer (used on retire to avoid stale lookups). +static inline void pool_hotbox_v2_header_clear(void* page_base) { + if (!page_base) return; + void** hdr = (void**)page_base; + *hdr = NULL; +} + +// Load the page pointer from the page header (may return NULL). +static inline void* pool_hotbox_v2_header_load(void* page_base) { + if (!page_base) return NULL; + void** hdr = (void**)page_base; + return *hdr; +} + diff --git a/core/box/pool_mid_desc.inc.h b/core/box/pool_mid_desc.inc.h index a260507a..87da7696 100644 --- a/core/box/pool_mid_desc.inc.h +++ b/core/box/pool_mid_desc.inc.h @@ -41,6 +41,22 @@ static void mid_desc_register(void* page, int class_idx, uint64_t owner_tid) { void* canonical_page = (void*)((uintptr_t)page & ~((uintptr_t)POOL_PAGE_SIZE - 1)); uint32_t h = mid_desc_hash(canonical_page); pthread_mutex_lock(&g_mid_desc_mu[h]); + + // Check if descriptor already exists + MidPageDesc* existing = g_mid_desc_head[h]; + while (existing) { + if (existing->page == canonical_page) { + // Descriptor already exists, update owner_tid if needed + if (existing->owner_tid == 0 && owner_tid != 0) { + existing->owner_tid = owner_tid; + } + pthread_mutex_unlock(&g_mid_desc_mu[h]); + return; + } + existing = existing->next; + } + + // Descriptor doesn't exist, create new one MidPageDesc* d = (MidPageDesc*)hkm_libc_malloc(sizeof(MidPageDesc)); // P0 Fix: Use libc malloc if (d) { d->page = canonical_page; @@ -76,7 +92,16 @@ static void mid_desc_adopt(void* addr, int class_idx, uint64_t owner_tid) { if (d->owner_tid == 0) d->owner_tid = owner_tid; } else { MidPageDesc* nd = (MidPageDesc*)hkm_libc_malloc(sizeof(MidPageDesc)); // P0 Fix: Use libc malloc - if (nd) { nd->page = page; nd->class_idx = (uint8_t)class_idx; nd->owner_tid = owner_tid; nd->next = g_mid_desc_head[h]; g_mid_desc_head[h] = nd; } + if (nd) { + nd->page = page; + nd->class_idx = (uint8_t)class_idx; + nd->owner_tid = owner_tid; + nd->next = g_mid_desc_head[h]; + atomic_store(&nd->in_use, 0); + nd->blocks_per_page = 0; + atomic_store(&nd->pending_dn, 0); + g_mid_desc_head[h] = nd; + } } pthread_mutex_unlock(&g_mid_desc_mu[h]); } diff --git a/core/box/smallobject_cold_iface_v1.h b/core/box/smallobject_cold_iface_v1.h new file mode 100644 index 00000000..0c57214f --- /dev/null +++ b/core/box/smallobject_cold_iface_v1.h @@ -0,0 +1,80 @@ +// smallobject_cold_iface_v1.h - Cold interface wrapper for SmallObject HotBox v3 +// 役割: +// - SmallObject Hot Box (v3) と既存 v1 Tiny Cold 層の境界を 1 箇所にまとめる。 +// - Phase A: C7 の refill/retire だけを v1 TinyHeap へラップする。 +#pragma once + +#include +#include +#include "tiny_heap_box.h" +#include "smallobject_hotbox_v3_box.h" +#include "../hakmem_tiny.h" // TINY_SLAB_SIZE for slab base mask + +struct so_page_v3; + +typedef struct SmallObjectColdIface { + struct so_page_v3* (*refill_page)(void* cold_ctx, uint32_t class_idx); + void (*retire_page)(void* cold_ctx, uint32_t class_idx, struct so_page_v3* page); +} SmallObjectColdIface; + +static inline struct so_page_v3* smallobject_cold_refill_page_v1(void* cold_ctx, uint32_t class_idx) { + if (class_idx != 7 && class_idx != 6) { + return NULL; // Phase A-2: C7/C6 のみ対応 + } + tiny_heap_ctx_t* ctx = cold_ctx ? (tiny_heap_ctx_t*)cold_ctx : tiny_heap_ctx_for_thread(); + if (!ctx) return NULL; + tiny_heap_page_t* lease = tiny_heap_prepare_page(ctx, (int)class_idx); + if (!lease) return NULL; + + so_page_v3* page = (so_page_v3*)calloc(1, sizeof(so_page_v3)); + if (!page) return NULL; + + page->lease_page = lease; + page->meta = lease->meta; + page->ss = lease->ss; + page->slab_idx = lease->slab_idx; + page->base = lease->base; + page->capacity = lease->capacity; + page->block_size = (uint32_t)tiny_stride_for_class((int)class_idx); + page->class_idx = class_idx; + page->slab_ref = lease; + return page; +} + +static inline void smallobject_cold_retire_page_v1(void* cold_ctx, uint32_t class_idx, struct so_page_v3* page) { + if (!page || (class_idx != 7 && class_idx != 6)) { + if (page) { + free(page); + } + return; + } + tiny_heap_ctx_t* ctx = cold_ctx ? (tiny_heap_ctx_t*)cold_ctx : tiny_heap_ctx_for_thread(); + if (!ctx) { + free(page); + return; + } + tiny_heap_page_t* lease = page->lease_page; + if (!lease) { + free(page); + return; + } + + lease->base = (uint8_t*)page->base; + lease->capacity = (uint16_t)page->capacity; + lease->used = (uint16_t)page->used; + lease->meta = page->meta; + lease->ss = page->ss; + lease->slab_idx = page->slab_idx; + lease->free_list = page->freelist; + + tiny_heap_page_becomes_empty(ctx, (int)class_idx, lease); + free(page); +} + +static inline SmallObjectColdIface smallobject_cold_iface_v1(void) { + SmallObjectColdIface iface = { + .refill_page = smallobject_cold_refill_page_v1, + .retire_page = smallobject_cold_retire_page_v1, + }; + return iface; +} diff --git a/core/box/smallobject_hotbox_v3_box.h b/core/box/smallobject_hotbox_v3_box.h new file mode 100644 index 00000000..d2fe609a --- /dev/null +++ b/core/box/smallobject_hotbox_v3_box.h @@ -0,0 +1,74 @@ +// smallobject_hotbox_v3_box.h - SmallObject HotHeap v3 (C7-first skeleton) +// +// Phase A/B: 型と TLS / stats を用意し、front が呼べる枠を置く。 +// まだ中身は v1 fallback(so_alloc は NULL を返す)。 +#pragma once + +#include +#include +#include +#include "tiny_geometry_box.h" +#include "smallobject_hotbox_v3_env_box.h" +#include "tiny_region_id.h" + +#ifndef SMALLOBJECT_NUM_CLASSES +#define SMALLOBJECT_NUM_CLASSES TINY_NUM_CLASSES +#endif + +struct tiny_heap_page_t; +struct TinySlabMeta; +struct SuperSlab; + +typedef struct so_page_v3 { + void* freelist; + uint32_t used; + uint32_t capacity; + uint32_t block_size; + uint32_t class_idx; + uint32_t flags; + void* base; // carve 後のユーザ領域先頭 + void* slab_base; // 64KiB slab 基底(page_of 用ヘッダを書き込む) + struct TinySlabMeta* meta; + struct SuperSlab* ss; + uint16_t slab_idx; + struct tiny_heap_page_t* lease_page; + void* slab_ref; // kept as a generic token; currently same as lease_page for v1 + struct so_page_v3* next; +} so_page_v3; + +typedef struct so_class_v3 { + so_page_v3* current; + so_page_v3* partial; + uint16_t max_partial_pages; + uint16_t partial_count; + uint32_t block_size; +} so_class_v3; + +typedef struct so_ctx_v3 { + so_class_v3 cls[SMALLOBJECT_NUM_CLASSES]; +} so_ctx_v3; + +typedef struct so_stats_class_v3 { + _Atomic uint64_t route_hits; + _Atomic uint64_t alloc_calls; + _Atomic uint64_t alloc_refill; + _Atomic uint64_t alloc_fallback_v1; + _Atomic uint64_t free_calls; + _Atomic uint64_t free_fallback_v1; +} so_stats_class_v3; + +// Stats helpers (defined in core/smallobject_hotbox_v3.c) +int so_v3_stats_enabled(void); +void so_v3_record_route_hit(uint8_t ci); +void so_v3_record_alloc_call(uint8_t ci); +void so_v3_record_alloc_refill(uint8_t ci); +void so_v3_record_alloc_fallback(uint8_t ci); +void so_v3_record_free_call(uint8_t ci); +void so_v3_record_free_fallback(uint8_t ci); + +// TLS accessor (core/smallobject_hotbox_v3.c) +so_ctx_v3* so_tls_get(void); + +// Hot path API (Phase B: stub → always fallback to v1) +void* so_alloc(uint32_t class_idx); +void so_free(uint32_t class_idx, void* ptr); diff --git a/core/box/smallobject_hotbox_v3_env_box.h b/core/box/smallobject_hotbox_v3_env_box.h new file mode 100644 index 00000000..6a6580f0 --- /dev/null +++ b/core/box/smallobject_hotbox_v3_env_box.h @@ -0,0 +1,47 @@ +// smallobject_hotbox_v3_env_box.h - ENV gate for SmallObject HotHeap v3 +// 役割: +// - HAKMEM_SMALL_HEAP_V3_ENABLED / HAKMEM_SMALL_HEAP_V3_CLASSES をまとめて読む。 +// - デフォルトは C7-only ON(クラスマスク 0x80)。ENV で明示的に 0 を指定した場合のみ v3 を無効化。 +#pragma once + +#include +#include + +#include "../hakmem_tiny_config.h" + +static inline int small_heap_v3_enabled(void) { + static int g_enable = -1; + if (__builtin_expect(g_enable == -1, 0)) { + const char* e = getenv("HAKMEM_SMALL_HEAP_V3_ENABLED"); + if (e && *e) { + g_enable = (*e != '0') ? 1 : 0; + } else { + // デフォルトは ON(ENV 未指定時は有効) + g_enable = 1; + } + } + return g_enable; +} + +static inline int small_heap_v3_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_SMALL_HEAP_V3_CLASSES"); + if (e && *e) { + unsigned v = (unsigned)strtoul(e, NULL, 0); + g_mask = v & 0xFFu; + } else { + // デフォルトは C7 のみ v3 ON + g_mask = 0x80u; + } + g_parsed = 1; + } + if (!small_heap_v3_enabled()) return 0; + if (class_idx >= TINY_NUM_CLASSES) return 0; + return (g_mask & (1u << class_idx)) != 0; +} + +static inline int small_heap_v3_c7_enabled(void) { + return small_heap_v3_class_enabled(7); +} diff --git a/core/box/ss_allocation_box.c b/core/box/ss_allocation_box.c index 21846163..453d48a0 100644 --- a/core/box/ss_allocation_box.c +++ b/core/box/ss_allocation_box.c @@ -360,7 +360,7 @@ void superslab_free(SuperSlab* ss) { } if (lazy_zero_enabled) { #ifdef MADV_DONTNEED - (void)madvise((void*)ss, ss_size, MADV_DONTNEED); + (void)ss_os_madvise_guarded((void*)ss, ss_size, MADV_DONTNEED, "ss_lru_madvise"); ss_os_stats_record_madvise(); #endif } diff --git a/core/box/ss_os_acquire_box.c b/core/box/ss_os_acquire_box.c index 3205aa82..ab1e30be 100644 --- a/core/box/ss_os_acquire_box.c +++ b/core/box/ss_os_acquire_box.c @@ -1,6 +1,7 @@ // ss_os_acquire_box.c - SuperSlab OS Memory Acquisition Box Implementation #include "ss_os_acquire_box.h" #include "../hakmem_build_flags.h" +#include "../hakmem_env_cache.h" #include #include #include @@ -15,8 +16,11 @@ 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_madvise_fail_enomem; +extern _Atomic uint64_t g_ss_os_madvise_fail_other; extern _Atomic uint64_t g_ss_os_huge_alloc_calls; extern _Atomic uint64_t g_ss_os_huge_fail_calls; +extern _Atomic bool g_ss_madvise_disabled; // ============================================================================ // OOM Diagnostics @@ -240,9 +244,12 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p // See: EXPLICIT_PREFAULT_IMPLEMENTATION_REPORT_20251205.md #ifdef MADV_POPULATE_WRITE if (populate) { - int ret = madvise(ptr, ss_size, MADV_POPULATE_WRITE); - ss_os_stats_record_madvise(); + int ret = ss_os_madvise_guarded(ptr, ss_size, MADV_POPULATE_WRITE, "ss_os_acquire_populate"); if (ret != 0) { + if (HAK_ENV_SS_MADVISE_STRICT() && errno == EINVAL) { + fprintf(stderr, "[SS_OS] madvise(MADV_POPULATE_WRITE) EINVAL (strict mode). Aborting.\n"); + abort(); + } // Fallback for kernels that support MADV_POPULATE_WRITE but it fails // Use explicit page-by-page touching with writes volatile char* p = (volatile char*)ptr; @@ -273,10 +280,14 @@ static void ss_os_stats_destructor(void) { 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", + "[SS_OS_STATS] alloc=%llu free=%llu madvise=%llu madvise_enomem=%llu madvise_other=%llu madvise_disabled=%d " + "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_os_madvise_fail_enomem, memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_ss_os_madvise_fail_other, memory_order_relaxed), + atomic_load_explicit(&g_ss_madvise_disabled, memory_order_relaxed) ? 1 : 0, (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), diff --git a/core/box/ss_os_acquire_box.h b/core/box/ss_os_acquire_box.h index fb85a539..f93ecf61 100644 --- a/core/box/ss_os_acquire_box.h +++ b/core/box/ss_os_acquire_box.h @@ -18,7 +18,11 @@ #include #include #include +#include #include +#include +#include +#include // ============================================================================ // Global Counters (for debugging/diagnostics) @@ -29,8 +33,11 @@ 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_madvise_fail_enomem; +extern _Atomic uint64_t g_ss_os_madvise_fail_other; extern _Atomic uint64_t g_ss_os_huge_alloc_calls; extern _Atomic uint64_t g_ss_os_huge_fail_calls; +extern _Atomic bool g_ss_madvise_disabled; static inline int ss_os_stats_enabled(void) { static int g_ss_os_stats_enabled = -1; @@ -62,6 +69,52 @@ static inline void ss_os_stats_record_madvise(void) { atomic_fetch_add_explicit(&g_ss_os_madvise_calls, 1, memory_order_relaxed); } +// ============================================================================ +// madvise guard (shared by Superslab hot/cold paths) +// ============================================================================ +// +static inline int ss_os_madvise_guarded(void* ptr, size_t len, int advice, const char* where) { + (void)where; + if (!ptr || len == 0) { + return 0; + } + + if (atomic_load_explicit(&g_ss_madvise_disabled, memory_order_relaxed)) { + return 0; + } + + int ret = madvise(ptr, len, advice); + ss_os_stats_record_madvise(); + if (ret == 0) { + return 0; + } + + int e = errno; + if (e == ENOMEM) { + atomic_fetch_add_explicit(&g_ss_os_madvise_fail_enomem, 1, memory_order_relaxed); + atomic_store_explicit(&g_ss_madvise_disabled, true, memory_order_relaxed); +#if !HAKMEM_BUILD_RELEASE + static _Atomic bool g_ss_madvise_enomem_logged = false; + bool already = atomic_exchange_explicit(&g_ss_madvise_enomem_logged, true, memory_order_relaxed); + if (!already) { + fprintf(stderr, + "[SS_OS_MADVISE] madvise(advice=%d, ptr=%p, len=%zu) failed with ENOMEM " + "(vm.max_map_count reached?). Disabling further madvise calls.\n", + advice, ptr, len); + } +#endif + return 0; // soft fail, do not propagate ENOMEM + } + + atomic_fetch_add_explicit(&g_ss_os_madvise_fail_other, 1, memory_order_relaxed); + if (e == EINVAL) { + errno = e; + return -1; // let caller decide (strict mode) + } + errno = e; + return 0; +} + // ============================================================================ // HugePage Experiment (research-only) // ============================================================================ diff --git a/core/box/tiny_cold_iface_v1.h b/core/box/tiny_cold_iface_v1.h new file mode 100644 index 00000000..15cfd8ab --- /dev/null +++ b/core/box/tiny_cold_iface_v1.h @@ -0,0 +1,37 @@ +// tiny_cold_iface_v1.h +// TinyHotHeap v2 など別 Hot Box が Superslab/Tier/Stats と話すための共通境界 (v1 wrapper)。 +// 前提: tiny_heap_box.h で tiny_heap_page_t / tiny_heap_ctx_t が定義済みであること。 +#pragma once + +#include "tiny_heap_box.h" + +typedef struct TinyColdIface { + tiny_heap_page_t* (*refill_page)(void* cold_ctx, uint32_t class_idx); + void (*retire_page)(void* cold_ctx, uint32_t class_idx, tiny_heap_page_t* page); +} TinyColdIface; + +// Forward declarations for the v1 cold helpers (defined in tiny_heap_box.h) +tiny_heap_page_t* tiny_heap_prepare_page(tiny_heap_ctx_t* ctx, int class_idx); +void tiny_heap_page_becomes_empty(tiny_heap_ctx_t* ctx, int class_idx, tiny_heap_page_t* page); + +static inline tiny_heap_page_t* tiny_cold_refill_page_v1(void* cold_ctx, uint32_t class_idx) { + if (!cold_ctx) { + return NULL; + } + return tiny_heap_prepare_page((tiny_heap_ctx_t*)cold_ctx, (int)class_idx); +} + +static inline void tiny_cold_retire_page_v1(void* cold_ctx, uint32_t class_idx, tiny_heap_page_t* page) { + if (!cold_ctx || !page) { + return; + } + tiny_heap_page_becomes_empty((tiny_heap_ctx_t*)cold_ctx, (int)class_idx, page); +} + +static inline TinyColdIface tiny_cold_iface_v1(void) { + TinyColdIface iface = { + .refill_page = tiny_cold_refill_page_v1, + .retire_page = tiny_cold_retire_page_v1, + }; + return iface; +} diff --git a/core/box/tiny_front_v3_env_box.h b/core/box/tiny_front_v3_env_box.h new file mode 100644 index 00000000..016df2cc --- /dev/null +++ b/core/box/tiny_front_v3_env_box.h @@ -0,0 +1,101 @@ +// tiny_front_v3_env_box.h - Tiny Front v3 ENV gate & snapshot (guard/UC/header) +#pragma once + +#include +#include +#include +#include + +typedef struct TinyFrontV3Snapshot { + bool unified_cache_on; + bool tiny_guard_on; + uint8_t header_mode; // tiny_header_mode() の値をキャッシュ + bool header_v3_enabled; // ENV: HAKMEM_TINY_HEADER_V3_ENABLED + bool header_v3_skip_c7; // ENV: HAKMEM_TINY_HEADER_V3_SKIP_C7 +} TinyFrontV3Snapshot; + +// Size→class/route entry for Tiny front v3 LUT (route_kind は tiny_route_kind_t を想定) +typedef struct TinyFrontV3SizeClassEntry { + uint8_t class_idx; + uint8_t route_kind; +} TinyFrontV3SizeClassEntry; + +#define TINY_FRONT_V3_INVALID_CLASS ((uint8_t)0xFF) + +extern TinyFrontV3Snapshot g_tiny_front_v3_snapshot; +extern int g_tiny_front_v3_snapshot_ready; + +// ENV gate: default OFF +static inline bool tiny_front_v3_enabled(void) { + static int g_enable = -1; + if (__builtin_expect(g_enable == -1, 0)) { + const char* e = getenv("HAKMEM_TINY_FRONT_V3_ENABLED"); + g_enable = (e && *e && *e != '0') ? 1 : 0; + } + return g_enable != 0; +} + +// Optional: size→class LUT gate (default OFF, for A/B) +static inline bool tiny_front_v3_lut_enabled(void) { + static int g = -1; + if (__builtin_expect(g == -1, 0)) { + const char* e = getenv("HAKMEM_TINY_FRONT_V3_LUT_ENABLED"); + g = (e && *e && *e != '0') ? 1 : 0; + } + return g != 0; +} + +// Optional: route fast path (Tiny LUT→1 switch). Default OFF for easy rollback. +static inline bool tiny_front_v3_route_fast_enabled(void) { + static int g = -1; + if (__builtin_expect(g == -1, 0)) { + const char* e = getenv("HAKMEM_TINY_FRONT_V3_ROUTE_FAST_ENABLED"); + g = (e && *e && *e != '0') ? 1 : 0; + } + return g != 0; +} + +// Optional stats gate +static inline bool tiny_front_v3_stats_enabled(void) { + static int g = -1; + if (__builtin_expect(g == -1, 0)) { + const char* e = getenv("HAKMEM_TINY_FRONT_V3_STATS"); + g = (e && *e && *e != '0') ? 1 : 0; + } + return g != 0; +} + +// Header v3 experimental gate (default OFF) +static inline bool tiny_header_v3_enabled(void) { + static int g = -1; + if (__builtin_expect(g == -1, 0)) { + const char* e = getenv("HAKMEM_TINY_HEADER_V3_ENABLED"); + g = (e && *e && *e != '0') ? 1 : 0; + } + return g != 0; +} + +// Skip header write for C7 v3 allocs (bench/experiment, default OFF) +static inline bool tiny_header_v3_skip_c7(void) { + static int g = -1; + if (__builtin_expect(g == -1, 0)) { + const char* e = getenv("HAKMEM_TINY_HEADER_V3_SKIP_C7"); + g = (e && *e && *e != '0') ? 1 : 0; + } + return g != 0; +} + +// Snapshot initializer (implemented in hakmem_tiny.c) +void tiny_front_v3_snapshot_init(void); + +// LUT initializer / lookup (implemented in hakmem_tiny.c) +void tiny_front_v3_size_class_lut_init(void); +const TinyFrontV3SizeClassEntry* tiny_front_v3_lut_lookup(size_t size); + +// Get cached snapshot (lazy init) +static inline const TinyFrontV3Snapshot* tiny_front_v3_snapshot_get(void) { + if (__builtin_expect(!g_tiny_front_v3_snapshot_ready, 0)) { + tiny_front_v3_snapshot_init(); + } + return &g_tiny_front_v3_snapshot; +} diff --git a/core/box/tiny_hotheap_v2_box.h b/core/box/tiny_hotheap_v2_box.h index 6c3e9c04..5e795aae 100644 --- a/core/box/tiny_hotheap_v2_box.h +++ b/core/box/tiny_hotheap_v2_box.h @@ -36,7 +36,8 @@ typedef struct tiny_hotheap_class_v2 { tiny_hotheap_page_v2* partial_pages; tiny_hotheap_page_v2* full_pages; uint16_t stride; - uint16_t _pad; + uint16_t max_partial_pages; // 空ページを保持する上限(C7 専用で 1〜2 を想定) + uint16_t partial_count; // いま握っている partial の枚数 tiny_hotheap_page_v2 storage_page; // C7 専用の 1 枚だけをまず保持(Phase36: reuse when空き) } tiny_hotheap_class_v2; @@ -51,8 +52,8 @@ 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); +void tiny_hotheap_v2_record_route_fallback(uint8_t class_idx); +void tiny_hotheap_v2_record_free_fallback(uint8_t class_idx); typedef struct tiny_hotheap_v2_stats_snapshot { uint64_t route_hits; @@ -65,11 +66,19 @@ typedef struct tiny_hotheap_v2_stats_snapshot { uint64_t free_calls; uint64_t free_fast; uint64_t free_fallback_v1; + uint64_t cold_refill_fail; + uint64_t cold_retire_calls; + uint64_t retire_calls_v2; uint64_t prepare_calls; uint64_t prepare_with_current_null; uint64_t prepare_from_partial; uint64_t free_made_current; uint64_t page_retired; + uint64_t partial_pushes; + uint64_t partial_pops; + uint64_t partial_peak; + uint64_t refill_with_current; + uint64_t refill_with_partial; } tiny_hotheap_v2_stats_snapshot_t; void tiny_hotheap_v2_debug_snapshot(tiny_hotheap_v2_stats_snapshot_t* out); diff --git a/core/box/tiny_route_env_box.h b/core/box/tiny_route_env_box.h index 46bcd300..a223b1a6 100644 --- a/core/box/tiny_route_env_box.h +++ b/core/box/tiny_route_env_box.h @@ -9,10 +9,13 @@ #include "../hakmem_tiny_config.h" #include "tiny_heap_env_box.h" +#include "smallobject_hotbox_v3_env_box.h" + typedef enum { TINY_ROUTE_LEGACY = 0, - TINY_ROUTE_HEAP = 1, // TinyHeap v1 - TINY_ROUTE_HOTHEAP_V2 = 2, // TinyHotHeap v2 + TINY_ROUTE_HEAP = 1, // TinyHeap v1 + TINY_ROUTE_HOTHEAP_V2 = 2, // TinyHotHeap v2 + TINY_ROUTE_SMALL_HEAP_V3 = 3, // SmallObject HotHeap v3 (C7-first,研究箱) } tiny_route_kind_t; extern tiny_route_kind_t g_tiny_route_class[TINY_NUM_CLASSES]; @@ -20,7 +23,9 @@ 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)) { + if (small_heap_v3_class_enabled((uint8_t)i)) { + g_tiny_route_class[i] = TINY_ROUTE_SMALL_HEAP_V3; + } else 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; @@ -42,7 +47,7 @@ static inline tiny_route_kind_t tiny_route_for_class(uint8_t ci) { } static inline int tiny_route_is_heap_kind(tiny_route_kind_t route) { - return route == TINY_ROUTE_HEAP || route == TINY_ROUTE_HOTHEAP_V2; + return route == TINY_ROUTE_HEAP || route == TINY_ROUTE_HOTHEAP_V2 || route == TINY_ROUTE_SMALL_HEAP_V3; } // C7 front が TinyHeap を使うか(Route snapshot 経由で判定) diff --git a/core/front/malloc_tiny_fast.h b/core/front/malloc_tiny_fast.h index 6c2714a1..41127bad 100644 --- a/core/front/malloc_tiny_fast.h +++ b/core/front/malloc_tiny_fast.h @@ -40,6 +40,8 @@ #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/smallobject_hotbox_v3_box.h" // SmallObject HotHeap v3 skeleton +#include "../box/tiny_front_v3_env_box.h" // Tiny front v3 snapshot gate #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 @@ -102,24 +104,58 @@ static inline int front_gate_unified_enabled(void) { // __attribute__((always_inline)) static inline void* malloc_tiny_fast(size_t size) { - // size → class_idx を 1 回だけ決定 - int class_idx = hak_tiny_size_to_class(size); - if (__builtin_expect(class_idx < 0 || class_idx >= TINY_NUM_CLASSES, 0)) { - return NULL; + const int front_v3_on = tiny_front_v3_enabled(); + const TinyFrontV3Snapshot* front_snap = + __builtin_expect(front_v3_on, 0) ? tiny_front_v3_snapshot_get() : NULL; + const bool route_fast_on = front_v3_on && tiny_front_v3_lut_enabled() && + tiny_front_v3_route_fast_enabled(); + + int class_idx = -1; + tiny_route_kind_t route = TINY_ROUTE_LEGACY; + bool route_trusted = false; + + if (front_v3_on && tiny_front_v3_lut_enabled()) { + const TinyFrontV3SizeClassEntry* e = tiny_front_v3_lut_lookup(size); + if (e && e->class_idx != TINY_FRONT_V3_INVALID_CLASS) { + class_idx = (int)e->class_idx; + route = (tiny_route_kind_t)e->route_kind; + route_trusted = route_fast_on; + } } + + if (__builtin_expect(class_idx < 0 || class_idx >= TINY_NUM_CLASSES, 0)) { + class_idx = hak_tiny_size_to_class(size); + if (__builtin_expect(class_idx < 0 || class_idx >= TINY_NUM_CLASSES, 0)) { + return NULL; + } + route = tiny_route_for_class((uint8_t)class_idx); + route_trusted = false; + } else if (!route_trusted && + route != TINY_ROUTE_LEGACY && route != TINY_ROUTE_HEAP && + route != TINY_ROUTE_HOTHEAP_V2 && route != TINY_ROUTE_SMALL_HEAP_V3) { + route = tiny_route_for_class((uint8_t)class_idx); + } + 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(); + case TINY_ROUTE_SMALL_HEAP_V3: { + void* v3p = so_alloc((uint32_t)class_idx); + if (TINY_HOT_LIKELY(v3p != NULL)) { + return v3p; } + so_v3_record_alloc_fallback((uint8_t)class_idx); + // fallthrough to v2/v1 + __attribute__((fallthrough)); + } + case TINY_ROUTE_HOTHEAP_V2: { + void* v2p = tiny_hotheap_v2_alloc((uint8_t)class_idx); + if (TINY_HOT_LIKELY(v2p != NULL)) { + return v2p; + } + tiny_hotheap_v2_record_route_fallback((uint8_t)class_idx); // fallthrough to TinyHeap v1 + __attribute__((fallthrough)); } case TINY_ROUTE_HEAP: { void* heap_ptr = NULL; @@ -139,7 +175,10 @@ static inline void* malloc_tiny_fast(size_t size) { } // Legacy Tiny front - void* ptr = tiny_hot_alloc_fast(class_idx); + void* ptr = NULL; + if (!front_snap || front_snap->unified_cache_on) { + ptr = tiny_hot_alloc_fast(class_idx); + } if (TINY_HOT_LIKELY(ptr != NULL)) { return ptr; } @@ -192,6 +231,8 @@ static inline int free_tiny_fast(void* ptr) { 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); + const TinyFrontV3Snapshot* front_snap = + __builtin_expect(tiny_front_v3_enabled(), 0) ? tiny_front_v3_snapshot_get() : NULL; // TWO-SPEED: SuperSlab registration check is DEBUG-ONLY to keep HOT PATH fast. // In Release builds, we trust header magic (0xA0) as sufficient validation. @@ -255,6 +296,9 @@ static inline int free_tiny_fast(void* ptr) { // Same-thread + TinyHeap route → route-based free if (__builtin_expect(use_tiny_heap, 0)) { switch (route) { + case TINY_ROUTE_SMALL_HEAP_V3: + so_free((uint32_t)class_idx, base); + return 1; case TINY_ROUTE_HOTHEAP_V2: tiny_hotheap_v2_free((uint8_t)class_idx, base, meta); return 1; @@ -276,7 +320,9 @@ static inline int free_tiny_fast(void* ptr) { 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_hotheap_v2_record_free_fallback((uint8_t)class_idx); + } else if (route == TINY_ROUTE_SMALL_HEAP_V3) { + so_v3_record_free_fallback((uint8_t)class_idx); } tiny_heap_free_class_fast(tiny_heap_ctx_for_thread(), class_idx, ptr); return 1; @@ -300,7 +346,10 @@ static inline int free_tiny_fast(void* ptr) { } #endif - int pushed = unified_cache_push(class_idx, HAK_BASE_FROM_RAW(base)); + int pushed = 0; + if (!front_snap || front_snap->unified_cache_on) { + pushed = unified_cache_push(class_idx, HAK_BASE_FROM_RAW(base)); + } if (__builtin_expect(pushed, 1)) { return 1; // Success } diff --git a/core/hakmem_batch.c b/core/hakmem_batch.c index 03d4cd76..b6587ec0 100644 --- a/core/hakmem_batch.c +++ b/core/hakmem_batch.c @@ -11,6 +11,7 @@ #include "hakmem_sys.h" // Phase 6.11.1: Syscall wrappers with timing #include "hakmem_whale.h" // Phase 6.11.1: Whale fast-path cache #include "hakmem_env_cache.h" // Priority-2: ENV cache +#include "box/ss_os_acquire_box.h" // madvise guard #include #include #include @@ -117,12 +118,17 @@ void hak_batch_flush(void) { size_t size = snap.sizes[i]; // Step 1: MADV_FREE to release physical pages (fast, low TLB cost) - int ret = madvise(ptr, size, MADV_FREE); + int ret = ss_os_madvise_guarded(ptr, size, MADV_FREE, "batch_free"); if (ret != 0) { + if (HAK_ENV_SS_MADVISE_STRICT() && errno == EINVAL) { + fprintf(stderr, "[Batch] madvise(MADV_FREE) EINVAL (STRICT). Aborting.\n"); + abort(); + } // Fallback to MADV_DONTNEED if MADV_FREE not supported - ret = madvise(ptr, size, MADV_DONTNEED); - if (ret != 0) { - fprintf(stderr, "[Batch] Warning: madvise failed for block %p (size %zu)\n", ptr, size); + ret = ss_os_madvise_guarded(ptr, size, MADV_DONTNEED, "batch_dontneed"); + if (ret != 0 && HAK_ENV_SS_MADVISE_STRICT() && errno == EINVAL) { + fprintf(stderr, "[Batch] madvise(MADV_DONTNEED) EINVAL (STRICT). Aborting.\n"); + abort(); } } diff --git a/core/hakmem_env_cache.h b/core/hakmem_env_cache.h index ccdc4c29..bb637ad6 100644 --- a/core/hakmem_env_cache.h +++ b/core/hakmem_env_cache.h @@ -91,6 +91,9 @@ typedef struct { // ===== Cold Path: Batch (1 variable) ===== int batch_bg; // HAKMEM_BATCH_BG (default: 0) + // ===== Cold Path: Superslab Madvise (1 variable) ===== + int ss_madvise_strict; // HAKMEM_SS_MADVISE_STRICT (default: 1) + } HakEnvCache; // Global cache instance (initialized once at startup) @@ -289,10 +292,17 @@ static inline void hakmem_env_cache_init(void) { g_hak_env_cache.batch_bg = (e && atoi(e) != 0) ? 1 : 0; // default: 0 (OFF) } + // ===== Cold Path: Superslab Madvise ===== + { + const char* e = getenv("HAKMEM_SS_MADVISE_STRICT"); + // Default: 1 (STRICT), set HAKMEM_SS_MADVISE_STRICT=0 to relax + g_hak_env_cache.ss_madvise_strict = (e && *e && *e == '0') ? 0 : 1; + } + #if !HAKMEM_BUILD_RELEASE // Debug: Print cache summary (stderr only) if (!g_hak_env_cache.quiet) { - fprintf(stderr, "[ENV_CACHE_INIT] Parsed %d ENV variables at startup\n", 49); + fprintf(stderr, "[ENV_CACHE_INIT] Parsed %d ENV variables at startup\n", 50); fprintf(stderr, "[ENV_CACHE_INIT] Hot path syscalls eliminated: ~2000/sec → 0/sec\n"); fflush(stderr); } @@ -361,4 +371,7 @@ static inline void hakmem_env_cache_init(void) { // Cold path: Batch #define HAK_ENV_BATCH_BG() (g_hak_env_cache.batch_bg) +// Cold path: Superslab Madvise +#define HAK_ENV_SS_MADVISE_STRICT() (g_hak_env_cache.ss_madvise_strict) + #endif // HAKMEM_ENV_CACHE_H diff --git a/core/hakmem_l25_pool.c b/core/hakmem_l25_pool.c index 50b68b89..b8ed4916 100644 --- a/core/hakmem_l25_pool.c +++ b/core/hakmem_l25_pool.c @@ -49,6 +49,7 @@ #include "hakmem_l25_pool.h" #include "hakmem_config.h" #include "hakmem_internal.h" // For AllocHeader and HAKMEM_MAGIC +#include "box/ss_os_acquire_box.h" #include "hakmem_syscall.h" // Phase 6.X P0 Fix: Box 3 syscall layer (bypasses LD_PRELOAD) #include "box/pagefault_telemetry_box.h" // Box PageFaultTelemetry (PF_BUCKET_L25) #include "page_arena.h" // Phase 24: PageArena integration for L25 @@ -560,7 +561,7 @@ void hak_l25_pool_free_fast(void* user_ptr, uintptr_t site_id) { // Optional: demand-zero for larger classes if (g_l25_pool.demand_zero && class_idx >= 3) { - madvise((char*)raw, HEADER_SIZE + g_class_sizes[class_idx], MADV_DONTNEED); + (void)ss_os_madvise_guarded((char*)raw, HEADER_SIZE + g_class_sizes[class_idx], MADV_DONTNEED, "l25_pool_dontneed_class"); } // Same-thread hint: prefer per-block owner if header present (HDR_LIGHT>=1), else page owner @@ -1118,7 +1119,7 @@ void hak_l25_pool_free(void* ptr, size_t size, uintptr_t site_id) { if (g_l25_pool.demand_zero) { int class_idx_dz = hak_l25_pool_get_class_index(size); if (class_idx_dz >= 3) { - madvise((char*)raw, HEADER_SIZE + size, MADV_DONTNEED); + (void)ss_os_madvise_guarded((char*)raw, HEADER_SIZE + size, MADV_DONTNEED, "l25_pool_dontneed_size"); } } diff --git a/core/hakmem_pool.c b/core/hakmem_pool.c index ac15e829..3e305bcc 100644 --- a/core/hakmem_pool.c +++ b/core/hakmem_pool.c @@ -46,7 +46,9 @@ #include "hakmem_pool.h" #include "hakmem_config.h" #include "hakmem_internal.h" // For AllocHeader and HAKMEM_MAGIC +#include "box/pool_hotbox_v2_header_box.h" #include "hakmem_syscall.h" // Box 3 syscall layer (bypasses LD_PRELOAD) +#include "box/pool_hotbox_v2_box.h" #include #include #include @@ -58,6 +60,11 @@ #include "hakmem_policy.h" // FrozenPolicy caps (Soft CAP gating) #include "hakmem_debug.h" +#define POOL_HOTBOX_V2_HEADER_BYTES ((size_t)sizeof(void*)) +// Use an over-sized mapping to guarantee POOL_PAGE_SIZE alignment for the +// v2 page base. This keeps page_of() O(1) without relying on mmap alignment. +#define POOL_HOTBOX_V2_MAP_LEN (POOL_PAGE_SIZE * 2) + // False sharing mitigation: padded mutex type (64B) typedef struct { pthread_mutex_t m; char _pad[64 - (sizeof(pthread_mutex_t) % 64)]; } PaddedMutex; @@ -808,6 +815,513 @@ static int g_pool_min_bundle = 2; // env: HAKMEM_POOL_MIN_BUNDLE (default 2) static int g_count_sample_exp = 10; // env: HAKMEM_POOL_COUNT_SAMPLE (0..16) static __thread uint32_t t_pool_rng = 0x243f6a88u; // per-thread RNG for sampling +// --------------------------------------------------------------------------- +// PoolHotBox v2 scaffolding (research-only; defaults to v1) +// --------------------------------------------------------------------------- +PoolHotBoxV2Stats g_pool_hotbox_v2_stats[POOL_NUM_CLASSES]; +static __thread pool_ctx_v2* g_pool_ctx_v2 = NULL; + +// Forward decls for helpers used in HotBox v2. +static inline uint32_t pool_hotbox_v2_block_size(int ci); +static inline uint32_t pool_block_size_for_class(int ci); +static inline void mid_set_header(AllocHeader* hdr, size_t class_sz, uintptr_t site_id); +static inline void mid_page_inuse_inc(void* raw); +static void* pool_cold_refill_page_v1(void* cold_ctx, uint32_t ci, uint32_t* out_block_size, uint32_t* out_capacity, void** out_slab_ref); +static void pool_cold_retire_page_v1(void* cold_ctx, uint32_t ci, void* slab_ref, void* base); + +static int pool_hotbox_v2_global_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; +} + +static unsigned pool_hotbox_v2_class_mask(void) { + static int parsed = 0; + static unsigned mask = 0; + if (__builtin_expect(!parsed, 0)) { + const char* e = getenv("HAKMEM_POOL_V2_CLASSES"); + if (e && *e) { + mask = (unsigned)strtoul(e, NULL, 0); + } else { + mask = 0; // default: all OFF (opt-in only) + } + parsed = 1; + } + return mask; +} + +int pool_hotbox_v2_class_enabled(int class_idx) { + if (!pool_hotbox_v2_global_enabled()) return 0; + if (class_idx < 0 || class_idx >= POOL_NUM_CLASSES) return 0; + unsigned mask = pool_hotbox_v2_class_mask(); + static int logged = 0; + if (__builtin_expect(!logged && pool_hotbox_v2_stats_enabled(), 0)) { + fprintf(stderr, "[POOL_V2_MASK] enabled=0x%x\n", mask); + logged = 1; + } + return (mask & (1u << class_idx)) != 0; +} + +int pool_hotbox_v2_stats_enabled(void) { + static int g = -1; + if (__builtin_expect(g == -1, 0)) { + const char* e = getenv("HAKMEM_POOL_V2_STATS"); + g = (e && *e && *e != '0') ? 1 : 0; + } + return g; +} + +pool_ctx_v2* pool_v2_tls_get(void) { + pool_ctx_v2* ctx = g_pool_ctx_v2; + if (__builtin_expect(ctx == NULL, 0)) { + ctx = (pool_ctx_v2*)calloc(1, sizeof(pool_ctx_v2)); + if (!ctx) abort(); + for (int i = 0; i < POOL_NUM_CLASSES; i++) { + uint32_t user_sz = pool_block_size_for_class(i); + ctx->cls[i].block_size = user_sz ? (user_sz + HEADER_SIZE) : 0; + ctx->cls[i].max_partial_pages = 2; + } + g_pool_ctx_v2 = ctx; + } + return ctx; +} + +static inline uint32_t pool_hotbox_v2_block_size(int ci) { + switch (ci) { + case 0: return POOL_CLASS_2KB; + case 1: return POOL_CLASS_4KB; + case 2: return POOL_CLASS_8KB; + case 3: return POOL_CLASS_16KB; + case 4: return POOL_CLASS_32KB; + case 5: return POOL_CLASS_40KB; + case 6: return POOL_CLASS_52KB; + default: return 0; + } +} + +static inline uint32_t pool_block_size_for_class(int ci) { + return pool_hotbox_v2_block_size(ci); +} + +static inline void pool_hotbox_v2_record_alloc(uint32_t ci) { + if ((int)ci >= POOL_NUM_CLASSES) return; + atomic_fetch_add_explicit(&g_pool_hotbox_v2_stats[ci].alloc_calls, 1, memory_order_relaxed); +} + +static inline void pool_hotbox_v2_record_alloc_refill(uint32_t ci) { + if ((int)ci >= POOL_NUM_CLASSES) return; + atomic_fetch_add_explicit(&g_pool_hotbox_v2_stats[ci].alloc_refill, 1, memory_order_relaxed); +} + +static inline void pool_hotbox_v2_record_alloc_refill_fail(uint32_t ci) { + if ((int)ci >= POOL_NUM_CLASSES) return; + atomic_fetch_add_explicit(&g_pool_hotbox_v2_stats[ci].alloc_refill_fail, 1, memory_order_relaxed); +} + +void pool_hotbox_v2_record_alloc_fallback(uint32_t ci) { + if ((int)ci >= POOL_NUM_CLASSES) return; + atomic_fetch_add_explicit(&g_pool_hotbox_v2_stats[ci].alloc_fallback_v1, 1, memory_order_relaxed); +} + +static inline void pool_hotbox_v2_record_free(uint32_t ci) { + if ((int)ci >= POOL_NUM_CLASSES) return; + atomic_fetch_add_explicit(&g_pool_hotbox_v2_stats[ci].free_calls, 1, memory_order_relaxed); +} + +void pool_hotbox_v2_record_free_call(uint32_t ci) { + pool_hotbox_v2_record_free(ci); +} + +void pool_hotbox_v2_record_free_fallback(uint32_t ci) { + if ((int)ci >= POOL_NUM_CLASSES) return; + atomic_fetch_add_explicit(&g_pool_hotbox_v2_stats[ci].free_fallback_v1, 1, memory_order_relaxed); +} + +enum pool_v2_pageof_fail { + POOL_V2_PAGEOF_NONE = 0, + POOL_V2_PAGEOF_OUT_OF_RANGE = 1, + POOL_V2_PAGEOF_MISALIGNED = 2, + POOL_V2_PAGEOF_HEADER_MISSING = 3, + POOL_V2_PAGEOF_UNKNOWN = 4, +}; + +static inline void pool_hotbox_v2_record_pageof_fail(uint32_t ci, int reason) { + if ((int)ci >= POOL_NUM_CLASSES) return; + switch (reason) { + case POOL_V2_PAGEOF_HEADER_MISSING: + atomic_fetch_add_explicit(&g_pool_hotbox_v2_stats[ci].page_of_fail_header_missing, 1, memory_order_relaxed); + break; + case POOL_V2_PAGEOF_OUT_OF_RANGE: + atomic_fetch_add_explicit(&g_pool_hotbox_v2_stats[ci].page_of_fail_out_of_range, 1, memory_order_relaxed); + break; + case POOL_V2_PAGEOF_MISALIGNED: + atomic_fetch_add_explicit(&g_pool_hotbox_v2_stats[ci].page_of_fail_misaligned, 1, memory_order_relaxed); + break; + case POOL_V2_PAGEOF_UNKNOWN: + default: + atomic_fetch_add_explicit(&g_pool_hotbox_v2_stats[ci].page_of_fail_unknown, 1, memory_order_relaxed); + break; + } +} + +static pool_page_v2* pool_hotbox_v2_page_acquire(void) { + pool_page_v2* p = (pool_page_v2*)calloc(1, sizeof(pool_page_v2)); + return p; +} + +static void pool_hotbox_v2_page_release(pool_page_v2* p) { + free(p); +} + +static void* pool_hotbox_v2_build_freelist(pool_page_v2* p) { + if (!p || !p->base || p->block_size == 0 || p->capacity == 0) return NULL; + uint8_t* base = (uint8_t*)p->base + POOL_HOTBOX_V2_HEADER_BYTES; + void* head = NULL; + for (uint32_t i = 0; i < p->capacity; i++) { + void* blk = base + ((size_t)i * p->block_size); + *(void**)blk = head; + head = blk; + } + return head; +} + +static PoolColdIface pool_cold_iface_v1(void); + +static pool_page_v2* pool_hotbox_v2_page_of(pool_ctx_v2* ctx, uint32_t ci, void* ptr, int* out_reason) { + if (out_reason) *out_reason = POOL_V2_PAGEOF_UNKNOWN; + if (!ctx || ci >= POOL_NUM_CLASSES || !ptr) return NULL; + // Compute page base by mask (POOL_PAGE_SIZE is a power of two). + void* page_base = pool_hotbox_v2_page_base(ptr, POOL_PAGE_SIZE); + pool_page_v2* p = (pool_page_v2*)pool_hotbox_v2_header_load(page_base); + if (!p) { + if (out_reason) *out_reason = POOL_V2_PAGEOF_HEADER_MISSING; + return NULL; + } + if (p->class_idx != ci || !p->base) { + if (out_reason) *out_reason = POOL_V2_PAGEOF_UNKNOWN; + return NULL; + } + + uint8_t* data_base = (uint8_t*)p->base + POOL_HOTBOX_V2_HEADER_BYTES; + size_t span = (size_t)p->block_size * (size_t)p->capacity; + uintptr_t off = (uintptr_t)((uint8_t*)ptr - data_base); + if (off >= span) { + if (out_reason) *out_reason = POOL_V2_PAGEOF_OUT_OF_RANGE; + return NULL; + } + if (off % p->block_size != 0) { + if (out_reason) *out_reason = POOL_V2_PAGEOF_MISALIGNED; + return NULL; + } + if (out_reason) *out_reason = POOL_V2_PAGEOF_NONE; + return p; +} + +static void pool_hotbox_v2_page_retire_slow(pool_ctx_v2* ctx, uint32_t ci, pool_page_v2* p) { + (void)ctx; + if (!p) return; + // Clear reverse header to avoid stale page_of hits. + pool_hotbox_v2_header_clear(p->base); + PoolColdIface cold = pool_cold_iface_v1(); + if (cold.retire_page) { + void* cold_ctx = NULL; + cold.retire_page(cold_ctx, ci, p->slab_ref, p->base); + } + pool_hotbox_v2_page_release(p); +} + +static void pool_hotbox_v2_push_partial(pool_class_v2* hc, pool_page_v2* p) { + if (!hc || !p) return; + p->next = hc->partial; + hc->partial = p; + if (hc->partial_count < UINT16_MAX) hc->partial_count++; +} + +static pool_page_v2* pool_hotbox_v2_pop_partial(pool_class_v2* hc) { + if (!hc || !hc->partial) return NULL; + pool_page_v2* p = hc->partial; + hc->partial = p->next; + p->next = NULL; + if (hc->partial_count > 0) hc->partial_count--; + return p; +} + +static pool_page_v2* pool_hotbox_v2_take_usable_partial(pool_class_v2* hc) { + if (!hc) return NULL; + pool_page_v2* prev = NULL; + pool_page_v2* p = hc->partial; + while (p) { + if (p->freelist && p->used < p->capacity) { + if (prev) { + prev->next = p->next; + } else { + hc->partial = p->next; + } + p->next = NULL; + if (hc->partial_count > 0) hc->partial_count--; + return p; + } + prev = p; + p = p->next; + } + return NULL; +} + +static int pool_hotbox_v2_unlink_partial(pool_class_v2* hc, pool_page_v2* target) { + if (!hc || !target) return 0; + pool_page_v2* prev = NULL; + pool_page_v2* p = hc->partial; + while (p) { + if (p == target) { + if (prev) { + prev->next = p->next; + } else { + hc->partial = p->next; + } + p->next = NULL; + if (hc->partial_count > 0) hc->partial_count--; + return 1; + } + prev = p; + p = p->next; + } + return 0; +} + +static void pool_hotbox_v2_record_alloc_fast(uint32_t ci) { + if ((int)ci >= POOL_NUM_CLASSES) return; + atomic_fetch_add_explicit(&g_pool_hotbox_v2_stats[ci].alloc_fast, 1, memory_order_relaxed); +} + +static void pool_hotbox_v2_record_free_fast(uint32_t ci) { + if ((int)ci >= POOL_NUM_CLASSES) return; + atomic_fetch_add_explicit(&g_pool_hotbox_v2_stats[ci].free_fast, 1, memory_order_relaxed); +} + +static inline void* pool_hotbox_v2_alloc_fast(pool_ctx_v2* ctx, uint32_t ci, uintptr_t site_id) { + pool_class_v2* hc = &ctx->cls[ci]; + pool_page_v2* p = hc->current; + if (p && p->freelist && p->used < p->capacity) { + void* blk = p->freelist; + p->freelist = *(void**)blk; + p->used++; + pool_hotbox_v2_record_alloc_fast(ci); + AllocHeader* hdr = (AllocHeader*)blk; + size_t class_sz = pool_hotbox_v2_block_size((int)ci); + mid_set_header(hdr, class_sz, site_id); + mid_page_inuse_inc(blk); + return (char*)blk + HEADER_SIZE; + } + if (p) { + // Keep exhausted current reachable for free() + pool_hotbox_v2_push_partial(hc, p); + hc->current = NULL; + } + p = pool_hotbox_v2_take_usable_partial(hc); + if (p) { + hc->current = p; + void* blk = p->freelist; + p->freelist = *(void**)blk; + p->used++; + pool_hotbox_v2_record_alloc_fast(ci); + AllocHeader* hdr = (AllocHeader*)blk; + size_t class_sz = pool_hotbox_v2_block_size((int)ci); + mid_set_header(hdr, class_sz, site_id); + mid_page_inuse_inc(blk); + return (char*)blk + HEADER_SIZE; + } + return NULL; +} + +static void pool_hotbox_v2_page_init(pool_page_v2* p, uint32_t ci, void* base, uint32_t block_size, uint32_t capacity, void* slab_ref) { + if (!p) return; + // Adjust capacity if caller did not account for header reservation. + size_t avail = (POOL_PAGE_SIZE > POOL_HOTBOX_V2_HEADER_BYTES) ? (POOL_PAGE_SIZE - POOL_HOTBOX_V2_HEADER_BYTES) : 0; + if (block_size > 0) { + uint32_t max_cap = (uint32_t)(avail / (size_t)block_size); + if (capacity == 0 || capacity > max_cap) capacity = max_cap; + } + p->freelist = NULL; + p->used = 0; + p->capacity = capacity; + p->block_size = block_size; + p->class_idx = ci; + p->base = base; + p->slab_ref = slab_ref; + p->next = NULL; + pool_hotbox_v2_header_store(p->base, p); +} + +static PoolColdIface pool_cold_iface_v1(void) { + PoolColdIface iface = {pool_cold_refill_page_v1, pool_cold_retire_page_v1}; + return iface; +} + +static void* pool_cold_refill_page_v1(void* cold_ctx, uint32_t ci, uint32_t* out_block_size, uint32_t* out_capacity, void** out_slab_ref) { + (void)cold_ctx; + uint32_t user_sz = pool_hotbox_v2_block_size((int)ci); + if (user_sz == 0) return NULL; + uint32_t bs = user_sz + HEADER_SIZE; + if (bs == 0) return NULL; + uint32_t cap = 0; + if (POOL_PAGE_SIZE > POOL_HOTBOX_V2_HEADER_BYTES) { + cap = (uint32_t)((POOL_PAGE_SIZE - POOL_HOTBOX_V2_HEADER_BYTES) / bs); + } + if (cap == 0) return NULL; + + // Over-allocate so we can align to POOL_PAGE_SIZE (64KiB) for O(1) page_of. + void* raw = mmap(NULL, POOL_HOTBOX_V2_MAP_LEN, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (raw == MAP_FAILED || !raw) { + return NULL; + } + uintptr_t aligned = ((uintptr_t)raw + (POOL_PAGE_SIZE - 1)) & ~((uintptr_t)POOL_PAGE_SIZE - 1); + void* base = (void*)aligned; + + // Register page ownership for same-thread fast free consistency. + mid_desc_register(base, (int)ci, (uint64_t)(uintptr_t)pthread_self()); + g_pool.refills[ci]++; + g_pool.total_pages_allocated++; + g_pool.pages_by_class[ci]++; + g_pool.total_bytes_allocated += POOL_HOTBOX_V2_MAP_LEN; + + if (out_block_size) *out_block_size = bs; + if (out_capacity) *out_capacity = cap; + // slab_ref keeps the raw mapping pointer for unmap. + if (out_slab_ref) *out_slab_ref = raw; + return base; +} + +static void pool_cold_retire_page_v1(void* cold_ctx, uint32_t ci, void* slab_ref, void* base) { + (void)cold_ctx; + (void)ci; + void* addr = slab_ref ? slab_ref : base; + if (!addr) return; + if (ci < POOL_NUM_CLASSES) { + if (g_pool.pages_by_class[ci] > 0) g_pool.pages_by_class[ci]--; + } + if (g_pool.total_pages_allocated > 0) g_pool.total_pages_allocated--; + if (g_pool.total_bytes_allocated >= POOL_HOTBOX_V2_MAP_LEN) g_pool.total_bytes_allocated -= POOL_HOTBOX_V2_MAP_LEN; + munmap(addr, POOL_HOTBOX_V2_MAP_LEN); +} + +void* pool_hotbox_v2_alloc(uint32_t class_idx, size_t size, uintptr_t site_id) { + (void)size; + (void)site_id; + if ((int)class_idx < 0 || class_idx >= POOL_NUM_CLASSES) return NULL; + pool_hotbox_v2_record_alloc(class_idx); + + pool_ctx_v2* ctx = pool_v2_tls_get(); + void* blk = pool_hotbox_v2_alloc_fast(ctx, class_idx, site_id); + if (blk) return blk; + + // slow: refill via Cold IF + PoolColdIface cold = pool_cold_iface_v1(); + uint32_t bs = 0, cap = 0; + void* slab_ref = NULL; + void* base = cold.refill_page ? cold.refill_page(NULL, class_idx, &bs, &cap, &slab_ref) : NULL; + if (!base || !bs || !cap) { + pool_hotbox_v2_record_alloc_refill_fail(class_idx); + return NULL; + } + + pool_class_v2* hc = &ctx->cls[class_idx]; + pool_page_v2* page = pool_hotbox_v2_page_acquire(); + if (!page) { + if (cold.retire_page) cold.retire_page(NULL, class_idx, slab_ref, base); + pool_hotbox_v2_record_alloc_refill_fail(class_idx); + return NULL; + } + pool_hotbox_v2_page_init(page, class_idx, base, bs, cap, slab_ref); + page->freelist = pool_hotbox_v2_build_freelist(page); + if (!page->freelist) { + pool_hotbox_v2_record_alloc_refill_fail(class_idx); + if (cold.retire_page) cold.retire_page(NULL, class_idx, slab_ref, base); + pool_hotbox_v2_page_release(page); + return NULL; + } + + hc->current = page; + pool_hotbox_v2_record_alloc_refill(class_idx); + return pool_hotbox_v2_alloc_fast(ctx, class_idx, site_id); +} + +int pool_hotbox_v2_free(uint32_t class_idx, void* raw_block) { + if (!raw_block || (int)class_idx < 0 || class_idx >= POOL_NUM_CLASSES) return 0; + pool_hotbox_v2_record_free(class_idx); + + pool_ctx_v2* ctx = pool_v2_tls_get(); + + int pageof_reason = POOL_V2_PAGEOF_UNKNOWN; + pool_page_v2* p = pool_hotbox_v2_page_of(ctx, class_idx, raw_block, &pageof_reason); + if (!p) { + pool_hotbox_v2_record_pageof_fail(class_idx, pageof_reason); + if (pool_hotbox_v2_stats_enabled()) { + static _Atomic uint32_t dbg = 0; + uint32_t n = atomic_fetch_add_explicit(&dbg, 1, memory_order_relaxed); + if (n < 4) { + pool_class_v2* hc = &ctx->cls[class_idx]; + fprintf(stderr, + "[POOL_V2 page_of_fail] cls=%u ptr=%p reason=%d cur=%p cur_base=%p cur_cap=%u cur_bs=%u partial=%p\n", + class_idx, raw_block, pageof_reason, + (void*)hc->current, + hc->current ? hc->current->base : NULL, + hc->current ? hc->current->capacity : 0u, + hc->current ? hc->current->block_size : 0u, + (void*)hc->partial); + } + } + return 0; // let caller fall back to v1 + } + + *(void**)raw_block = p->freelist; + p->freelist = raw_block; + if (p->used > 0) p->used--; + pool_hotbox_v2_record_free_fast(class_idx); + + pool_class_v2* hc = &ctx->cls[class_idx]; + if (p->used == 0) { + pool_hotbox_v2_unlink_partial(hc, p); + if (hc->current == p) hc->current = NULL; + if (hc->partial_count < hc->max_partial_pages) { + pool_hotbox_v2_push_partial(hc, p); + } else { + pool_hotbox_v2_page_retire_slow(ctx, class_idx, p); + } + } else { + if (!hc->current) hc->current = p; + } + return 1; +} + +__attribute__((destructor)) static void pool_hotbox_v2_dump_stats(void) { + if (!pool_hotbox_v2_stats_enabled()) return; + for (int i = 0; i < POOL_NUM_CLASSES; i++) { + uint64_t ac = atomic_load_explicit(&g_pool_hotbox_v2_stats[i].alloc_calls, memory_order_relaxed); + uint64_t ar = atomic_load_explicit(&g_pool_hotbox_v2_stats[i].alloc_refill, memory_order_relaxed); + uint64_t arf = atomic_load_explicit(&g_pool_hotbox_v2_stats[i].alloc_refill_fail, memory_order_relaxed); + uint64_t afb = atomic_load_explicit(&g_pool_hotbox_v2_stats[i].alloc_fallback_v1, memory_order_relaxed); + uint64_t fc = atomic_load_explicit(&g_pool_hotbox_v2_stats[i].free_calls, memory_order_relaxed); + uint64_t ffb = atomic_load_explicit(&g_pool_hotbox_v2_stats[i].free_fallback_v1, memory_order_relaxed); + uint64_t af = atomic_load_explicit(&g_pool_hotbox_v2_stats[i].alloc_fast, memory_order_relaxed); + uint64_t ff = atomic_load_explicit(&g_pool_hotbox_v2_stats[i].free_fast, memory_order_relaxed); + uint64_t pf_hdr = atomic_load_explicit(&g_pool_hotbox_v2_stats[i].page_of_fail_header_missing, memory_order_relaxed); + uint64_t pf_range = atomic_load_explicit(&g_pool_hotbox_v2_stats[i].page_of_fail_out_of_range, memory_order_relaxed); + uint64_t pf_mis = atomic_load_explicit(&g_pool_hotbox_v2_stats[i].page_of_fail_misaligned, memory_order_relaxed); + uint64_t pf_unknown = atomic_load_explicit(&g_pool_hotbox_v2_stats[i].page_of_fail_unknown, memory_order_relaxed); + if (ac || afb || fc || ffb || ar || arf || af || ff || pf_hdr || pf_range || pf_mis || pf_unknown) { + fprintf(stderr, "[POOL_V2_STATS] cls=%d alloc_calls=%llu alloc_fast=%llu alloc_refill=%llu alloc_refill_fail=%llu alloc_fb_v1=%llu free_calls=%llu free_fast=%llu free_fb_v1=%llu pageof_hdr=%llu pageof_range=%llu pageof_misaligned=%llu pageof_unknown=%llu\n", + i, (unsigned long long)ac, (unsigned long long)af, (unsigned long long)ar, + (unsigned long long)arf, (unsigned long long)afb, + (unsigned long long)fc, (unsigned long long)ff, (unsigned long long)ffb, + (unsigned long long)pf_hdr, (unsigned long long)pf_range, (unsigned long long)pf_mis, (unsigned long long)pf_unknown); + } + } +} + // Size class table (for O(1) lookup). Index 5/6 are Bridge classes for 32-64KB gap. // 7 classes including Bridge classes (40KB, 52KB) to fill 32-64KB gap static size_t g_class_sizes[POOL_NUM_CLASSES] = { @@ -893,10 +1407,9 @@ int hak_pool_get_shard_index(uintptr_t site_id) { return (int)((uint32_t)x & (POOL_NUM_SHARDS - 1)); } -// TLS helpers +// TLS helpers (non-inline helpers for shard bookkeeping) #include "box/pool_tls_core.inc.h" - // Refill/ACE (boxed) #include "box/pool_refill.inc.h" diff --git a/core/hakmem_sys.c b/core/hakmem_sys.c index 75941ca1..de4b553a 100644 --- a/core/hakmem_sys.c +++ b/core/hakmem_sys.c @@ -5,9 +5,12 @@ #include "hakmem_sys.h" #include "hakmem_debug.h" +#include "hakmem_env_cache.h" // For HAK_ENV_SS_MADVISE_STRICT +#include "box/ss_os_acquire_box.h" #include #include #include +#include // For errno values // madvise constants (Linux) #ifndef MADV_DONTNEED @@ -56,12 +59,16 @@ void hkm_sys_madvise_dontneed(void* ptr, size_t size) { HKM_TIME_START(t0); - int ret = madvise(ptr, size, MADV_DONTNEED); + int ret = ss_os_madvise_guarded(ptr, size, MADV_DONTNEED, "hakmem_sys_dontneed"); HKM_TIME_END(HKM_CAT_SYSCALL_MADVISE, t0); if (ret != 0) { - fprintf(stderr, "[HAKMEM SYS] madvise(DONTNEED, %p, %zu) failed\n", ptr, size); + fprintf(stderr, "[HAKMEM SYS] madvise(DONTNEED, %p, %zu) failed errno=%d\n", ptr, size, errno); + if (HAK_ENV_SS_MADVISE_STRICT() && errno == EINVAL) { + fprintf(stderr, "[HAKMEM SYS] Critical: madvise(DONTNEED) failed with EINVAL in strict mode. Aborting.\n"); + abort(); + } } } @@ -70,11 +77,15 @@ void hkm_sys_madvise_willneed(void* ptr, size_t size) { HKM_TIME_START(t0); - int ret = madvise(ptr, size, MADV_WILLNEED); + int ret = ss_os_madvise_guarded(ptr, size, MADV_WILLNEED, "hakmem_sys_willneed"); HKM_TIME_END(HKM_CAT_SYSCALL_MADVISE, t0); if (ret != 0) { - fprintf(stderr, "[HAKMEM SYS] madvise(WILLNEED, %p, %zu) failed\n", ptr, size); + fprintf(stderr, "[HAKMEM SYS] madvise(WILLNEED, %p, %zu) failed errno=%d\n", ptr, size, errno); + if (HAK_ENV_SS_MADVISE_STRICT() && errno == EINVAL) { + fprintf(stderr, "[HAKMEM SYS] Critical: madvise(WILLNEED) failed with EINVAL in strict mode. Aborting.\n"); + abort(); + } } } diff --git a/core/hakmem_tiny.c b/core/hakmem_tiny.c index a35563cf..e3694b7e 100644 --- a/core/hakmem_tiny.c +++ b/core/hakmem_tiny.c @@ -11,6 +11,7 @@ #include "box/tiny_next_ptr_box.h" // Box API: next pointer read/write #include "box/ptr_conversion_box.h" // Box API: pointer conversion #include "hakmem_env_cache.h" // Priority-2: ENV cache +#include "box/tiny_cold_iface_v1.h" // Cold boundary wrapper for TinyHotHeap v2 // Phase 1 modules (must come AFTER hakmem_tiny.h for TinyPool definition) #include "hakmem_tiny_batch_refill.h" // Phase 1: Batch refill/spill for mini-magazine #include "hakmem_tiny_stats.h" // Phase 1: Batched statistics (replaces XOR RNG) @@ -24,6 +25,8 @@ #include "tiny_route.h" #include "front/tiny_heap_v2.h" #include "box/tiny_front_stats_box.h" +#include "box/tiny_front_v3_env_box.h" +#include "box/ss_os_acquire_box.h" #include "tiny_tls_guard.h" #include "tiny_ready.h" #include "box/c7_meta_used_counter_box.h" @@ -32,6 +35,8 @@ #include "box/tiny_hotheap_v2_box.h" #include "box/tiny_route_env_box.h" #include "box/super_reg_box.h" +#include "tiny_region_id.h" +#include "tiny_debug_api.h" #include "hakmem_tiny_tls_list.h" #include "hakmem_tiny_remote_target.h" // Phase 2C-1: Remote target queue #include "hakmem_tiny_bg_spill.h" // Phase 2C-2: Background spill queue @@ -59,6 +64,13 @@ tiny_route_kind_t g_tiny_route_class[TINY_NUM_CLASSES] = {0}; int g_tiny_route_snapshot_done = 0; _Atomic uint64_t g_tiny_front_alloc_class[TINY_NUM_CLASSES] = {0}; _Atomic uint64_t g_tiny_front_free_class[TINY_NUM_CLASSES] = {0}; +TinyFrontV3Snapshot g_tiny_front_v3_snapshot = {0}; +int g_tiny_front_v3_snapshot_ready = 0; +static TinyFrontV3SizeClassEntry g_tiny_front_v3_lut[TINY_MAX_SIZE + 1] = {0}; +static int g_tiny_front_v3_lut_ready = 0; + +// Forward decls (to keep deps light in this TU) +int unified_cache_enabled(void); static int tiny_heap_stats_dump_enabled(void) { static int g = -1; @@ -70,6 +82,59 @@ static int tiny_heap_stats_dump_enabled(void) { return g; } +void tiny_front_v3_snapshot_init(void) { + if (g_tiny_front_v3_snapshot_ready) { + return; + } + TinyFrontV3Snapshot snap = { + .unified_cache_on = unified_cache_enabled(), + .tiny_guard_on = tiny_guard_is_enabled(), + .header_mode = (uint8_t)tiny_header_mode(), + .header_v3_enabled = tiny_header_v3_enabled(), + .header_v3_skip_c7 = tiny_header_v3_skip_c7(), + }; + g_tiny_front_v3_snapshot = snap; + g_tiny_front_v3_snapshot_ready = 1; +} + +void tiny_front_v3_size_class_lut_init(void) { + if (g_tiny_front_v3_lut_ready) { + return; + } + tiny_route_snapshot_init(); + size_t max_size = tiny_get_max_size(); + if (max_size > TINY_MAX_SIZE) { + max_size = TINY_MAX_SIZE; + } + for (size_t sz = 0; sz <= TINY_MAX_SIZE; sz++) { + TinyFrontV3SizeClassEntry e = { + .class_idx = TINY_FRONT_V3_INVALID_CLASS, + .route_kind = (uint8_t)TINY_ROUTE_LEGACY, + }; + if (sz == 0 || sz > max_size) { + g_tiny_front_v3_lut[sz] = e; + continue; + } + int cls = hak_tiny_size_to_class((int)sz); + if (cls >= 0 && cls < TINY_NUM_CLASSES) { + e.class_idx = (uint8_t)cls; + e.route_kind = (uint8_t)tiny_route_for_class((uint8_t)cls); + } + g_tiny_front_v3_lut[sz] = e; + } + g_tiny_front_v3_lut_ready = 1; +} + +const TinyFrontV3SizeClassEntry* tiny_front_v3_lut_lookup(size_t size) { + if (__builtin_expect(!g_tiny_front_v3_lut_ready, 0)) { + tiny_front_v3_size_class_lut_init(); + } + if (size == 0 || size > TINY_MAX_SIZE) { + return NULL; + } + return &g_tiny_front_v3_lut[size]; +} + __attribute__((destructor)) static void tiny_heap_stats_dump(void) { if (!tiny_heap_stats_enabled() || !tiny_heap_stats_dump_enabled()) { @@ -159,16 +224,24 @@ static inline int tiny_hotheap_v2_stats_enabled(void) { 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; +static _Atomic uint64_t g_tiny_hotheap_v2_route_hits[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static _Atomic uint64_t g_tiny_hotheap_v2_alloc_calls[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static _Atomic uint64_t g_tiny_hotheap_v2_alloc_fast[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static _Atomic uint64_t g_tiny_hotheap_v2_alloc_lease[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static _Atomic uint64_t g_tiny_hotheap_v2_alloc_fallback_v1[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static _Atomic uint64_t g_tiny_hotheap_v2_alloc_refill[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static _Atomic uint64_t g_tiny_hotheap_v2_refill_with_current[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static _Atomic uint64_t g_tiny_hotheap_v2_refill_with_partial[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static _Atomic uint64_t g_tiny_hotheap_v2_alloc_route_fb[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static _Atomic uint64_t g_tiny_hotheap_v2_free_calls[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static _Atomic uint64_t g_tiny_hotheap_v2_free_fast[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static _Atomic uint64_t g_tiny_hotheap_v2_free_fallback_v1[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static _Atomic uint64_t g_tiny_hotheap_v2_cold_refill_fail[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static _Atomic uint64_t g_tiny_hotheap_v2_cold_retire_calls[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static _Atomic uint64_t g_tiny_hotheap_v2_retire_calls_v2[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static _Atomic uint64_t g_tiny_hotheap_v2_partial_pushes[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static _Atomic uint64_t g_tiny_hotheap_v2_partial_pops[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static _Atomic uint64_t g_tiny_hotheap_v2_partial_peak[TINY_HOTHEAP_MAX_CLASSES] = {0}; typedef struct { _Atomic uint64_t prepare_calls; @@ -178,34 +251,54 @@ typedef struct { _Atomic uint64_t page_retired; } TinyHotHeapV2PageStats; -static TinyHotHeapV2PageStats g_tiny_hotheap_v2_page_stats = {0}; +static TinyHotHeapV2PageStats g_tiny_hotheap_v2_page_stats[TINY_HOTHEAP_MAX_CLASSES] = {0}; +static void tiny_hotheap_v2_page_retire_slow(tiny_hotheap_ctx_v2* ctx, + uint8_t class_idx, + tiny_hotheap_page_v2* page); -void tiny_hotheap_v2_record_route_fallback(void) { - atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_alloc_route_fb, 1, memory_order_relaxed); +static inline uint8_t tiny_hotheap_v2_idx(uint8_t class_idx) { + return (class_idx < TINY_HOTHEAP_MAX_CLASSES) ? class_idx : 0; } -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_record_route_fallback(uint8_t class_idx) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_alloc_route_fb[tiny_hotheap_v2_idx(class_idx)], + 1, + memory_order_relaxed); +} + +void tiny_hotheap_v2_record_free_fallback(uint8_t class_idx) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_free_fallback_v1[tiny_hotheap_v2_idx(class_idx)], + 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); + uint8_t ci = 7; + out->route_hits = atomic_load_explicit(&g_tiny_hotheap_v2_route_hits[ci], memory_order_relaxed); + out->alloc_calls = atomic_load_explicit(&g_tiny_hotheap_v2_alloc_calls[ci], memory_order_relaxed); + out->alloc_fast = atomic_load_explicit(&g_tiny_hotheap_v2_alloc_fast[ci], memory_order_relaxed); + out->alloc_lease = atomic_load_explicit(&g_tiny_hotheap_v2_alloc_lease[ci], memory_order_relaxed); + out->alloc_refill = atomic_load_explicit(&g_tiny_hotheap_v2_alloc_refill[ci], memory_order_relaxed); + out->refill_with_current = atomic_load_explicit(&g_tiny_hotheap_v2_refill_with_current[ci], memory_order_relaxed); + out->refill_with_partial = atomic_load_explicit(&g_tiny_hotheap_v2_refill_with_partial[ci], memory_order_relaxed); + out->alloc_fallback_v1 = atomic_load_explicit(&g_tiny_hotheap_v2_alloc_fallback_v1[ci], memory_order_relaxed); + out->alloc_route_fb = atomic_load_explicit(&g_tiny_hotheap_v2_alloc_route_fb[ci], memory_order_relaxed); + out->free_calls = atomic_load_explicit(&g_tiny_hotheap_v2_free_calls[ci], memory_order_relaxed); + out->free_fast = atomic_load_explicit(&g_tiny_hotheap_v2_free_fast[ci], memory_order_relaxed); + out->free_fallback_v1 = atomic_load_explicit(&g_tiny_hotheap_v2_free_fallback_v1[ci], memory_order_relaxed); + out->cold_refill_fail = atomic_load_explicit(&g_tiny_hotheap_v2_cold_refill_fail[ci], memory_order_relaxed); + out->cold_retire_calls = atomic_load_explicit(&g_tiny_hotheap_v2_cold_retire_calls[ci], memory_order_relaxed); + out->retire_calls_v2 = atomic_load_explicit(&g_tiny_hotheap_v2_retire_calls_v2[ci], memory_order_relaxed); + out->prepare_calls = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats[ci].prepare_calls, memory_order_relaxed); + out->prepare_with_current_null = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats[ci].prepare_with_current_null, memory_order_relaxed); + out->prepare_from_partial = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats[ci].prepare_from_partial, memory_order_relaxed); + out->free_made_current = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats[ci].free_made_current, memory_order_relaxed); + out->page_retired = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats[ci].page_retired, memory_order_relaxed); + out->partial_pushes = atomic_load_explicit(&g_tiny_hotheap_v2_partial_pushes[ci], memory_order_relaxed); + out->partial_pops = atomic_load_explicit(&g_tiny_hotheap_v2_partial_pops[ci], memory_order_relaxed); + out->partial_peak = atomic_load_explicit(&g_tiny_hotheap_v2_partial_peak[ci], memory_order_relaxed); } static tiny_hotheap_page_v2* tiny_hotheap_v2_acquire_page_node(tiny_hotheap_class_v2* hc) { @@ -246,6 +339,57 @@ static tiny_hotheap_page_v2* tiny_hotheap_v2_find_page(tiny_hotheap_class_v2* hc return NULL; } +static inline void tiny_hotheap_v2_partial_push(tiny_hotheap_class_v2* hc, + tiny_hotheap_page_v2* page, + uint8_t class_idx, + int stats_on) { + if (!hc || !page) return; + page->next = hc->partial_pages; + hc->partial_pages = page; + if (hc->partial_count < UINT16_MAX) { + hc->partial_count++; + } + if (stats_on) { + uint8_t idx = tiny_hotheap_v2_idx(class_idx); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_partial_pushes[idx], 1, memory_order_relaxed); + uint64_t cur = hc->partial_count; + uint64_t old = atomic_load_explicit(&g_tiny_hotheap_v2_partial_peak[idx], memory_order_relaxed); + while (cur > old && + !atomic_compare_exchange_weak_explicit(&g_tiny_hotheap_v2_partial_peak[idx], + &old, + cur, + memory_order_relaxed, + memory_order_relaxed)) { + old = atomic_load_explicit(&g_tiny_hotheap_v2_partial_peak[idx], memory_order_relaxed); + } + } +} + +static inline void tiny_hotheap_v2_maybe_trim_partial(tiny_hotheap_ctx_v2* ctx, + tiny_hotheap_class_v2* hc, + uint8_t class_idx, + int stats_on) { + if (!ctx || !hc) return; + uint16_t limit = hc->max_partial_pages; + if (limit == 0) { + return; + } + while (hc->partial_count > limit && hc->partial_pages) { + tiny_hotheap_page_v2* victim = hc->partial_pages; + hc->partial_pages = victim->next; + if (hc->partial_count > 0) { + hc->partial_count--; + } + victim->next = NULL; + if (stats_on) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_partial_pops[tiny_hotheap_v2_idx(class_idx)], + 1, + memory_order_relaxed); + } + tiny_hotheap_v2_page_retire_slow(ctx, class_idx, victim); + } +} + static inline void tiny_hotheap_v2_build_freelist(tiny_hotheap_page_v2* page, uint8_t class_idx, uint16_t stride) { @@ -265,16 +409,6 @@ static inline void tiny_hotheap_v2_build_freelist(tiny_hotheap_page_v2* page, 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) { @@ -295,6 +429,9 @@ static void tiny_hotheap_v2_unlink_page(tiny_hotheap_class_v2* hc, tiny_hotheap_ *head = cur->next; } cur->next = NULL; + if (i == 0 && hc->partial_count > 0) { + hc->partial_count--; + } break; } prev = cur; @@ -304,17 +441,35 @@ static void tiny_hotheap_v2_unlink_page(tiny_hotheap_class_v2* hc, tiny_hotheap_ } static tiny_hotheap_page_v2* tiny_hotheap_v2_refill_slow(tiny_hotheap_ctx_v2* ctx, uint8_t class_idx) { - if (!ctx || class_idx != 7) { + if (!ctx || class_idx >= TINY_HOTHEAP_MAX_CLASSES) { 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); + int stats_on = tiny_hotheap_v2_stats_enabled(); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_alloc_refill[class_idx], 1, memory_order_relaxed); + TinyHeapClassStats* stats = tiny_heap_stats_for_class(class_idx); 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) { + if (hc) { + if (hc->current_page) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_refill_with_current[class_idx], + 1, + memory_order_relaxed); + } + if (hc->partial_pages) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_refill_with_partial[class_idx], + 1, + memory_order_relaxed); + } + } + + // Cold iface (v1 TinyHeap) からページを 1 枚借りる + TinyColdIface cold = tiny_cold_iface_v1(); + tiny_heap_ctx_t* cold_ctx = tiny_heap_ctx_for_thread(); + tiny_heap_page_t* ipage = cold.refill_page ? cold.refill_page(cold_ctx, class_idx) : NULL; + if (!ipage || !ipage->base || ipage->capacity == 0 || ipage->meta == NULL) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_cold_refill_fail[class_idx], 1, memory_order_relaxed); return NULL; } @@ -327,33 +482,25 @@ static tiny_hotheap_page_v2* tiny_hotheap_v2_refill_slow(tiny_hotheap_ctx_v2* ct 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; - } + page->lease_page = ipage; + page->meta = ipage->meta; + page->ss = ipage->ss; + page->base = ipage->base; + page->capacity = ipage->capacity; + page->slab_idx = ipage->slab_idx; + page->freelist = NULL; + page->used = 0; + 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_v2_build_freelist(page, class_idx, stride); 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; + tiny_hotheap_v2_partial_push(hc, old_cur, class_idx, stats_on); } + tiny_hotheap_v2_maybe_trim_partial(ctx, hc, class_idx, stats_on); 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", @@ -361,7 +508,7 @@ static tiny_hotheap_page_v2* tiny_hotheap_v2_refill_slow(tiny_hotheap_ctx_v2* ct 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 NULL; } return hc->current_page; } @@ -370,17 +517,26 @@ 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; + uint8_t idx = tiny_hotheap_v2_idx(class_idx); 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->lease_page) { + page->lease_page->used = page->used; + page->lease_page->free_list = page->freelist; + if (page->lease_page->meta) { + atomic_store_explicit(&page->lease_page->meta->freelist, page->freelist, memory_order_release); + atomic_store_explicit(&page->lease_page->meta->used, page->used, memory_order_relaxed); + } + } + TinyColdIface cold = tiny_cold_iface_v1(); + tiny_heap_ctx_t* cold_ctx = tiny_heap_ctx_for_thread(); + if (cold.retire_page) { + cold.retire_page(cold_ctx, class_idx, page->lease_page); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_cold_retire_calls[idx], 1, memory_order_relaxed); + } + if (tiny_hotheap_v2_stats_enabled()) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_retire_calls_v2[idx], 1, memory_order_relaxed); + } if (page != &hc->storage_page) { free(page); } else { @@ -394,38 +550,42 @@ static void tiny_hotheap_v2_page_retire_slow(tiny_hotheap_ctx_v2* ctx, } } if (tiny_hotheap_v2_stats_enabled()) { - atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats.page_retired, 1, memory_order_relaxed); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats[idx].page_retired, 1, memory_order_relaxed); } } -static inline void* tiny_hotheap_v2_try_pop(tiny_hotheap_page_v2* candidate, - tiny_heap_class_t* v1hcls, +static inline void* tiny_hotheap_v2_try_pop(tiny_hotheap_class_v2* hc, + tiny_hotheap_page_v2* page, + uint8_t class_idx, TinyHeapClassStats* stats, int stats_on) { - if (!candidate || !candidate->lease_page || !v1hcls) { + if (!hc || !page || !page->base || page->capacity == 0) { 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)) { + if (hc->stride == 0) { + hc->stride = (uint16_t)tiny_stride_for_class(class_idx); + } + const uint16_t stride = hc->stride; + void* block = NULL; + if (page->freelist) { + block = page->freelist; + void* next = tiny_next_read(class_idx, block); + page->freelist = next; + } else if (page->used < page->capacity) { + block = (void*)((uint8_t*)page->base + ((size_t)page->used * stride)); + } else { 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); - } + page->used++; 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); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_alloc_fast[tiny_hotheap_v2_idx(class_idx)], + 1, + memory_order_relaxed); } - candidate->freelist = ipage->free_list; - candidate->used = ipage->used; - return tiny_region_id_write_header(user, 7); + return tiny_region_id_write_header(block, class_idx); } __attribute__((destructor)) @@ -433,35 +593,55 @@ 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); + for (uint8_t ci = 0; ci < TINY_HOTHEAP_MAX_CLASSES; ci++) { + uint64_t alloc_calls = atomic_load_explicit(&g_tiny_hotheap_v2_alloc_calls[ci], memory_order_relaxed); + uint64_t route_hits = atomic_load_explicit(&g_tiny_hotheap_v2_route_hits[ci], memory_order_relaxed); + uint64_t alloc_fast = atomic_load_explicit(&g_tiny_hotheap_v2_alloc_fast[ci], memory_order_relaxed); + uint64_t alloc_lease = atomic_load_explicit(&g_tiny_hotheap_v2_alloc_lease[ci], memory_order_relaxed); + uint64_t alloc_fb = atomic_load_explicit(&g_tiny_hotheap_v2_alloc_fallback_v1[ci], memory_order_relaxed); + uint64_t free_calls = atomic_load_explicit(&g_tiny_hotheap_v2_free_calls[ci], memory_order_relaxed); + uint64_t free_fast = atomic_load_explicit(&g_tiny_hotheap_v2_free_fast[ci], memory_order_relaxed); + uint64_t free_fb = atomic_load_explicit(&g_tiny_hotheap_v2_free_fallback_v1[ci], memory_order_relaxed); + uint64_t cold_refill_fail = atomic_load_explicit(&g_tiny_hotheap_v2_cold_refill_fail[ci], memory_order_relaxed); + uint64_t cold_retire_calls = atomic_load_explicit(&g_tiny_hotheap_v2_cold_retire_calls[ci], memory_order_relaxed); + uint64_t retire_calls_v2 = atomic_load_explicit(&g_tiny_hotheap_v2_retire_calls_v2[ci], memory_order_relaxed); + uint64_t partial_pushes = atomic_load_explicit(&g_tiny_hotheap_v2_partial_pushes[ci], memory_order_relaxed); + uint64_t partial_pops = atomic_load_explicit(&g_tiny_hotheap_v2_partial_pops[ci], memory_order_relaxed); + uint64_t partial_peak = atomic_load_explicit(&g_tiny_hotheap_v2_partial_peak[ci], memory_order_relaxed); + uint64_t refill_with_cur = atomic_load_explicit(&g_tiny_hotheap_v2_refill_with_current[ci], memory_order_relaxed); + uint64_t refill_with_partial = atomic_load_explicit(&g_tiny_hotheap_v2_refill_with_partial[ci], 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), - }; + TinyHotHeapV2PageStats ps = { + .prepare_calls = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats[ci].prepare_calls, memory_order_relaxed), + .prepare_with_current_null = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats[ci].prepare_with_current_null, memory_order_relaxed), + .prepare_from_partial = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats[ci].prepare_from_partial, memory_order_relaxed), + .free_made_current = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats[ci].free_made_current, memory_order_relaxed), + .page_retired = atomic_load_explicit(&g_tiny_hotheap_v2_page_stats[ci].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) { + 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 || retire_calls_v2 || partial_pushes || partial_pops || partial_peak)) { + continue; + } + + tiny_route_kind_t route_kind = tiny_route_for_class(ci); 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", + "[HOTHEAP_V2_STATS cls=%u route=%d] route_hits=%llu alloc_calls=%llu alloc_fast=%llu alloc_lease=%llu alloc_refill=%llu refill_cur=%llu refill_partial=%llu alloc_fb_v1=%llu alloc_route_fb=%llu cold_refill_fail=%llu cold_retire_calls=%llu retire_v2=%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 partial_push=%llu partial_pop=%llu partial_peak=%llu\n", + (unsigned)ci, + (int)route_kind, (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)atomic_load_explicit(&g_tiny_hotheap_v2_alloc_refill[ci], memory_order_relaxed), + (unsigned long long)refill_with_cur, + (unsigned long long)refill_with_partial, (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)atomic_load_explicit(&g_tiny_hotheap_v2_alloc_route_fb[ci], memory_order_relaxed), + (unsigned long long)cold_refill_fail, + (unsigned long long)cold_retire_calls, + (unsigned long long)retire_calls_v2, (unsigned long long)free_calls, (unsigned long long)free_fast, (unsigned long long)free_fb, @@ -469,7 +649,10 @@ static void tiny_hotheap_v2_stats_dump(void) { (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); + (unsigned long long)ps.page_retired, + (unsigned long long)partial_pushes, + (unsigned long long)partial_pops, + (unsigned long long)partial_peak); } } tiny_hotheap_ctx_v2* tiny_hotheap_v2_tls_get(void) { @@ -484,6 +667,8 @@ tiny_hotheap_ctx_v2* tiny_hotheap_v2_tls_get(void) { 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); + ctx->cls[i].max_partial_pages = (i == 7 || i == 6) ? 2 : 0; // C6/C7 は 1〜2 枚握る + ctx->cls[i].partial_count = 0; } } return ctx; @@ -491,143 +676,174 @@ tiny_hotheap_ctx_v2* tiny_hotheap_v2_tls_get(void) { void* tiny_hotheap_v2_alloc(uint8_t class_idx) { int stats_on = tiny_hotheap_v2_stats_enabled(); + uint8_t idx = tiny_hotheap_v2_idx(class_idx); 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); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_route_hits[idx], 1, memory_order_relaxed); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_alloc_calls[idx], 1, memory_order_relaxed); } - if (__builtin_expect(class_idx != 7, 0)) { - return NULL; // いまは C7 専用 + if (__builtin_expect(!(class_idx == 6 || class_idx == 7), 0)) { + return NULL; // いまは C6/C7 のみ } tiny_hotheap_ctx_v2* v2ctx = tiny_hotheap_v2_tls_get(); - tiny_hotheap_class_v2* vhcls = v2ctx ? &v2ctx->cls[7] : NULL; + tiny_hotheap_class_v2* vhcls = v2ctx ? &v2ctx->cls[class_idx] : NULL; tiny_hotheap_page_v2* v2page = vhcls ? vhcls->current_page : NULL; - 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); + TinyHeapClassStats* stats = tiny_heap_stats_for_class(class_idx); + + // current_page が壊れていそうなら一度捨てて slow に降りる + if (v2page && (!v2page->base || v2page->capacity == 0)) { + vhcls->current_page = NULL; + v2page = NULL; + } // Hot path: current_page → partial → refill - void* user = tiny_hotheap_v2_try_pop(v2page, v1hcls, stats, stats_on); + void* user = tiny_hotheap_v2_try_pop(vhcls, v2page, class_idx, stats, stats_on); if (user) { return user; } + + // move exhausted current_page to full list if needed + if (vhcls && v2page && v2page->used >= v2page->capacity && vhcls->current_page == v2page) { + vhcls->current_page = NULL; + v2page->next = vhcls->full_pages; + vhcls->full_pages = v2page; + } + 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); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats[idx].prepare_calls, 1, memory_order_relaxed); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats[idx].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); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats[idx].prepare_with_current_null, 1, memory_order_relaxed); } } v2page = vhcls->partial_pages; vhcls->partial_pages = vhcls->partial_pages->next; + if (vhcls->partial_count > 0) { + vhcls->partial_count--; + } + if (stats_on) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_partial_pops[idx], 1, memory_order_relaxed); + } v2page->next = NULL; vhcls->current_page = v2page; - user = tiny_hotheap_v2_try_pop(v2page, v1hcls, stats, stats_on); + user = tiny_hotheap_v2_try_pop(vhcls, v2page, class_idx, stats, stats_on); if (user) { return user; } + if (v2page->used >= v2page->capacity) { + v2page->next = vhcls->full_pages; + vhcls->full_pages = v2page; + vhcls->current_page = NULL; + } } // Lease a page from v1 (C7 SAFE) and wrap it - tiny_hotheap_page_v2* leased = tiny_hotheap_v2_refill_slow(v2ctx, 7); - if (!leased || !v1hcls) { + tiny_hotheap_page_v2* leased = tiny_hotheap_v2_refill_slow(v2ctx, class_idx); + if (!leased) { 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); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_alloc_fallback_v1[idx], 1, memory_order_relaxed); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_alloc_route_fb[idx], 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 + size_t size = vhcls ? (vhcls->stride ? vhcls->stride : tiny_stride_for_class(class_idx)) : tiny_stride_for_class(class_idx); + if (class_idx == 7) { + return tiny_c7_alloc_fast(size); // safety fallback to v1 + } + tiny_heap_ctx_t* cold_ctx = tiny_heap_ctx_for_thread(); + return tiny_heap_alloc_class_fast(cold_ctx, class_idx, size); } 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); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_alloc_lease[idx], 1, memory_order_relaxed); } - user = tiny_hotheap_v2_try_pop(v2page, v1hcls, stats, stats_on); + user = tiny_hotheap_v2_try_pop(vhcls, v2page, class_idx, stats, stats_on); if (user) { return user; } 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_alloc_fallback_v1[idx], 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); + size_t size = vhcls ? (vhcls->stride ? vhcls->stride : tiny_stride_for_class(class_idx)) : tiny_stride_for_class(class_idx); + if (class_idx == 7) { + return tiny_c7_alloc_fast(size); + } + tiny_heap_ctx_t* cold_ctx = tiny_heap_ctx_for_thread(); + return tiny_heap_alloc_class_fast(cold_ctx, class_idx, size); } void tiny_hotheap_v2_free(uint8_t class_idx, void* p, void* meta) { - if (__builtin_expect(class_idx != 7, 0)) { + if (__builtin_expect(!(class_idx == 6 || class_idx == 7), 0)) { return; } + uint8_t idx = tiny_hotheap_v2_idx(class_idx); 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); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_free_calls[idx], 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_class_v2* vhcls = v2ctx ? &v2ctx->cls[class_idx] : 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); - - 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; + tiny_hotheap_page_v2* page = tiny_hotheap_v2_find_page(vhcls, class_idx, p, meta_ptr); + if (page && page->base && page->capacity > 0) { + tiny_next_write(class_idx, p, page->freelist); + page->freelist = p; + if (page->used > 0) { + page->used--; } 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); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_page_stats[idx].free_made_current, 1, memory_order_relaxed); + } + if (page->used == 0) { + // 空ページは一度 partial に温存し、上限を超えたら retire + tiny_hotheap_v2_unlink_page(vhcls, page); + page->next = NULL; + if (vhcls && vhcls->current_page == NULL) { + vhcls->current_page = page; + } else if (vhcls) { + tiny_hotheap_v2_partial_push(vhcls, page, class_idx, stats_on); + tiny_hotheap_v2_maybe_trim_partial(v2ctx, vhcls, class_idx, stats_on); + } + } else if (stats_on) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_free_fast[idx], 1, memory_order_relaxed); + } + if (stats_on && page->used == 0) { + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_free_fast[idx], 1, memory_order_relaxed); } return; } // Fallback: mimic v1 free path if (stats_on) { - atomic_fetch_add_explicit(&g_tiny_hotheap_v2_c7_free_fallback_v1, 1, memory_order_relaxed); + atomic_fetch_add_explicit(&g_tiny_hotheap_v2_free_fallback_v1[idx], 1, memory_order_relaxed); } SuperSlab* ss = hak_super_lookup(p); if (ss && ss->magic == SUPERSLAB_MAGIC) { int slab_idx = slab_index_for(ss, p); if (slab_idx >= 0 && slab_idx < ss_slabs_capacity(ss)) { - tiny_c7_free_fast_with_meta(ss, slab_idx, p); + if (class_idx == 7) { + tiny_c7_free_fast_with_meta(ss, slab_idx, p); + } else { + tiny_heap_ctx_t* cold_ctx = tiny_heap_ctx_for_thread(); + tiny_heap_free_class_fast_with_meta(cold_ctx, class_idx, ss, slab_idx, p); + } return; } } - tiny_c7_free_fast(p); + if (class_idx == 7) { + tiny_c7_free_fast(p); + } else { + tiny_heap_ctx_t* cold_ctx = tiny_heap_ctx_for_thread(); + tiny_heap_free_class_fast(cold_ctx, class_idx, p); + } } #if !HAKMEM_BUILD_RELEASE diff --git a/core/hakmem_tiny_init.inc b/core/hakmem_tiny_init.inc index 2d1895b2..5bd2daa7 100644 --- a/core/hakmem_tiny_init.inc +++ b/core/hakmem_tiny_init.inc @@ -9,6 +9,11 @@ // // Cold path only - called once at startup. +// Some build configurations expect this hook but do not provide an implementation. +// Provide a no-op stub so that non-debug builds continue to link without optional +// signal-dump support. +static inline void hak_tiny_enable_signal_dump(void) { } + void hak_tiny_init(void) { if (g_tiny_initialized) return; diff --git a/core/hakmem_tiny_intel.inc b/core/hakmem_tiny_intel.inc index e3416f1e..0d25fda4 100644 --- a/core/hakmem_tiny_intel.inc +++ b/core/hakmem_tiny_intel.inc @@ -9,6 +9,9 @@ typedef struct { uint16_t thread_id; // low bits of thread id (best-effort) } AllocEvent; +// Forward decl (defined in ss_os_acquire_box.h) +extern int ss_os_madvise_guarded(void* ptr, size_t len, int advice, const char* where); + #define EVENTQ_CAP 65536u #define EVENTQ_MASK (EVENTQ_CAP - 1u) static _Atomic uint32_t g_ev_tail = 0; @@ -689,7 +692,7 @@ static inline void superslab_partial_release(SuperSlab* ss, uint32_t epoch) { uint32_t prev = ss->partial_epoch; if (epoch != 0 && (epoch - prev) < g_ss_partial_interval) return; size_t len = (size_t)1 << ss->lg_size; - if (madvise(ss, len, MADV_DONTNEED) == 0) { + if (ss_os_madvise_guarded(ss, len, MADV_DONTNEED, "tiny_ss_partial") == 0) { ss->partial_epoch = epoch; } #else diff --git a/core/smallobject_hotbox_v3.c b/core/smallobject_hotbox_v3.c new file mode 100644 index 00000000..56488d6d --- /dev/null +++ b/core/smallobject_hotbox_v3.c @@ -0,0 +1,325 @@ +// smallobject_hotbox_v3.c - SmallObject HotHeap v3 skeleton (C7-first) +// Phase A/B: 型と stats だけ。alloc/free は v1 にフォールバックさせる。 + +#include +#include +#include +#include "box/smallobject_hotbox_v3_box.h" +#include "box/smallobject_cold_iface_v1.h" +#include "box/tiny_heap_box.h" +#include "box/tiny_front_v3_env_box.h" +#include "hakmem_tiny.h" // TINY_SLAB_SIZE mask for page_of +#include "tiny_region_id.h" + +static __thread so_ctx_v3* g_so_ctx_v3; +static int g_so_stats_enabled = -1; +static so_stats_class_v3 g_so_stats[SMALLOBJECT_NUM_CLASSES]; + +int so_v3_stats_enabled(void) { + if (__builtin_expect(g_so_stats_enabled == -1, 0)) { + const char* e = getenv("HAKMEM_SMALL_HEAP_V3_STATS"); + g_so_stats_enabled = (e && *e && *e != '0') ? 1 : 0; + } + return g_so_stats_enabled; +} + +static inline so_stats_class_v3* so_stats_for(uint8_t ci) { + if (!so_v3_stats_enabled()) return NULL; + if (ci >= SMALLOBJECT_NUM_CLASSES) return NULL; + return &g_so_stats[ci]; +} + +void so_v3_record_route_hit(uint8_t ci) { + so_stats_class_v3* st = so_stats_for(ci); + if (st) atomic_fetch_add_explicit(&st->route_hits, 1, memory_order_relaxed); +} + +void so_v3_record_alloc_call(uint8_t ci) { + so_stats_class_v3* st = so_stats_for(ci); + if (st) atomic_fetch_add_explicit(&st->alloc_calls, 1, memory_order_relaxed); +} + +void so_v3_record_alloc_refill(uint8_t ci) { + so_stats_class_v3* st = so_stats_for(ci); + if (st) atomic_fetch_add_explicit(&st->alloc_refill, 1, memory_order_relaxed); +} + +void so_v3_record_alloc_fallback(uint8_t ci) { + so_stats_class_v3* st = so_stats_for(ci); + if (st) atomic_fetch_add_explicit(&st->alloc_fallback_v1, 1, memory_order_relaxed); +} + +void so_v3_record_free_call(uint8_t ci) { + so_stats_class_v3* st = so_stats_for(ci); + if (st) atomic_fetch_add_explicit(&st->free_calls, 1, memory_order_relaxed); +} + +void so_v3_record_free_fallback(uint8_t ci) { + so_stats_class_v3* st = so_stats_for(ci); + if (st) atomic_fetch_add_explicit(&st->free_fallback_v1, 1, memory_order_relaxed); +} + +so_ctx_v3* so_tls_get(void) { + so_ctx_v3* ctx = g_so_ctx_v3; + if (__builtin_expect(ctx == NULL, 0)) { + ctx = (so_ctx_v3*)calloc(1, sizeof(so_ctx_v3)); + if (!ctx) { + fprintf(stderr, "[SMALL_HEAP_V3] TLS alloc failed\n"); + abort(); + } + for (int i = 0; i < SMALLOBJECT_NUM_CLASSES; i++) { + so_class_v3* hc = &ctx->cls[i]; + hc->block_size = (uint32_t)tiny_stride_for_class(i); + hc->max_partial_pages = 2; + } + g_so_ctx_v3 = ctx; + } + return ctx; +} + +static inline void* so_build_freelist(so_page_v3* page) { + if (!page || !page->base || page->block_size == 0 || page->capacity == 0) return NULL; + uint8_t* base = (uint8_t*)page->base; + void* head = NULL; + for (uint32_t i = 0; i < page->capacity; i++) { + uint8_t* blk = base + ((size_t)i * page->block_size); + *(void**)blk = head; + head = blk; + } + return head; +} + +static inline int so_ptr_in_page(so_page_v3* page, void* ptr) { + if (!page || !ptr) return 0; + uintptr_t base = (uintptr_t)page->base; + uintptr_t p = (uintptr_t)ptr; + uintptr_t span = (uintptr_t)page->block_size * (uintptr_t)page->capacity; + if (p < base || p >= base + span) return 0; + if (((p - base) % page->block_size) != 0) return 0; + return 1; +} + +static inline so_page_v3* so_page_of(so_class_v3* hc, void* ptr) { + if (!ptr || !hc) return NULL; + so_page_v3* page = hc->current; + if (page && so_ptr_in_page(page, ptr)) { + return page; + } + page = hc->partial; + while (page) { + if (so_ptr_in_page(page, ptr)) { + return page; + } + page = page->next; + } + return NULL; +} + +static inline void so_page_push_partial(so_class_v3* hc, so_page_v3* page) { + if (!hc || !page) return; + page->next = hc->partial; + hc->partial = page; + hc->partial_count++; +} + +static inline void so_page_retire_slow(so_ctx_v3* ctx, uint32_t ci, so_page_v3* page); + +static inline void* so_alloc_fast(so_ctx_v3* ctx, uint32_t ci) { + so_class_v3* hc = &ctx->cls[ci]; + const bool skip_header_c7 = (ci == 7) && tiny_header_v3_enabled() && tiny_header_v3_skip_c7(); + so_page_v3* p = hc->current; + if (p && p->freelist && p->used < p->capacity) { + void* blk = p->freelist; + p->freelist = *(void**)blk; + p->used++; + if (skip_header_c7) { + uint8_t* header_ptr = (uint8_t*)blk; + *header_ptr = (uint8_t)(HEADER_MAGIC | (ci & HEADER_CLASS_MASK)); + return header_ptr + 1; // mirror tiny_region_id_write_header fast path + } + return tiny_region_id_write_header(blk, (int)ci); + } + + if (hc->partial) { + so_page_v3* old_cur = hc->current; + p = hc->partial; + hc->partial = p->next; + if (hc->partial_count > 0) { + hc->partial_count--; + } + p->next = NULL; + hc->current = p; + if (old_cur && old_cur != p) { + if (hc->partial_count < hc->max_partial_pages) { + so_page_push_partial(hc, old_cur); + } else { + so_page_retire_slow(ctx, ci, old_cur); + } + } + if (p->freelist && p->used < p->capacity) { + void* blk = p->freelist; + p->freelist = *(void**)blk; + p->used++; + if (skip_header_c7) { + uint8_t* header_ptr = (uint8_t*)blk; + *header_ptr = (uint8_t)(HEADER_MAGIC | (ci & HEADER_CLASS_MASK)); + return header_ptr + 1; + } + return tiny_region_id_write_header(blk, (int)ci); + } + } + return NULL; +} + +static inline int so_unlink_partial(so_class_v3* hc, so_page_v3* target) { + if (!hc || !target) return 0; + so_page_v3* prev = NULL; + so_page_v3* cur = hc->partial; + while (cur) { + if (cur == target) { + if (prev) { + prev->next = cur->next; + } else { + hc->partial = cur->next; + } + if (hc->partial_count > 0) { + hc->partial_count--; + } + return 1; + } + prev = cur; + cur = cur->next; + } + return 0; +} + +static inline void so_page_retire_slow(so_ctx_v3* ctx, uint32_t ci, so_page_v3* page) { + SmallObjectColdIface cold = smallobject_cold_iface_v1(); + void* cold_ctx = (void*)tiny_heap_ctx_for_thread(); + if (cold.retire_page) { + cold.retire_page(cold_ctx, ci, page); + } else { + free(page); + } + (void)ctx; +} + +static inline void so_free_fast(so_ctx_v3* ctx, uint32_t ci, void* ptr) { + so_class_v3* hc = &ctx->cls[ci]; + so_page_v3* page = so_page_of(hc, ptr); + if (!page) { + so_v3_record_free_fallback((uint8_t)ci); + tiny_heap_free_class_fast(tiny_heap_ctx_for_thread(), (int)ci, ptr); + return; + } + + *(void**)ptr = page->freelist; + page->freelist = ptr; + if (page->used > 0) { + page->used--; + } + + if (page->used == 0) { + (void)so_unlink_partial(hc, page); + if (hc->partial_count < hc->max_partial_pages) { + so_page_push_partial(hc, page); + if (!hc->current) { + hc->current = page; + } + } else { + if (hc->current == page) { + hc->current = NULL; + } + so_page_retire_slow(ctx, ci, page); + } + } else if (!hc->current) { + hc->current = page; + } +} + +static inline so_page_v3* so_alloc_refill_slow(so_ctx_v3* ctx, uint32_t ci) { + SmallObjectColdIface cold = smallobject_cold_iface_v1(); + void* cold_ctx = (void*)tiny_heap_ctx_for_thread(); + if (!cold.refill_page) return NULL; + so_page_v3* page = cold.refill_page(cold_ctx, ci); + if (!page) return NULL; + + if (page->block_size == 0) { + page->block_size = (uint32_t)tiny_stride_for_class((int)ci); + } + page->class_idx = ci; + + page->used = 0; + page->freelist = so_build_freelist(page); + if (!page->freelist) { + if (cold.retire_page) { + cold.retire_page(cold_ctx, ci, page); + } else { + free(page); + } + return NULL; + } + page->next = NULL; + + so_class_v3* hc = &ctx->cls[ci]; + if (hc->current) { + if (hc->partial_count < hc->max_partial_pages) { + so_page_push_partial(hc, hc->current); + } else { + so_page_retire_slow(ctx, ci, hc->current); + } + } + hc->current = page; + return page; +} + +void* so_alloc(uint32_t class_idx) { + if (__builtin_expect(class_idx >= SMALLOBJECT_NUM_CLASSES, 0)) { + return NULL; + } + so_v3_record_route_hit((uint8_t)class_idx); + so_v3_record_alloc_call((uint8_t)class_idx); + + so_ctx_v3* ctx = so_tls_get(); + void* blk = so_alloc_fast(ctx, class_idx); + if (blk) return blk; + + so_page_v3* page = so_alloc_refill_slow(ctx, class_idx); + if (!page) { + so_v3_record_alloc_fallback((uint8_t)class_idx); + return NULL; + } + so_v3_record_alloc_refill((uint8_t)class_idx); + blk = so_alloc_fast(ctx, class_idx); + if (!blk) { + so_v3_record_alloc_fallback((uint8_t)class_idx); + } + return blk; +} + +void so_free(uint32_t class_idx, void* ptr) { + if (__builtin_expect(class_idx >= SMALLOBJECT_NUM_CLASSES, 0)) { + return; + } + so_v3_record_free_call((uint8_t)class_idx); + so_ctx_v3* ctx = so_tls_get(); + so_free_fast(ctx, class_idx, ptr); +} + +__attribute__((destructor)) +static void so_v3_stats_dump(void) { + if (!so_v3_stats_enabled()) return; + for (int i = 0; i < SMALLOBJECT_NUM_CLASSES; i++) { + so_stats_class_v3* st = &g_so_stats[i]; + uint64_t rh = atomic_load_explicit(&st->route_hits, memory_order_relaxed); + uint64_t ac = atomic_load_explicit(&st->alloc_calls, memory_order_relaxed); + uint64_t ar = atomic_load_explicit(&st->alloc_refill, memory_order_relaxed); + uint64_t afb = atomic_load_explicit(&st->alloc_fallback_v1, memory_order_relaxed); + uint64_t fc = atomic_load_explicit(&st->free_calls, memory_order_relaxed); + uint64_t ffb = atomic_load_explicit(&st->free_fallback_v1, memory_order_relaxed); + if (rh + ac + afb + fc + ffb + ar == 0) continue; + fprintf(stderr, "[SMALL_HEAP_V3_STATS] cls=%d route_hits=%llu alloc_calls=%llu alloc_refill=%llu alloc_fb_v1=%llu free_calls=%llu free_fb_v1=%llu\n", + i, (unsigned long long)rh, (unsigned long long)ac, + (unsigned long long)ar, (unsigned long long)afb, (unsigned long long)fc, (unsigned long long)ffb); + } +} diff --git a/core/superslab_cache.c b/core/superslab_cache.c index 2222c5e2..4a574400 100644 --- a/core/superslab_cache.c +++ b/core/superslab_cache.c @@ -4,6 +4,7 @@ // Date: 2025-11-28 #include "hakmem_tiny_superslab_internal.h" +#include "hakmem_env_cache.h" #include "box/ss_os_acquire_box.h" // ============================================================================ @@ -116,9 +117,12 @@ void* ss_os_acquire(uint8_t size_class, size_t ss_size, uintptr_t ss_mask, int p // This is critical: we MUST touch the pages after munmap() to establish valid mappings // 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(); + int ret = ss_os_madvise_guarded(ptr, ss_size, MADV_POPULATE_WRITE, "ss_cache_populate"); if (ret != 0) { + if (HAK_ENV_SS_MADVISE_STRICT() && errno == EINVAL) { + fprintf(stderr, "[SS_CACHE] madvise(MADV_POPULATE_WRITE) EINVAL (strict). Aborting.\n"); + abort(); + } // Fallback: explicit memset memset(ptr, 0, ss_size); } diff --git a/core/superslab_stats.c b/core/superslab_stats.c index d605e325..061c6411 100644 --- a/core/superslab_stats.c +++ b/core/superslab_stats.c @@ -4,6 +4,8 @@ // Date: 2025-11-28 #include "hakmem_tiny_superslab_internal.h" +#include "box/ss_os_acquire_box.h" +#include #include // ============================================================================ @@ -33,8 +35,11 @@ _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_madvise_fail_enomem = 0; +_Atomic uint64_t g_ss_os_madvise_fail_other = 0; _Atomic uint64_t g_ss_os_huge_alloc_calls = 0; _Atomic uint64_t g_ss_os_huge_fail_calls = 0; +_Atomic bool g_ss_madvise_disabled = false; // Superslab/slab observability (Tiny-only; relaxed updates) _Atomic uint64_t g_ss_live_by_class[8] = {0}; @@ -224,10 +229,14 @@ static void ss_os_stats_dump(void) { 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", + "[SS_OS_STATS] alloc=%llu free=%llu madvise=%llu madvise_enomem=%llu madvise_other=%llu madvise_disabled=%d " + "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_os_madvise_fail_enomem, memory_order_relaxed), + (unsigned long long)atomic_load_explicit(&g_ss_os_madvise_fail_other, memory_order_relaxed), + atomic_load_explicit(&g_ss_madvise_disabled, memory_order_relaxed) ? 1 : 0, (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), diff --git a/docs/analysis/MID_LARGE_CPU_HOTPATH_ANALYSIS.md b/docs/analysis/MID_LARGE_CPU_HOTPATH_ANALYSIS.md index 8a91a7b0..367a89a3 100644 --- a/docs/analysis/MID_LARGE_CPU_HOTPATH_ANALYSIS.md +++ b/docs/analysis/MID_LARGE_CPU_HOTPATH_ANALYSIS.md @@ -73,3 +73,6 @@ - flatten OFF: **23.68M ops/s**(参考値・前フェーズと同じ)。 - flatten ON : **26.70M ops/s**(約 +13% vs OFF)、`alloc_tls_hit=499,871 alloc_fb=229 free_tls_hit=489,147 free_fb=10,952 page_null=3,476 not_mine=7,476 other=0`。 - 所感: page_null が大幅減(≈40k→≈3.4k)。not_mine が顕在化したため、次は owner 判定/自スレ判定の精度を軽く見直す余地がある。デフォルトは引き続き flatten OFF(安全側)で、bench/実験時のみ ON。 + +### 安全側の運用メモ +- C7_SAFE / C7_ULTRA_BENCH プロファイルと pool v1 flatten の組み合わせではクラッシュ報告があるため、`HAKMEM_TINY_HEAP_PROFILE` がこれらのときは flatten を強制 OFF(ENV を立てても無効化)としている。LEGACY や研究用途でのみ flatten を opt-in する方針。 diff --git a/docs/analysis/PERF_ANALYSIS_MID_LARGE_PHASE53.md b/docs/analysis/PERF_ANALYSIS_MID_LARGE_PHASE53.md index dd292ecd..6122d426 100644 --- a/docs/analysis/PERF_ANALYSIS_MID_LARGE_PHASE53.md +++ b/docs/analysis/PERF_ANALYSIS_MID_LARGE_PHASE53.md @@ -27,3 +27,9 @@ - 次の選択肢: - mid/smallmid パス(Tiny 以外)のホットパスを狙う箱 - もしくは Tiny 側での残り課題と比較して優先度を決める材料に。 + +## 2025-12-XX 追記: madvise(ENOMEM) を握りつぶすガードを追加 +- Superslab OS Box に `ss_os_madvise_guarded()` を導入し、madvise(DONTNEED/POPULATE) が ENOMEM(vm.max_map_count)を返したら + `g_ss_madvise_disabled` を立てて以降の madvise をスキップ。EINVAL だけは STRICT=1 で Fail-Fast のまま。 +- `[SS_OS_STATS]` に `madvise_enomem/madvise_other/madvise_disabled` を追加(`HAKMEM_SS_OS_STATS=1`)。 +- 目的: ENOMEM 多発で VMA を増やし続けないようにしつつ、アロケータ本体は継続させる。 diff --git a/docs/analysis/PERF_ANALYSIS_TINY_FRONT_FLATTEN_PHASE42.md b/docs/analysis/PERF_ANALYSIS_TINY_FRONT_FLATTEN_PHASE42.md index 1dcd148e..75732ede 100644 --- a/docs/analysis/PERF_ANALYSIS_TINY_FRONT_FLATTEN_PHASE42.md +++ b/docs/analysis/PERF_ANALYSIS_TINY_FRONT_FLATTEN_PHASE42.md @@ -17,6 +17,23 @@ - 結果: `Throughput = 65979164 ops/s` - 備考: FRONT_CLASS alloc counts(cls2:147, cls3:341, cls4:720, cls5:9857)。HEAP_STATS ダンプは出ず。 +# Phase65 後半: 長尺 v2 ON/OFF A/B(ws=400, iters=1M, PROFILE=C7_SAFE) +- 共通 ENV: `HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1 HAKMEM_TINY_HOTHEAP_V2_STATS=1` +## C7-only +- v2 OFF (`HOTHEAP_V2=0`): **38.24M ops/s**, HEAP_STATS[7] fast=550099 slow=1。 +- v2 ON (`HOTHEAP_V2=1`, classes=0x80): **38.68M ops/s**, HEAP_STATS[7] fast=550099 slow=1。v2 stats: refill=1、fail/fallback=0。 +## Mixed 16–1024B +- v2 OFF: **41.78M ops/s**, HEAP_STATS[7] fast=283169 slow=1。 +- v2 ON: **41.94M ops/s**, HEAP_STATS[7] fast=283169 slow=1。v2 stats: refill=1、fail/fallback=0。 +- 所感: 長尺では v2 ON/OFF とも slow≈1 に収束し、性能も ±5% 以内(むしろ微プラス)。短尺で見える refill≈50 はウォームアップ由来と判断。 + +### Phase65/66 サマリ表(1M / ws=400, PROFILE=C7_SAFE) + +| プロファイル | v2 OFF ops/s | v2 ON ops/s | HEAP_STATS[7] slow | 備考 | +|-----------------------|-------------:|------------:|--------------------:|-------------------------| +| C7-only (C7-only ベンチ) | 38.24M | 38.68M | 1 | v2 は微プラス・refill=1 | +| Mixed 16–1024B | 41.78M | 41.94M | 1 | v2 は微プラス・refill=1 | + --- # Phase43 (HEAP_STATS 再有効化の再計測) ※v2 OFF @@ -43,6 +60,16 @@ ### メモ - C7-only では HEAP_STATS/PAGE_STATS が出ることを確認。Mixed/Tiny-only では cls7 以外の HEAP_STATS が落ちておらず、環境/計測対象の条件を再確認する余地あり。 +# Phase63: C6 v2 A/B(C6-heavy / Mixed, C7 SAFE) +- 共通 ENV: `HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2_STATS=1 HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1` +## C6-heavy (min=257/max=768, ws=400, iters=1M) +- v2 OFF (`HOTHEAP_V2=0`): **42.15M ops/s**, HEAP_STATS[7] fast=283169 slow=1。 +- v2 ON (`HOTHEAP_V2=1`, classes=0x40): **29.69M ops/s**。HEAP_STATS[6] fast=266914 slow=16。HOTHEAP_V2_STATS cls6 route_hits=0 / free_fb_v1=266930(実質 v1 経路)、fail=0。 +## Mixed 16–1024B (ws=400, iters=1M) +- C7 v2 only (`classes=0x80`): **45.07M ops/s**。HEAP_STATS[7] fast=283170 slow=2276、v2 alloc_lease/refill=2276。 +- C6+C7 v2 (`classes=0xC0`): **35.65M ops/s**。HEAP_STATS[6] fast=137306 slow=1 / cls7 slow=2276。HOTHEAP_V2_STATS cls6 route_hits=0、cls7 refills=2276。 +- 所感: C6 v2 は初回 A/B で大幅マイナスかつ v2 ルートが当たっていない。C7 v2 もこのプロファイルでは refill が 2,276 件と多く throughput 低下。v2 は研究箱のまま、デフォルトは v2 OFF。 + # Phase43'': 計測専用で C6/C7 を TinyHeap に載せた場合(v2 OFF) ## Mixed 16–1024B (ws=256, iters=20000, class mask=0xC0, C6+C7) @@ -203,3 +230,45 @@ - そのため HugePage ON/OFF で throughput / cycles / page-fault はほぼ同一となり、初期実装の段階では効果は「ゼロに近い」。 - 2MB Superslab 専用クラスを設計するか、サイズ条件を緩めて HugePage を実際に使うかを決めないと、Mode A の効果は評価できない。 - 一方で pf はすでに ≈6.6k と小さく、たとえ HugePage でさらに削っても全体への寄与は限定的なため、現時点では **CPU ホットパス側の最適化を優先し、HugePage 実験は research-only の位置付けに留める** のが妥当と見える。 + +## Phase59: C7 HotHeap v2 Mixed A/B(C7-only route ON/OFF) +- プロファイル: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_LARSON_FIX=1 HAKMEM_TINY_HEAP_STATS=1 HAKMEM_TINY_HEAP_STATS_DUMP=1 HAKMEM_TINY_HOTHEAP_CLASSES=0x80` +- v2 OFF (`HAKMEM_TINY_HOTHEAP_V2=0`): + - Throughput: **45,113,732 ops/s** + - HEAP_STATS[7]: fast=5691 slow=1 + - C7_PAGE_STATS: prepare_calls=1(current を握り続けている) +- v2 ON (`HAKMEM_TINY_HOTHEAP_V2=1`, `HAKMEM_TINY_HOTHEAP_V2_STATS=1`): + - Throughput: **46,209,226 ops/s**(約 +2.4%) + - HEAP_STATS[7]: fast=5692 slow=45 + - HOTHEAP_V2_C7_STATS: route_hits=5692, alloc_fast=5692, alloc_lease=45 (=refill=45), cold_refill_fail=0, free_fast=4376, page_retired=4, fallback_v1=0 +- 所感: + - Mixed でも v2 ON で微増(+2〜3%)だが、slow_prepare/alloc_lease が 45 と多く、current/partial を使い切る前に refill が走っている可能性がある。 + - cold_refill_fail/fallback は 0 のため、境界は安定。次は v2 内で空ページを握る/refill の発生頻度を下げる調整が必要。 + +## Phase60: C7 HotHeap v2 空ページ保持ポリシー(partial 温存)初期ベンチ +- 変更点: `max_partial_pages` を C7 用に 2 へ設定し、free で `used==0` のページは retire せず partial へ温存。partial が上限を超えたときだけ retire。partial push/pop/peak/retire_v2 を v2 stats に追加。 +- C7-only (ws=64, iters=20k, PROFILE=C7_SAFE): + - v2 OFF: **41.94M ops/s**, HEAP_STATS[7] fast=11015 slow=1。 + - v2 ON: **50.43M ops/s**, HEAP_STATS[7] fast=11016 slow=48。HOTHEAP_V2_C7_STATS: alloc_lease=48 (=refill), partial_push/pop/peak=0, retire_v2=0。 + - メモ: workload が常時多数の live ブロックを持つため page が空にならず、partial_push/retire はまだ発火していない。slow≈refill となっており、ページ容量/同時 live 数が原因と推定。 +- Mixed 16–1024B (ws=256, iters=20k, PROFILE=C7_SAFE): + - v2 OFF: **42.82M ops/s**, HEAP_STATS[7] fast=5691 slow=1。 + - v2 ON: **47.71M ops/s**, HEAP_STATS[7] fast=5692 slow=42。HOTHEAP_V2_C7_STATS: alloc_lease=42 (=refill), partial_push/pop/peak=0, retire_v2=0。 +- 所感: + - slow_prepare は「refill したページ数」と一致しており、空ページ保持ポリシーはまだ効く場面に遭遇していない(ページが一度も空にならないワークロード)。 + - v2 は C7-only/Mixed ともプラスを維持(+20% 近い向上)だが、refill=slow が 40〜50 回発生。partial を効かせるには「空になるページ」が出るプロファイルで検証するか、ページ容量/lease 戦略の見直しが必要。 +- 備考(容量と live 数): + - C7 の page capacity は 32 blocks(約 32KiB)で、ws=64/256/400 の Mixed では常時多数の live ブロックが存在するため、空 page がほぼ発生しない。partial/retire は保険として温存しつつ、現行ベンチでは出番がない前提で扱う。 + +## Phase61: C7 v2 長尺 Mixed (ws=400, iters=1M) 安定性確認 +- プロファイル: Mixed 16–1024B, ws=400, iters=1M, PROFILE=C7_SAFE, C7_HOT=1, v2 classes=0x80, LARSON_FIX=1, STATS_BOX=on, STATS_BATCH=0。 +- v2 OFF: **41.58M ops/s**。HEAP_STATS[7] fast=283169 slow=1。cold_refill_fail=0, fallback_v1=0。 +- v2 ON: **42.41M ops/s**(約 +2%)。HEAP_STATS[7] fast=283169 slow=1。v2 stats dumpはなし(fail/fallbackなし)。 +- 所感: + - 長尺でも v2 ON で安定稼働し、+2% 程度の改善を維持。slow_prepare は 1 に張り付き、短尺で見えていた refill 多発は長尺では再現せず。 + - 引き続き v2 は C7-only の研究箱(デフォルト OFF)のまま、Mixed でも回帰しないことを確認。 + +### Phase61’(短尺再確認): Mixed 16–1024B (ws=256, iters=20k) +- v2 OFF: **43.27M ops/s**, HEAP_STATS[7] fast=5691 slow=1。 +- v2 ON (C7-only v2, classes=0x80, v2 stats ON): **44.57M ops/s**(約 +3%)、HEAP_STATS[7] fast=5691 slow=1、cold_refill_fail/fallback=0。C7_PAGE_STATS: prepare_calls=1 で安定。 +- 所感: 20k/256 でも slow_prepare は 1 に収まり、v2 ON で軽いプラスを維持。長尺と同様に安定。partial/retire ポリシーは依然発火せず(ページが空にならない負荷のため)、本線 Mixed では v2 を研究箱として継続しつつ、さらなる薄型化/slow圧縮を検討。 diff --git a/docs/analysis/POOL_V2_BOX_DESIGN.md b/docs/analysis/POOL_V2_BOX_DESIGN.md new file mode 100644 index 00000000..30e50444 --- /dev/null +++ b/docs/analysis/POOL_V2_BOX_DESIGN.md @@ -0,0 +1,89 @@ +# POOL_V2_BOX_DESIGN (Phase68) + +目的 +---- +- mid/smallmid (257–768B を主軸) のホットパスで pool allocator が支配的なため、TinyHotHeap v2 と同様の「Hot/Cold 分離」を pool にも導入する設計を整理する。 +- まずは箱構造と境界だけを決め、実装は後続フェーズでゲート付き A/B できるようにする(デフォルトは現行 v1 のまま)。 + +箱と境界(方針) +----------------- +- Hot Box: PoolHotBox v2(per-thread / per-class pool) + - 構造: pool_page に `freelist / used / capacity / base`、pool_class に `current_page / partial_pages / full_pages / stride`。 + - 責務: alloc/free では page 内 freelist pop/push だけを扱う。Stats/学習/OS には触れない。 +- Cold Box: Superslab/Segment/Tier/Guard/Remote + pool 専用統計 + - 責務: pool_page の供給・返却、Tier/Guard 判定、OS との境界。 +- 境界は 2 箇所に集約 + - alloc 側: `pool_refill_page(class_idx)` … Hot が page を 1 枚借りる + - free 側: `pool_page_retire(class_idx, page)` … Hot が empty page を返す + +現行 v1/v2 の棚卸し(箱視点) +----------------------------- +- v1: 安定線。pool TLS freelist + shared pool を直接操作。perf 上は hak_pool_try_alloc/free, memset が支配的。 +- v2: Phase55–57 で導入した軽量化パス(TLS fast path 直線化など)だが perf 未達。`HAKMEM_POOL_V2_ENABLED` で研究箱としてゲート。 +- 共通課題: freelist pop/push と mid_desc_lookup/owner 判定がホットパスに残りすぎている。Cold/OS と混在している。 + +設計メモ(次フェーズ実装タスクの種) +----------------------------------- +- Hot 型 + - `pool_page_v2`, `pool_class_v2`, `pool_ctx_v2` を TinyHotHeap v2 のミニ版として定義。 + - TLS で per-thread ctx を保持し、stride/block_size を class 初期化時にセット。 +- Hot パス + - `pool_v2_alloc(class_idx)` / `pool_v2_free(class_idx, ptr)` は current→partial→refill、freelist pop/push だけで完結させる。 + - Debug/統計/slow パスは unlikely 側に寄せる。 +- Cold IF + - `PoolColdIface` に `refill_page` / `retire_page` を定義。初期実装は既存 pool/superslab の API を薄くラップして流用。 +- Gate/ENV + - `HAKMEM_POOL_V2_ENABLED` / `HAKMEM_POOL_V2_CLASSES`(mask)で v1/v2 を切替。C6-heavy プロファイルから段階的に A/B する前提。 + +狙いと目標値 +------------ +- スループット目標: mid/smallmid 単体で +5〜10%(28–29M → 30–32M ops/s)をまず狙う。 +- 命令数削減: hak_pool_try_alloc/free, memset, mid_desc_lookup の self% を数ポイント下げる。 +- pf/sys は既に小さいため、CPU パスの直線化とキャッシュで効果を出す。 + +ガード +------ +- 本ドキュメントは設計のみ。実装はすべて ENV ゲート付きで、デフォルトは v1 挙動を維持すること。 +- Superslab/OS/Stats/Learning のロジックを変えない(Cold IF で接続するだけ)。*** + +Phase69 メモ(初回 A/B と Cold IF 状況) +---------------------------------------- +- HotBox v2 の初実装を通電(freelist/current/partial を v2 内で管理)。Cold IF はまだ posix_memalign/free を使うダミーのまま。 +- A/B(Release, min=2048/max=8192, ws=400, iters=100k, C7_SAFE, Tiny v2 OFF): + - v2 OFF: 28.70M ops/s + - v2 ON (classes=0x7F, Cold IF=posix_memalign): 2.15M ops/s、`alloc_refill=1630`, `free_fb_v1` 多数 → page_of 失敗で v1 free に大量フォールバック。 +- 次の一手: + - Cold IF を v1 pool / Superslab 経路に差し替える(posix_memalign ダミーを撤去)。 + - free_fb_v1 の主因を解消する(page_of か class 判定の欠落)。必要ならヘッダに page token を持たせるなど Hot 側のトラッキングを強化。 + - A/B は C6-heavy プロファイル(pool が効くサイズ帯)で実施し、性能と stats を CURRENT_TASK.md に積む。 + +Phase70 メモ(Cold IF 差し替え・Fail-Fast 強化) +---------------------------------------------- +- Cold IF を v1 ベースの mmap + mid_desc_register に変更し、retire は munmap に統一。HotBox v2 からは geometry/token だけを受け取り、pop/push は v2 freelist だけで完結させる方針に近づけた。 +- page_of は class/base/offset を厳密チェックし、ミスマッチ時は Fail-Fast(v2 内では v1 fallback しない)。free_fb_v1 は front 側の route ミスマッチ検知専用に狭める前提。 +- 次ステップ: C6-heavy で v2 OFF/ON を再A/Bし、`alloc_refill_fail==0`, `free_fb_v1≈0` を確認。研究箱のまま、標準は v1 を維持。 + +Phase71 メモ(v2 ON で abort, A/B 取れず) +---------------------------------------- +- C6-heavy (min=2048/max=8192, ws=400, iters=1M, PROFILE=C7_SAFE, Tiny v2 OFF, classes=0x7F/0x1, POOL_V2_STATS=1) で v2 ON が起動直後に `hak_pool_free_v2_impl → pool_hotbox_v2_page_of` の Fail-Fast で SIGABRT。短尺 10k でも同様で stats は出ず。 +- v2 OFF では **30.57M ops/s** を確認。v2 ON はクラッシュのため比較値なし。 +- 状態: v2 alloc→v1 free 混線 or page_of 範囲判定が未整合。free_fb_v1 は front 側のみでカウントする方針のため、ページ追跡を直さないと A/B が進まない。 +- 対応方針: route/gate と page_of の整合を再確認し、v2 alloc が NULL を返さないようにするか、front で v1 fallback に落とす。安定するまでは v2 を研究箱(デフォルト OFF)のまま維持。 + +Phase72 メモ(page_of O(1) 化と長尺挙動 / 凍結判断) +---------------------------------------------------- +- 実装の進展: + - HotBox v2 ページ先頭に self ポインタを埋め、`POOL_PAGE_SIZE` アラインのマスク+ヘッダ読み出しで `pool_hotbox_v2_page_of()` を O(1) 化。 + - capacity はヘッダ分を差し引いた領域から算出し、freelist はヘッダ直後から carve。retire/初期化時にヘッダを必ずセット/クリア。 + - Cold IF の mmap を 64KiB アライン取得に揃え、2ページ分の OS 統計だけ積む構造に整理(Superslab 相当の geometry を模倣)。 +- 短〜中尺の挙動(C6-heavy, v2 ON, POOL_V2_STATS=1): + - 10k〜100k/ws=400 では page_of_fail_x=0 / free_fb_v1=0 / alloc_refill 十数〜数十回で完走。構造バグ(page_of/route 混線)は解消。 + - Throughput は v1 基準より低く、短尺で ~20–25M ops/s 程度(v1≈30M)。オーダーは正しいがオーバーヘッドはまだ大きい。 +- 長尺 1M/ws=400 の挙動: + - v2 OFF: ≈27–30M ops/s で安定完走。 + - v2 ON: 120s タイムアウトで完走せず(ハング/極端な遅さ)。page_of_fail は短尺では 0 だが、1M 長尺では perf 観点で許容できないレベルの回帰。 +- 現フェーズの結論: + - PoolHotBox v2 の箱構造(page_of O(1)、Cold IF=v1 pool/Superslab)と Fail-Fast/統計まわりは通電し、研究用としては十分な観測性を得られた。 + - 一方で C6-heavy 長尺での極端な遅さを解消するには、page geometry / current/partial ポリシーや mid/pool 全体の設計をさらに大きく見直す必要があり、現フェーズ(Tiny v2 + mid/pool v2)での対応範囲を超える。 + - そのため、PoolHotBox v2 は **現行フェーズでは研究箱として凍結** し、運用デフォルトは引き続き `HAKMEM_POOL_V2_ENABLED=0`(v1 pool)のままとする。 + - mid/smallmid のさらなる最適化(mimalloc 7〜8割到達)を狙う場合は、将来の v3 テーマとして pool/mid の Hot/Cold 再設計を検討する(本ドキュメントはその出発点とする)。*** diff --git a/docs/analysis/SMALLOBJECT_HOTBOX_V3_DESIGN.md b/docs/analysis/SMALLOBJECT_HOTBOX_V3_DESIGN.md new file mode 100644 index 00000000..2b70c28c --- /dev/null +++ b/docs/analysis/SMALLOBJECT_HOTBOX_V3_DESIGN.md @@ -0,0 +1,268 @@ +SmallObject HotBox v3 Design (Tiny + mid/smallmid 統合案) +======================================================== + +目的と背景 +---------- +- 現状の性能スナップショット: + - Mixed 16–1024B (1t, ws=400, iters=1M, PROFILE=C7_SAFE, Tiny v2 OFF): + - HAKMEM ≈ 40–50M ops/s + - mimalloc ≈ 110–120M ops/s + - system ≈ 90M ops/s + - mid/smallmid (bench_mid_large_mt, 1t, ws=400, iters=1M): + - HAKMEM ≈ 28–29M ops/s + - mimalloc ≈ 54M ops/s + - system ≈ 15M ops/s +- これまでの Tiny/Pool v2 の試行から見えたこと: + - C7 v2 (TinyHotHeap v2) は C7-only / Mixed 長尺では v1 C7_SAFE と同等以上まで到達したが、C6/C5 への水平展開や pool v2 は現フェーズでは perf 未達で凍結。 + - pool v2 は page_of O(1) 化+Cold IF=v1 pool/Superslab まで実装したが、C6-heavy 長尺で極端な遅さが発生し、現行設計のまま押し上げるのは難しい。 +- 結論: + - mimalloc に 7〜8割で迫るには、「Tiny (16〜1KiB) と mid/smallmid の両方」を一体の SmallObject HotBox として設計し直す必要がある。 + - Superslab/Segment/Tier/Guard/Remote といった Cold/Safety 層は Box として残しつつ、SmallObject 側の Hot Box を 1 枚に集約する方向で v3 を設計する。 + +進捗サマリ (Phase A/B 通電) +--------------------------- +- ENV: `HAKMEM_SMALL_HEAP_V3_ENABLED` / `HAKMEM_SMALL_HEAP_V3_CLASSES` を追加(デフォルトは C7-only ON: ENABLED=1, CLASSES=0x80 相当)。 +- 型/IF: `core/box/smallobject_hotbox_v3_box.h` に so_page/class/ctx と stats を定義。TLS 初期化でクラス別 stride/max_partial をセット。 +- Cold IF: `smallobject_cold_iface_v1.h` で C7 専用の v1 Tiny ラッパを実装。refill で tiny_heap_prepare_page(7) を借り、retire で tiny_heap_page_becomes_empty に返す。 +- Hot: `core/smallobject_hotbox_v3.c` で so_alloc/so_free を実装(current/partial freelist を v3 で管理、refill 失敗は v1 fallback)。ページ内 freelist carve は v3 側で実施。 +- Route: `tiny_route_env_box.h` に `TINY_ROUTE_SMALL_HEAP_V3` を追加。クラスビットが立っているときだけ route snapshot で v3 に振り分け。 +- Front: malloc/free で v3 route を試し、失敗時は v2/v1/legacy に落とす直線パス。デフォルトは OFF なので挙動は従来通り。 + +設計ゴール (SmallObjectHotBox v3) +--------------------------------- +- 対象サイズ帯: + - 16〜約 2KiB 程度の「SmallObject」領域(既存 Tiny C0〜C7 + mid/smallmid の一部)。 + - それより大きいサイズは現行 mid/large ルート(pool v1 / direct mmap)を継続使用。 +- Box 構造: + - Hot Box: `SmallObjectHotBox` (per-thread) + - 責務: size→class→page→block のみを扱う。ページ内 freelist pop/push、current/partial 管理。 + - Tiny v2 / pool v2 で学んだ「page_of O(1)、current/partial/retire ポリシー」を統合。 + - Cold Box: Superslab/Segment/Tier/Guard/Remote + - 責務: Superslab/Segment の割当て・解放、Tier/HOT/DRAINING/FREE 管理、Remote Queue、Guard/Budget。 + - Hot からは refill/retire の 2 箇所でのみ触れる。 + - Policy Box: `SmallObjectPolicySnapshot` + - 責務: クラスごとの有効/無効、max_partial_pages、warm_cap などのポリシーを `_Atomic` スナップショットで保持。 + - Hot は snapshot を読むだけ。Learner/ENV は snapshot の更新のみ。 + - Stats Box / Learning Box: + - 責務: page/event 単位の delta を受け取り、Cold 側で集計・観測・学習を行う。 + - Hot は「page refill/retire 時」「alloc/free のカウンタ更新」以外では触らない。 +- 境界: + - Hot → Cold は 2 箇所に集約: + - `so_refill_page(cold_ctx, class_idx)` … SmallObjectHotBox が page を 1 枚借りる。 + - `so_page_retire(cold_ctx, class_idx, page)` … empty page を Cold 側に返却。 + - 現行 TinyColdIface / PoolColdIface の経験を活かし、SmallObject 用 Cold IF を 1 枚設計する。 + +データ構造案 +------------ + +### Page メタ (`so_page_t`) + +```c +typedef struct so_page_t { + void* freelist; // ページ内 block 単位の LIFO freelist + uint32_t used; // 使用中 block 数 + uint32_t capacity; // ページ内 block 総数 + uint16_t class_idx; // SmallObject クラス ID + uint16_t flags; // HOT/PARTIAL/FULL などの軽量フラグ + uint32_t block_size; // 1 block のバイト数 + void* base; // ページ base アドレス + void* slab_ref; // Superslab/Segment 側 token (Cold Box 用) + struct so_page_t* next; +} so_page_t; +``` + +ポイント: +- Tiny v2 / pool v2 のページ構造を統一し、「SmallObject 全体で同じ page 型」を使う。 +- page_of は `POOL_PAGE_SIZE` や Superslab サイズに合わせた mask + header で O(1) を前提とする(pool v2 で得た知見)。 + +### クラス状態 (`so_class_t`) + +```c +typedef struct so_class_t { + so_page_t* current; // ホットページ + so_page_t* partial; // 空きありページのリスト + uint16_t max_partial; // partial に保持する上限枚数 + uint16_t partial_count; // 現在の partial 枚数 + uint32_t block_size; // クラスの block サイズ +} so_class_t; +``` + +- Tiny C7 Safe / TinyHotHeap v2 / pool v2 の current/partial/retire ポリシーを統合。 +- full リストは v3 初期段階では不要(必要になったら追加)。 + +### TLS コンテキスト (`so_ctx_t`) + +```c +typedef struct so_ctx_t { + so_class_t cls[SMALLOBJECT_NUM_CLASSES]; +} so_ctx_t; +``` + +- TLS (`__thread`) で per-thread の SmallObject コンテキストを保持。 +- 初期化時にクラスごとの `block_size` / `max_partial` / ポリシー値をセットする。 + +Cold IF (SmallObjectColdIface) のイメージ +---------------------------------------- + +```c +typedef struct SmallObjectColdIface { + so_page_t* (*refill_page)(void* cold_ctx, uint32_t class_idx); + void (*retire_page)(void* cold_ctx, uint32_t class_idx, so_page_t* page); +} SmallObjectColdIface; +``` + +- `refill_page`: + - Cold Box が Superslab/Segment から SmallObject 用ページを 1 枚切り出し、 + - `base` / `block_size` / `capacity` / `slab_ref` を設定した `so_page_t` を返す。 + - v3 では `so_page_t` 自体を Cold 側で確保する案と、Hot 側 node を再利用する案のどちらかを選べるようにしておく。 +- `retire_page`: + - `used==0` のページを Cold Box に返却し、Tier/Guard/Remote の扱いは Cold 側に委譲。 +- 現行の TinyColdIface / pool Cold IF をラップする形で、SmallObject 用 Cold IF を段階的に導入する。 + +Hot パス設計(alloc/free) +------------------------- + +### alloc (Hot パス) + +```c +void* so_alloc_fast(so_ctx_t* ctx, uint32_t ci) { + so_class_t* hc = &ctx->cls[ci]; + so_page_t* p = hc->current; + + if (likely(p && p->freelist && p->used < p->capacity)) { + void* blk = p->freelist; + p->freelist = *(void**)blk; + p->used++; + return blk; + } + + if (hc->partial) { + p = hc->partial; + hc->partial = p->next; + p->next = NULL; + hc->current = p; + if (p->freelist && p->used < p->capacity) { + void* blk = p->freelist; + p->freelist = *(void**)blk; + p->used++; + return blk; + } + } + + return NULL; // slow_refill へ +} +``` + +- Slow パス (`so_alloc_refill_slow`) では: + - `SmallObjectColdIface.refill_page()` で Cold Box からページを 1 枚借りる。 + - `so_page_t` に geometry を設定し、ページ内 freelist を Hot Box 側で carve。 + - `hc->current` にセットしてから `so_alloc_fast` で pop。 + +### free (Hot パス) + +```c +void so_free_fast(so_ctx_t* ctx, uint32_t ci, void* ptr) { + so_class_t* hc = &ctx->cls[ci]; + so_page_t* p = so_page_of(ptr); // O(1) page_of + + *(void**)ptr = p->freelist; + p->freelist = ptr; + p->used--; + + if (p->used == 0) { + if (hc->partial_count < hc->max_partial) { + p->next = hc->partial; + hc->partial = p; + hc->partial_count++; + } else { + so_page_retire_slow(ctx, ci, p); // Cold IF 経由 + } + if (hc->current == p) hc->current = NULL; + } else { + if (!hc->current) hc->current = p; + } +} +``` + +- Tiny v2 / pool v2 で使った「空ページ温存 or retire」のポリシーを、クラス別 `max_partial` で制御する。 +- page_of は pool v2 と同様に O(1) で実装し、Fail-Fast ではなく統計+前段 fallback で診断できるようにする。 + +Front/Gate/Route の統合方針 +-------------------------- + +- size→class→route の LUT は既存 TinyRoute Box を流用しつつ、「SmallObjectHotBox v3 対応 route」を追加する。 +- 例: + - `ROUTE_SMALL_HEAP_V3` … SmallObjectHotBox v3。 + - `ROUTE_TINY_V1` / `ROUTE_POOL_V1` / `ROUTE_LEGACY` … 現行のまま。 +- PolicySnapshot で: + +```c +enum SmallObjectHeapVersion { + SO_HEAP_V1 = 0, + SO_HEAP_V3 = 1, +}; + +typedef struct SmallObjectPolicySnapshot { + uint8_t heap_version[SMALLOBJECT_NUM_CLASSES]; // V1/V3 + uint8_t enabled_mask[SMALLOBJECT_NUM_CLASSES]; // クラスごとの ON/OFF + uint16_t max_partial[SMALLOBJECT_NUM_CLASSES]; +} SmallObjectPolicySnapshot; +``` + +- Front からは: + - `class_idx = size_to_smallobject_class(size);` + - `route = g_smallobject_route[class_idx];` + - `switch (route) { SMALL_HEAP_V3 / TINY_V1 / POOL_V1 / LEGACY }` + という 1 LUT + 1 switch で決定。C7 v2 / Tiny v1 / pool v1 など既存経路もここで選べるようにする。 + +段階的 rollout 戦略 +------------------- + +1. Phase A: 設計・骨格導入(bench/実験専用) + - `SmallObjectHotBox` 型・SmallObjectColdIface・PolicySnapshot を導入。 + - まずは Tiny C7-only を SmallObjectHotBox v3 経由に差し替え(現行 C7 v2 を v3 枠に移すイメージ)。 + - ENV (`HAKMEM_SMALL_HEAP_V3_ENABLED`, `HAKMEM_SMALL_HEAP_V3_CLASSES`) で C7-only を v3 に切り替え可能に。 + +2. Phase B: C6/C5 など Tiny クラスを v3 に拡張 + - C6-heavy / C5-heavy ベンチで C6/C5 を v3 に載せ、v1 vs v3 の perf A/B を取得。 + - Mixed 16–1024B で C7-only v3 vs C6+C7 v3 を比較。 + - ここまでは pool v1 をそのまま使い、SmallObjectHotBox v3 側で Tiny 相当をまとめて扱う。 + +3. Phase C: mid/smallmid pool を SmallObject に寄せる + - mid/smallmid サイズクラスを SmallObjectHotBox v3 のクラスとして増やし、pool v1 経路の一部を v3 に移管する。 + - Cold IF は Superslab/Segment 共通のまま、SmallObject クラスの範囲だけ v3 で扱う。 + +4. Phase D: v1/v2/v3 の役割を整理 + - v1 TinyHeap / pool v1 は完全な fallback/研究箱とし、標準は SmallObjectHotBox v3 をメインにする。 + - v2 系(TinyHotHeap v2 / pool v2)は v3 開発の "失敗を記録した箱" として docs/analysis に残す。 + +非ゴール(この設計フェーズでやらないこと) +----------------------------------------- +- Superslab/Segment/Tier/Guard/Remote のフル再設計(Segment サイズや Tier ポリシー変更など)は v3 後半〜v4 テーマとする。 +- first-touch/pf/HugePage/NUMA 最適化は SmallObjectHotBox v3 の上に乗る別箱として扱い、この設計では触らない。 +- 学習層 (ACE/ELO) の仕様変更は行わず、PolicySnapshot の更新だけを学習側が持ち、Hot パスは snapshot を読むだけにする。 + +まとめ +------ +- SmallObjectHotBox v3 は、TinyHotHeap v2 / pool v2 で得た知見を統合し、「SmallObject 全体を 1 枚の Hot Box」として扱う設計。 +- Hot Box と Cold Box の境界を 2 箇所(refill/retire)に絞り、Policy/Stats/Learning を別箱に押し出すことで、Box Theory に沿った形で mimalloc に近い構造を目指す。 +- 実装は `docs/design/SMALLOBJECT_HOTBOX_V3_IMPLEMENTATION_GUIDE.md` に従って段階的に行い、常に v1/v2 への rollback path を維持する。 + +## Phase65 簡易ベンチメモ(C7-only v3, Tiny/Pool v2 OFF) +- 短尺 20k/ws=64: v3 OFF 41.26M ops/s → v3 ON 57.55M ops/s(alloc_refill=49, fallback_v1=0, page_of_fail=0)。 +- 長尺 1M/ws=400: v3 OFF 38.26M ops/s → v3 ON 50.24M ops/s(alloc_refill=5077, fallback_v1=0)。 +- Mixed 16–1024B 1M/ws=400: v3 OFF 41.56M ops/s → v3 ON 49.40M ops/s(alloc_refill=2446, fallback_v1=0)。 +- デフォルトは C7-only ON (`HAKMEM_SMALL_HEAP_V3_ENABLED` 未指定 / `HAKMEM_SMALL_HEAP_V3_CLASSES` 未指定で class7 のみ v3)。明示的に `ENABLED=0` または CLASSES から bit7 を外すことで v1 経路に戻せる。 + +## Phase65-HEAP_STATS 追加観測(C7-only v3 A/B, Tiny/Pool v2 OFF) +- 短尺 20k/ws=64: + - v3 OFF: 40.91M ops/s, `HEAP_STATS[7] fast=11015 slow=1`。 + - v3 ON: 56.43M ops/s, v3 stats `alloc_refill=49 fb_v1=0 page_of_fail=0`(短尺ウォームアップ由来の refill)。HEAP_STATS は Tiny v1 経路のみ出力。 +- 長尺 1M/ws=400: + - v3 OFF: 38.29M ops/s, `HEAP_STATS[7] fast=550099 slow=1`。 + - v3 ON: 50.25M ops/s, v3 stats `alloc_refill=5077 fb_v1=0 page_of_fail=0`。 +- Mixed 16–1024B 1M/ws=400(参考): + - v3 OFF: 42.35M ops/s (`HEAP_STATS[7] fast=283169 slow=1`)。 + - v3 ON: 49.60M ops/s (`alloc_refill=2446 fb_v1=0 page_of_fail=0`)。 +- まとめ: HEAP_STATS で slow≈1 を維持したまま v3 ON は C7-only/Mixed とも大幅プラス。デフォルトでは C7-only v3 を ON(ENABLED=1, CLASSES デフォルト=0x80)としつつ、混乱を避けるため `HAKMEM_SMALL_HEAP_V3_ENABLED=0` / クラスマスクでいつでも v1 に戻せるようにしている。*** +*** diff --git a/docs/analysis/TINY_CPU_HOTPATH_USERLAND_ANALYSIS.md b/docs/analysis/TINY_CPU_HOTPATH_USERLAND_ANALYSIS.md index db3be595..8f93620d 100644 --- a/docs/analysis/TINY_CPU_HOTPATH_USERLAND_ANALYSIS.md +++ b/docs/analysis/TINY_CPU_HOTPATH_USERLAND_ANALYSIS.md @@ -32,3 +32,53 @@ - pop/empty 内の分岐を整理し、C7 SAFE の理想パス(current_page固定)に寄せる。 - header write / memset を最小化する実験スイッチを検討。 +## C7 v3 ON, Mixed 16–1024B (ws=400, iters=1,000,000, userland cycles) + +環境: `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_POOL_V2_ENABLED=0 HAKMEM_SMALL_HEAP_V3_ENABLED=1 HAKMEM_SMALL_HEAP_V3_CLASSES=0x80` + +- Throughput: **48.96M ops/s**(前回 v3 ON と同レンジ) +- perf record `cycles:u` (171 samples, release build without symbols): + - 上位は `free` / `malloc` / `main` の無名フレームに潰れてしまい、非C7 Tiny front の細かい関数名が出ず。 + - シンボル再取得には DEBUG/記号付きビルドで perf し直す必要あり。 +- 所感: C7 v3 以外のホットパス特定には、size→class→route 前段や unified cache hit パスを再シンボル化して見る必要がある。現状のバイナリでは細部が見えない。 + +## Mixed 16–1024B (ws=400, iters=1,000,000, userland cycles, C7 v3 ON, DEBUGビルド) + +環境: `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_POOL_V2_ENABLED=0 HAKMEM_SMALL_HEAP_V3_ENABLED=1 HAKMEM_SMALL_HEAP_V3_CLASSES=0x80` +ビルド: `make clean && CFLAGS='-O2 -g …' USE_LTO=0 OPT_LEVEL=2 NATIVE=0 make bench_random_mixed_hakmem` + +- Throughput: **46.1–46.3M ops/s**(リリース時の ~49M よりやや低いが符号付きビルドで許容) +- perf record `cycles:u -F5000 --call-graph dwarf` (200 samples) の self% 上位(C7 v3 以外を抜粋): + - free 23.5%, malloc 18.8%, main 13.7%(ベンチハーネス由来) + - **tiny_region_id_write_header 6.7%** + - **ss_map_lookup 3.6%** + - unified_cache_enabled 2.8% + - tiny_guard_is_enabled 2.2% + - classify_ptr 1.4%(size→class 判定系) + - mid_desc_lookup 1.3% + - hak_free_at 0.9%, hak_pool_mid_lookup 0.7% + - so_alloc/so_free 合計 ~7%(C7 v3 本体) +- 所感(非C7 Tiny front 目線): + - header 書き込み(tiny_region_id_write_header)が依然目立つ。 + - Superslab 判定前の `ss_map_lookup` が 3–4% 程度残っている。 + - route/guard 判定(unified_cache_enabled / tiny_guard_is_enabled / classify_ptr)が合わせて ~6% 程度。 + - 次は「size→class→route 前段+header」をフラット化するターゲットが有力。 + +### Front v3 snapshot 導入メモ +- `TinyFrontV3Snapshot` を追加し、`unified_cache_on / tiny_guard_on / header_mode` を 1 回だけキャッシュする経路を front v3 ON 時に通すようにした(デフォルト OFF)。 +- Mixed 16–1024B (ws=400, iters=1M, C7 v3 ON, Tiny/Pool v2 OFF) で挙動変化なし(slow=1 維持)。ホットスポットは依然 front 前段 (`tiny_region_id_write_header`, `ss_map_lookup`, guard/route 判定) が中心。 + +### Front v3 size→class LUT メモ(Phase2-A 実装済み、A/B これから) +- ENV `HAKMEM_TINY_FRONT_V3_LUT_ENABLED` を追加(デフォルト OFF)。front v3 ON 時に size→class→route を Tiny 専用 LUT から 1 ルックアップで取得し、従来の `hak_tiny_size_to_class` + route 読みを代替する。 +- LUT は起動時に既存の size→class 変換と route スナップショットをそのまま写経して構築するため挙動は変えない。 +- A/B (Mixed 16–1024B, ws=400, iters=1M, C7 v3 ON, Tiny/Pool v2 OFF, front v3 ON): + - LUT=0: 44.820M ops/s + - LUT=1: 45.231M ops/s(+0.9%) + - HEAP_STATS は TinyHeap v1 経路外のため出力なし。C7_PAGE_STATS は prepare_calls=2446 で変化なし。 + +### Header v3 (C7-only) 簡易スキップ実験 +- ENV: `HAKMEM_TINY_HEADER_V3_ENABLED` / `HAKMEM_TINY_HEADER_V3_SKIP_C7` を追加。C7 v3 alloc 時だけ tiny_region_id_write_header を通さず 1byte store にする。 +- Mixed 16–1024B (ws=400, iters=1M, front v3 ON, LUT ON, route_fast=0, Tiny/Pool v2 OFF): + - header_v3=0: 44.29M ops/s, C7_PAGE_STATS prepare_calls=2446 + - header_v3=1 + SKIP_C7=1: 43.68M ops/s(約 -1.4%)、prepare_calls=2446、fallback/page_of_fail=0 +- 所感: C7 v3 のヘッダ簡略だけでは perf 改善は見えず。free 側のヘッダ依存を落とす or header light/off を別箱で検討する必要あり。 diff --git a/docs/analysis/TINY_HEAP_V2_DESIGN.md b/docs/analysis/TINY_HEAP_V2_DESIGN.md index f32263e5..6e94a8a0 100644 --- a/docs/analysis/TINY_HEAP_V2_DESIGN.md +++ b/docs/analysis/TINY_HEAP_V2_DESIGN.md @@ -6,6 +6,31 @@ 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 と同等を維持するのが目的。 +現状評価サマリ (Phase66 時点) +---------------------------- +- C7 v2: + - C7-only / Mixed 16–1024B の長尺プロファイル(ws=400, iters=1M, PROFILE=C7_SAFE)では、v2 ON/OFF いずれも `HEAP_STATS[7].slow≈1`・refill≈1 に収束し、性能も v1 比で ±5% 以内(むしろ数%プラス)。 + - 短尺(20k/ws=64)の refill≒50 はウォームアップ由来であり、本命プロファイルには影響しないことを確認。 + - デフォルト構成では `HAKMEM_TINY_HOTHEAP_V2=0` を維持しつつ、C7-only の bench/pro 用プロファイルでは `HAKMEM_TINY_HOTHEAP_V2=1 HAKMEM_TINY_HOTHEAP_CLASSES=0x80` を opt-in 推奨とする。本線候補としては「C7 SAFE v1/v2 並列」の状態。 +- C6 v2: + - route/gate 修正後も C6-heavy / Mixed で大幅な回帰が残っており、C6-only v2 ON は v1 より明確に遅い。 + - 現状は構造レベルでは通電しているが perf 未達のため、「研究箱」として `HAKMEM_TINY_HOTHEAP_CLASSES=0x40/0xC0` を明示したときだけ有効にし、標準プロファイルでは必ず OFF にする。 + +Phase63: C6 v2 を opt-in で拡張(ベンチ結果は要改善) +----------------------------------------------- +- 実装: v2 Hot Box を class_idx パラメータ化し、C6 も `HAKMEM_TINY_HOTHEAP_CLASSES` の bit6 で有効化可能にした。stats もクラス配列化し、C6/C7 両方で route_hits / refill / fallback を追跡。TLS 初期化で C6 も stride と max_partial_pages=2 を設定。 +- ベンチ結果(Release, 1M/400, PROFILE=C7_SAFE, C7_HOT=1, v2 stats ON): + - C6-heavy min=257/max=768: v2 OFF **42.15M ops/s**、v2 ON(0x40) **29.69M ops/s**。v2 stats cls6 route_hits=0 / free_fb_v1=266930 で実質 v1 経路に落ちており、大幅回帰。 + - Mixed 16–1024B: C7 v2 only (0x80) **45.07M ops/s** かつ cls7 slow_prepare=2276。C6+C7 v2 (0xC0) **35.65M ops/s** まで悪化、cls6 は v1 のまま(route_hits=0)。 +- 結論: C6 v2 は初回 A/B で大幅マイナスかつ C6 ルートが実際には v2 を踏んでいない。デフォルトは v2 OFF のまま。C6 v2 は研究箱扱いを継続し、route LUT/冷温境界の見直し・refill 多発の是正が必要。 + +Phase64: C6 v2 route 修正・C7 v2 refill 再確認 +--------------------------------------------- +- front の route switch をクラス汎用にし、class6 でも `TINY_ROUTE_HOTHEAP_V2` を通るよう修正。v2 stats に route 値を表示。 +- C6-heavy (min=257/max=768, ws=400, iters=100k, classes=0x40): route_hits=26660, alloc_refill=1, fallback_v1=0 → v2 パスが有効化。ただしスループットは **35.5M ops/s** と v1 より低めで要チューニング。 +- C7-only 20k/ws=64 v2 ON: alloc_refill=48 / HEAP_STATS slow=48(v2 OFF は slow=1)。Mixed 20k/ws=256 でも refills≈42。短尺では refill 多発が残るため原因再調査中。長尺 1M/ws=400 では slow=1 のまま。 +- `refill_with_current` / `refill_with_partial` のカウンタを追加し、短尺では current/partial が空の状態で refill が発生していることを確認(partial/retire は未発火)。当面の運用: デフォルトは v2 OFF。C6 v2 は opt-in 研究箱、C7 v2 は bench/研究プロファイルのみ。 + 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。 @@ -333,3 +358,109 @@ Phase36 (C7-only Hot Box 暫定実装) - 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 経由の更新に寄せる必要がある。 + +ChatGPT Pro からのフィードバックと v2 ロードマップ +----------------------------------------------- + +ChatGPT Pro との設計相談の結果、TinyHeap v2 について以下の整理とロードマップを追加する。 + +### 1. どこを優先して変えるべきか + +- Mixed 16–1024B の現状: + - HAKMEM ≈ 41M ops/s + - mimalloc ≈ 113M ops/s(≒ 2.7–2.8×) + - system ≈ 92M ops/s +- mid/smallmid: + - HAKMEM ≈ 28–29M / mimalloc ≈ 54M / system ≈ 15M +- Superslab/OS: + - 1M ops あたりの Superslab OS 呼び出しは数回(`alloc≈2, free≈3, madvise≈2`)。 + - page-fault ≈ 6.6k/1M ops はほぼ first-write 起因と推定。 + +この状態から 70–80M (mimalloc の 60–70%) を目指すなら、優先度は: + +1. TinyHeap v2 を **v1 への依存がない本物の Hot Box** として再設計する(A案の強化) +2. mid/smallmid/pool v2 はその次の波で攻める(現状でも system より速い) +3. pf/first-touch/HugePage は v3 以降の「最後の 5〜10% を詰める」テーマ + +### 2. v1 の上にラッパを乗せるのはやめる + +Phase32–35 の v2 は「v1 の page を lease+pop/push するラッパ」であり、結果として: +- v2 → lease → v1 `tiny_heap_prepare_page` / slow_prepare に落ちる構造 +- Mixed で slow_prepare が爆増し、v1 より遅くなるケースが多かった + +今後の方針: +- v2 は **「v1 の上に座るラッパ」ではなく、v1 と并列の Hot Box** として扱う。 +- Superslab/Tier/Guard/Stats/Learning とは v1/v2 共通の Cold iface を介して話し、v2 から v1 の内部関数を直接叩かない。 + +### 3. v1/v2 共通の Cold インタフェース (TinyColdIface) の導入 + +v1 TinyHeapBox / v2 TinyHotHeapBox の両方が、同じ Cold Box 群(Superslab/Tier/Guard/Stats/Learning)と話すための共通 IF を導入する。 + +```c +// Hot → Cold (共通インタフェースのイメージ) +typedef struct TinyColdIface { + TinyPageMeta* (*refill_page)(void* cold_ctx, uint32_t class_idx); + void (*retire_page)(void* cold_ctx, TinyPageMeta* page); +} TinyColdIface; +``` + +- v1: + - 既存 TinyHeapBox の refill/empty 経路を `TinyColdIface` に束ねる。 +- v2: + - Hot Box からは `TinyColdIface` 経由で Superslab/Warm/Tier/Stats に触れる。 + +Cold 側(Superslab/Tier/Guard/Stats)は、「呼び出し元が v1 か v2 か」を意識せずに済む。 + +### 4. v1/v2 並立構成(Hot Box の二系統) + +Box 構造のイメージ: + +```text +[Front/Gate Box] + | + +-- if tiny_heap_version[class] == V1 ----> [TinyHeapBox v1] + | + +-- if tiny_heap_version[class] == V2 ----> [TinyHotHeapBox v2] + | + v + [ColdSuperslab/Tier/Stats/Remote/Guard Box] +``` + +- Front/Gate は `size → class → route` まで決め、その後 `tiny_heap_version[class_idx]` で v1/v2 を選ぶ。 +- v1/v2 とも Cold Box とは `TinyColdIface` だけを使う。 +- v1 は「安定線/safe path」、v2 は「新 Hot Box」として A/B 可能。 + +### 5. v2 の適用範囲と rollout 方針 + +- 設計は最初から C5–C7 をカバーできるようにする(`TinyHotHeapCtxV2.cls[0..7]` を持つ)。 +- rollout は **C7-only → C6 → C5** の順で段階的に: + - PolicySnapshotBox に `tiny_heap_version[class_idx]` を追加(V1/V2)。 + - 初期値は `C7 = V1`, C5–C6 も V1。 + - bench/分析で v2(C7-only) が安定・同等以上になった段階で `C7=V2` に昇格。 + - Mixed で問題なければ C6/C5 を順に昇格。 +- v1 は常に fallback として残し、`HAKMEM_TINY_HEAP_PROFILE=LEGACY` や専用 ENV で「全クラス v1」に戻せるようにする。 +- Phase62 実装メモ: + - C7 v2 に加えて C6 v2 を同じ Hot Box で扱えるようにした(Route mask bit6/0x40 で opt-in、デフォルト OFF)。 + - Cold IF は v1 をそのままラップする形で、C6/C7 どちらも refill/retire の 2 箇所のみで Superslab/Tier/Stats に触れる。 + - ステータス: C7 v2 は Mixed/C7-only で微プラス安定。C6 v2 は実装済みだが bench はこれから(bench専用・デフォルトは v1)。 + +### 6. v2 のスコープと v3 の線引き + +- v2 世代でやること: + - Tiny front/route Box → すでに 1 LUT + 1 switch に整理済み。 + - TinyHotHeap v2 を **v1 と并列の Hot Box** として実装し、C5–C7 を段階的に移行。 + - mid/smallmid/pool は v1 を維持しつつ、v2 の構造スケッチ(page-based pool 等)まで設計しておく。 + - Superslab/Segment/Tier/Guard/Remote の構造は **変えない**(Cold Box を動かさない)。 +- v3 以降でやること: + - Superslab/Segment のサイズ・配置・Tier 設計を再定義。 + - Tiny と mid/pool を統合した SmallObjectHeap を設計(サイズクラス統合など)。 + - Remote/cross-thread free の方式(リモートリスト vs メッセージ vs per-CPU)を見直す。 + - HugePage/NUMA/first-touch 最適化を本格的に取り入れる。 + +まとめると: +- v2 は「Hot Box(TinyHeap v2)を作り直し、Cold Box はほぼ据え置き」の世代。 +- v3 は「Cold Box(Superslab/Segment/Tier)も含めてオブジェクト heap 全体を再構成する」世代。 + +### 7. Phase65 後半の観測(C7 v2 長尺) +- C7-only / Mixed (ws=400, iters=1M) で v2 ON/OFF を再取得したところ、どちらも `slow≈1 / refill≈1` に収束し、性能も v2 ON が微プラス(C7-only 38.68M vs OFF 38.24M、Mixed 41.94M vs 41.78M)。 +- 短尺 20k で見える refill≈50 はウォームアップ由来と判断。運用デフォルトは v2 OFF のまま(研究箱扱い)だが、bench では C7 v2 を本命候補として扱える状態。 diff --git a/docs/analysis/TINY_NEXT_STEPS.md b/docs/analysis/TINY_NEXT_STEPS.md index 994ff6de..c877165e 100644 --- a/docs/analysis/TINY_NEXT_STEPS.md +++ b/docs/analysis/TINY_NEXT_STEPS.md @@ -44,3 +44,34 @@ Tiny Next Steps (Phase 19 メモ) - 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 比率を観測する。結果が悪くてもよいベンチ専用モードとする。 + +Phase68(更新版): 次に攻める大きな箱候補 +---------------------------------------- +- C7 HotHeap v2: + - C7-only / Mixed 長尺プロファイルでは v1 同等以上まで来ており、bench/pro 用には十分な状態。 + - ただしデフォルトは引き続き v2 OFF(C7 SAFE v1 本線)。今後は「C7-heavy なワークロード向けオプション」として扱う。 +- C6/C5 TinyHeap v2: + - C6 v2 は route 修正後も C6-heavy/Mixed で明確にマイナス。構造は通電したが perf 未達のため、当面は研究箱として据え置く。 + - C5 への拡張も v2 の再設計フェーズ(TinyHeap v3 相当)でまとめて考えるのが良さそう。 +- mid/smallmid / pool 側: + - Mixed 16–1024B / mid-large ベンチの perf 分析では、pool allocator(hak_pool_try_alloc/free)や smallmid パスが依然として大きな self% を占めている。 + - Phase68 以降の本命: **pool/smallmid の Hot Box 化・front 整理**。新設 `docs/analysis/POOL_V2_BOX_DESIGN.md` と `docs/design/POOL_HOTBOX_V2_IMPLEMENTATION_GUIDE.md` に箱と境界を記載。 + - Tiny v2 は C7-only ベースで一旦凍結し、mid/pool を先に +5〜10% 押し上げるのが mimalloc 7〜8割に近づく近道と考える。*** + +補足: C7 v3 と Mixed front +--------------------------- +- C7 v3: C7-only / Mixed で v3 ON が +α(slow=1、fallback=0、refill はウォームアップ由来)を確認。デフォルトは OFF のまま、C7-heavy bench で opt-in。 +- Mixed 16–1024B で v3 ON の userland perf は、リリースビルドでは `free`/`malloc` に潰れて非C7 Tiny front が見えない。DEBUG/記号付きビルドで再 perf を取り、size→class→route 前段や header 書き込みのフラット化を次ターゲット候補として再評価する。 + +Phase69: SmallObjectHotBox v3 構想(Tiny + mid/smallmid 統合) +---------------------------------------------------------------- +- 背景: + - Tiny (16–1024B) と mid/smallmid の両方で mimalloc に 0.4〜0.5× 程度の差が残っており、Tiny v2 / pool v2 での局所最適では 7〜8割に届きにくいことが見えてきた。 + - TinyHotHeap v2 (C7-only) は C7-heavy/Mixed 長尺で v1 SAFE を上回るところまで到達した一方、C6/C5 や pool v2 は研究箱のまま凍結となった。 +- 方針: + - Tiny (C0〜C7) と mid/smallmid の一部を統合して扱う SmallObjectHotBox v3 を新設し、「SmallObject 全体を 1 枚の Hot Box」として設計し直す。 + - Superslab/Segment/Tier/Guard/Remote は Cold Box として据え置き、Hot 側は SmallObjectHotBox v3 に集約する。 + - 設計は `docs/analysis/SMALLOBJECT_HOTBOX_V3_DESIGN.md` にまとめ、実装タスクは `docs/design/SMALLOBJECT_HOTBOX_V3_IMPLEMENTATION_GUIDE.md` に段階的に記載。 +- 位置づけ: + - Tiny v2 / pool v2 で得た知見(page_of O(1)、current/partial/retire ポリシー、Cold IF の 2 箇所化)を統合しつつ、v3 では「Tiny + mid/smallmid の一体化」を目指す。 + - 実装は bench/研究プロファイルから段階的に進め、v1/v2 への戻り道を常に維持する。*** diff --git a/docs/design/POOL_HOTBOX_V2_IMPLEMENTATION_GUIDE.md b/docs/design/POOL_HOTBOX_V2_IMPLEMENTATION_GUIDE.md new file mode 100644 index 00000000..04d81dbd --- /dev/null +++ b/docs/design/POOL_HOTBOX_V2_IMPLEMENTATION_GUIDE.md @@ -0,0 +1,52 @@ +# POOL_HOTBOX_V2_IMPLEMENTATION_GUIDE (Phase68 スケルトン) + +目的 +---- +- pool allocator のホットパスを TinyHotHeap v2 と同様に Hot/Cold 分離するための実装ガイド。 +- 現行 v1 をデフォルトのまま残しつつ、ENV ゲートで v2 を A/B できる形で段階的に導入する。 + +前提・ゲート +------------ +- デフォルトは v1(`HAKMEM_POOL_V2_ENABLED=0`)。v2 は bench/研究用に opt-in。 +- クラスマスクを追加する場合は `HAKMEM_POOL_V2_CLASSES`(bit per class)を想定。 + +実装ステップ +------------ +1. 型整備(Hot Box) + - `pool_page_v2`(freelist/used/capacity/base/meta/token) + - `pool_class_v2`(current_page/partial_pages/full_pages/stride) + - `pool_ctx_v2`(全クラス配列) + +2. TLS 初期化 + - `pool_v2_tls_get()` を追加し、calloc + memset で ctx を用意。 + - 各クラスの block_size/stride を初期化。`max_partial_pages` 等のポリシー値もここで設定。 + +3. alloc/free Hot パス + - `pool_v2_alloc(class_idx)`: + - current → partial → refill の順で freelist pop、page 内 used++。 + - `pool_v2_free(class_idx, ptr)`: + - page_of(ptr) を求め、freelist push / used--。 + - used==0 なら retire 判定(partial 上限を超えたら Cold へ返却)。 + - Hot パスでは OS/Stats/学習に触れない。 + +4. Cold IF + - `PoolColdIface` を定義 (`refill_page`, `retire_page`)。 + - 初期実装は既存 pool/superslab の refill/retire API をラップして流用し、Hot からは IF だけを見る。 + +5. Gate/Route + - front 入口は 1 LUT + switch(route)で v1/v2/legacy を選択。 + - `HAKMEM_POOL_V2_ENABLED` / classes mask で v2 を有効化し、fallback は常に v1 へ。 + +6. Stats/Fail-Fast + - ENV で有効化できる軽量 stats(alloc_fast/slow/refill/fallback, free_fast/retire 等)を用意。 + - 不整合(class mismatch/範囲外)は Fail-Fast、フォールバックは明示的にカウントする。 + +ベンチ計画(目安) +------------------ +- プロファイル: `./bench_mid_large_mt_hakmem 1 1000000 400 1`(Release, C7_SAFE, v2 OFF が基準)。 +- 目標: 28–29M ops/s → 30–32M (+5〜10%)。perf stat で cycles を明確に下げる。 + +メモ +---- +- Superslab/OS/Stats/Learning のロジックは変えない。Cold IF を挟んで境界を明確にするだけ。 +- C6-heavy など特定クラスから段階的に v2 を試し、回帰が出たら即ゲートで戻せる構造を優先する。*** diff --git a/docs/design/SMALLOBJECT_HOTBOX_V3_IMPLEMENTATION_GUIDE.md b/docs/design/SMALLOBJECT_HOTBOX_V3_IMPLEMENTATION_GUIDE.md new file mode 100644 index 00000000..f482b444 --- /dev/null +++ b/docs/design/SMALLOBJECT_HOTBOX_V3_IMPLEMENTATION_GUIDE.md @@ -0,0 +1,171 @@ +# SmallObjectHotBox v3 Implementation Guide + +対象と前提 +---------- +- 対象: Tiny (16〜1KiB) と mid/smallmid の一部を統合して扱う SmallObjectHotBox v3 の実装タスク。 +- 前提: + - 現状の v1 TinyHeap / C7 SAFE / pool v1 を「安定線」とし、v3 は bench/研究プロファイルから段階的に導入する。 + - TinyHotHeap v2 / pool v2 は研究箱として凍結済み。v3 では設計のインスピレーションとして扱い、直接の継承は行わない。 + +0. ゴールと非ゴール +------------------- +- ゴール: + - SmallObjectHotBox v3 を per-thread Hot Box として実装し、C7-only → C6/C5 → mid/smallmid の順に対象クラスを広げられる骨格を作る。 + - Hot Box 側は heap→page→block のみを扱い、Superslab/Segment/Tier/Guard/Remote との境界を + - alloc 側: `so_alloc_refill_slow(...)` + - free 側: `so_page_retire_slow(...)` + の 2 箇所に集約する。 + - A/B: PolicySnapshot + ENV (`HAKMEM_SMALL_HEAP_V3_ENABLED`, `HAKMEM_SMALL_HEAP_V3_CLASSES`) で v1/v3 を即切替可能にする。 +- 非ゴール: + - Superslab/Segment/Tier/Guard/Remote の設計変更(サイズや Tier ポリシーの再定義)は行わない。 + - C0〜C4 や 2KiB 超〜large サイズ帯を v3 ですべて置き換えること(v3 初期フェーズでは 16〜1024B/2048B 付近に絞る)。 + - 学習層 (ACE/ELO) の仕様変更。PolicySnapshot の更新だけを学習側が持ち、Hot パスは snapshot を読むだけにする。 + +1. 型の導入 (Hot Box) +--------------------- + +ファイル案: +- `core/box/smallobject_hotbox_v3_box.h` … 型と inline API +- `core/smallobject_hotbox_v3.c` … slow path 実装(任意) + +作業指示: +1. `so_page_t` / `so_class_t` / `so_ctx_t` を定義する(`SMALLOBJECT_HOTBOX_V3_DESIGN.md` に準拠)。 + - `so_page_t` : freelist / used / capacity / class_idx / flags / block_size / base / slab_ref / next。 + - `so_class_t`: current / partial / max_partial / partial_count / block_size。 + - `so_ctx_t` : `so_class_t cls[SMALLOBJECT_NUM_CLASSES];`。 +2. TLS getter `so_tls_get()` を実装: + +```c +so_ctx_t* so_tls_get(void) { + static __thread so_ctx_t* s_ctx; + so_ctx_t* ctx = s_ctx; + if (!ctx) { + ctx = calloc(1, sizeof(so_ctx_t)); + if (!ctx) abort(); + s_ctx = ctx; + for (int i = 0; i < SMALLOBJECT_NUM_CLASSES; i++) { + ctx->cls[i].block_size = smallobject_block_size_for_class(i); + ctx->cls[i].max_partial = default_partial_for_class(i); // 例: 2 + } + } + return ctx; +} +``` + +2. Cold IF (SmallObjectColdIface) の実装 +--------------------------------------- + +ファイル案: +- `core/box/smallobject_cold_iface_v1.h` + +作業指示: +1. Cold IF struct を定義: + +```c +typedef struct SmallObjectColdIface { + so_page_t* (*refill_page)(void* cold_ctx, uint32_t class_idx); + void (*retire_page)(void* cold_ctx, uint32_t class_idx, so_page_t* page); +} SmallObjectColdIface; +``` + +2. v1 TinyHeap / pool v1 / Superslab の既存 API を呼ぶ v1 ラッパを追加: + - `smallobject_cold_refill_page_v1(void* cold_ctx, uint32_t ci)`: + - Tiny/Pool v1 側から SmallObject 用ページを 1 枚借りるヘルパ(class_idx→Superslab/Segment→ページ構造を構築)。 + - `so_page_t` に `base/block_size/capacity/slab_ref` をセットし返す。 + - `smallobject_cold_retire_page_v1(void* cold_ctx, uint32_t ci, so_page_t* page)`: + - page->slab_ref/base を使って v1 側にページ返却。 +3. `SmallObjectColdIface smallobject_cold_iface_v1(void)` を実装し、Hot Box 側はこの IF を通してしか Cold Box を触らないようにする。 + +3. Hot パス実装 (alloc/free) +---------------------------- + +3-1. alloc_fast / alloc_refill_slow +----------------------------------- + +1. `static inline void* so_alloc_fast(so_ctx_t* ctx, uint32_t ci);` + - current / partial から freelist pop。失敗時は `NULL` を返す(slow に任せる)。 +2. `void* so_alloc(uint32_t ci);` + - `ctx = so_tls_get();` + - fast で hit すればそのまま返す。 + - miss のとき: + - `SmallObjectColdIface cold = smallobject_cold_iface_v1();` + - `so_alloc_refill_slow(ctx, ci, &cold)` を呼ぶ。 +3. `so_alloc_refill_slow` の中で: + - v1 Cold IF から `so_page_t*` を 1 枚借りる。 + - `so_page_t` 内の freelist が未構築なら block_size/capacity/base から carve。 + - `hc->current` にセットしてから `so_alloc_fast` を再呼び出し。 + +3-2. free_fast / page_retire_slow +--------------------------------- + +1. `static inline void so_free_fast(so_ctx_t* ctx, uint32_t ci, void* ptr);` + - `so_page_t* p = so_page_of(ptr);`(O(1)page_of、ヘッダ+mask 前提)。 + - freelist push, `--used`。 + - `used==0` のとき: + - `partial_count < max_partial` なら partial リストに温存。 + - それ以外は `so_page_retire_slow()` に渡す。 +2. `so_page_retire_slow(so_ctx_t* ctx, uint32_t ci, so_page_t* p, SmallObjectColdIface* cold);` + - Hot のリストから unlink。 + - `cold->retire_page(cold_ctx, ci, p)` で Cold Box に返却。 + - `so_page_t` ノードは free or reuse。 + +4. Front/Gate/Route 統合 +------------------------ + +作業指示: +1. Route LUT に SmallObjectHotBox v3 用の route を追加: + - 例: `TINY_ROUTE_SMALL_HEAP_V3`。 +2. Policy/ENV: + - `HAKMEM_SMALL_HEAP_V3_ENABLED` / `HAKMEM_SMALL_HEAP_V3_CLASSES` を追加し、class_mask で v3 を有効化。 + - 初期値は 0(全クラス v1 のまま)。 +3. Front(malloc/free)で: + +```c +if (small_heap_v3_enabled_for_class(class_idx)) { + void* p = so_alloc((uint32_t)class_idx); + if (p) return p; + // v3 alloc 失敗時は静かに v1 へ fallback(stats に記録) +} +// 既存 Tiny/pool/legacy 経路へ +``` + +- free も同様に、v3 route のみ `so_free` を呼び、それ以外は現行経路へ。 + +5. Stats / Fail-Fast / A/B +-------------------------- + +5-1. Stats +--------- + +1. `so_stats_class_t` を定義し、クラス別に: + - `alloc_calls`, `alloc_fast`, `alloc_refill`, `alloc_fallback_v1` + - `free_calls`, `free_fast`, `free_fallback_v1` + を `_Atomic` で持つ。 +2. ENV `HAKMEM_SMALL_HEAP_V3_STATS=1` で destructor から 1 行ダンプ。 + +5-2. Fail-Fast +-------------- + +1. 範囲外 ptr / misaligned / header_missing など page_of の前提が壊れた場合は、デバッグフェーズでは abort する。 +2. 構造安定後は、Fail-Fast から「ログ+v1 fallback」に切り替えられるように ifdef/ENV を用意する。 + +5-3. A/B +-------- + +1. C7-only プロファイル: + - v3 OFF: 現行 C7 SAFE v1(または C7 v2)を基準にする。 + - v3 ON : C7 クラスだけ SmallObjectHotBox v3 に経路を切り替え、ops/s と stats を比較。 +2. Mixed 16–1024B: + - C7-only v3 と C7+C6 v3 の A/B を取り、±5% 以内 or プラスであれば次のクラスに広げる。 + +6. 段階的 rollout と rollback +------------------------------ + +作業指示: +1. すべての v3 変更は ENV で OFF にできるようにする: + - `HAKMEM_SMALL_HEAP_V3_ENABLED=0` で完全に v1/v2 へ戻る。 +2. クラス単位で v3 を有効にする: + - まず C7-only(bit7)→ C6/C5 → mid/smallmid の順で classes マスクを広げる。 +3. どのフェーズでも「v3 を OFF にすれば元の挙動に戻る」ことを前提に、Fail-Fast と stats を優先的に実装する。 + +このガイドに従って実装を進めれば、SmallObjectHotBox v3 を Box Theory に沿って段階的に導入しつつ、常に v1/v2 への安全な戻り道を確保できます。実装の各ステップは小さく分け、必ずベンチと stats で A/B を取りながら前に進めてください。*** diff --git a/docs/design/TINY_FRONT_V3_FLATTENING_GUIDE.md b/docs/design/TINY_FRONT_V3_FLATTENING_GUIDE.md new file mode 100644 index 00000000..5f0ea4f7 --- /dev/null +++ b/docs/design/TINY_FRONT_V3_FLATTENING_GUIDE.md @@ -0,0 +1,92 @@ +# TINY FRONT v3 FLATTENING GUIDE + +Mixed 16–1024B で C7 v3 を ON にしたときの前段ホットパスを薄くするための設計メモ。実装は別フェーズ担当 AI 向けの TODO リストです。 + +## 現状の malloc_tiny_fast 前段フロー(ざっくり) +- size → 「Tiny かどうか」判定。 +- Tiny なら size → class_idx (C0〜C7) 変換。 +- route LUT (`tiny_route_for_class(class_idx)`) で route 決定。 +- route switch で Tiny v1 / v2 / v3 / legacy を呼び分け。 +- C7 v3 は早期パスで direct に `so_alloc` / `so_free` へ行くが、他クラスの前段 if/switch がまだ残っている。 + +## perf で見えたボトルネック(Mixed 16–1024B, v3 ON, DEBUGビルド) +- `tiny_region_id_write_header` ~6.7% +- `ss_map_lookup` ~3.6% +- `unified_cache_enabled` ~2.8%, `tiny_guard_is_enabled` ~2.2% +- `classify_ptr` (size→class 判定) ~1.4% +- `mid_desc_lookup` ~1.3% +- so_alloc/so_free 自体は合計 ~7%(C7 v3 本体)。ここでは C7 以外の前段を削るのが目的。 + +## フラット化方針(次フェーズで実装する項目) +1) **size→class→route を 1 LUT + 1 switch に寄せる** + - C7 v3 の早期 if を最小限にし、C5/C6 などの分岐を共通 switch にまとめる。 + - route の再判定・重複チェックを減らす。デバッグ/稀パスは `unlikely` 側へ。 + +2) **header/guard 系の前段整理** + - `tiny_region_id_write_header` の呼び出し回数・書き込みバイト数を確認し、必要最小限にする。 + - guard 判定 (`tiny_guard_is_enabled` など) を Snapshot 側に寄せ、front では値を読むだけにする。 + +3) **Superslab 判定の軽量化** + - C7 v3 で self-thread が確定している場合の `ss_map_lookup` を避けられるか検討。 + - lookup が必要なケースを TinyLookupBox に閉じ込め、ホットパスは lookup なしで通す。 + +## A/B 用プロファイル(固定) +- `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_POOL_V2_ENABLED=0` +- `HAKMEM_SMALL_HEAP_V3_ENABLED=1` +- `HAKMEM_SMALL_HEAP_V3_CLASSES=0x80` +- ベンチ: `./bench_random_mixed_hakmem 1000000 400 1` + +成功ライン: 上記環境で v3 ON のまま **+5〜10%** か、少なくとも回帰なし& HEAP_STATS に異常なし。 + +## Phase1 (done): フラグ判定のスナップショット化 +- ENV ゲート: + - `HAKMEM_TINY_FRONT_V3_ENABLED` … デフォルト OFF(有効時のみ v3 フロント経路) + - `HAKMEM_TINY_FRONT_V3_STATS` … 任意のデバッグカウンタ用 +- Snapshot(`TinyFrontV3Snapshot`)に + - unified_cache_on + - tiny_guard_on + - header_mode + を 1 回だけキャッシュし、front v3 ON のときは `unified_cache_enabled` / `tiny_guard_is_enabled` / `tiny_header_mode` の呼び出しをホットパスから排除。 +- malloc/free とも snapshot を読むブロックに入り、UC/guard 判定をキャッシュ経由に統一。 +- A/B(Mixed 16–1024B, ws=400, iters=1M, C7 v3 ON, Tiny/Pool v2 OFF)で挙動変化なし(slow=1 維持)。次は size→class 前段のフラット化に進める。 + +## Phase2-A: size→class LUT 化(実装済み、A/B 待ち) +- ENV: `HAKMEM_TINY_FRONT_V3_LUT_ENABLED`(デフォルト OFF、v3 ON 時のみ有効化可能)。 +- Tiny 前段専用の LUT (`TinyFrontV3SizeClassEntry`) を 1 回だけ構築し、front v3 が有効かつ LUT ON のときは + `malloc_tiny_fast` が size→class→route を 1 ルックアップで取得(挙動は既存 `hak_tiny_size_to_class` / route スナップショットに一致)。 +- フォールバック: LUT が無効/範囲外/未初期化のときは従来の size→class ロジックに自動で戻る。 +- Mixed 16–1024B A/B(ws=400, iters=1M, C7 v3 ON, Tiny/Pool v2 OFF, front v3 ON) + - LUT=0: 44.820M ops/s + - LUT=1: 45.231M ops/s(+0.9%) + - C7_PAGE_STATS: prepare_calls=2446(短尺時と同値)、HEAP_STATS は TinyHeap v1 経路外のため出力なし。 + - 挙動回帰なしで微プラス。次は size→class→route 前段のさらなるフラット化を検討。 + +## Phase2-B: route fast path(LUT→1 switch) +- ENV: `HAKMEM_TINY_FRONT_V3_ROUTE_FAST_ENABLED`(デフォルト OFF、front v3 + LUT ON 時だけ opt-in)。 +- 仕様: + - size→class→route を LUT 2 バイト load で取得し、即 1 switch で v3/v2/v1/legacy に分岐。 + - route helper(tiny_route_for_class)呼び出しをホットパスから外し、挙動は LUT 構築時のスナップショットに従う。 + - ROUTE_FAST=0 のときは Phase2-A と同じ挙動に自動フォールバック。 +- A/B(Mixed 16–1024B, ws=400, iters=1M, C7 v3 ON, Tiny/Pool v2 OFF, front v3 ON, LUT ON) + - route_fast=0: 45.066M ops/s + - route_fast=1: 44.821M ops/s(約 -0.5%) + - C7_PAGE_STATS: prepare_calls=2446、回帰なし。HEAP_STATS は TinyHeap v1 経路外のため未出力。 +- メモ: 微小マイナスだったためデフォルトは ROUTE_FAST=0 のまま。size→class 前段や header/guard 整理の方が効果が出そう。 + +## Phase2-C: Header v3 (C7-only skip) 実験 +- ENV: + - `HAKMEM_TINY_HEADER_V3_ENABLED`(デフォルト 0) + - `HAKMEM_TINY_HEADER_V3_SKIP_C7`(デフォルト 0、C7 v3 alloc 時に tiny_region_id_write_header をスキップして簡易 1byte だけ書く) +- 実装ポイント: + - TinyFrontV3Snapshot に header_v3_{enabled,skip_c7} を追加。 + - so_alloc_fast(C7) で header_v3_skip_c7 が有効なときは header を簡易 1store のみで書き、ガード/重い処理を省略。 + - front/free 側の挙動は不変(ヘッダは class 判定用に 1byte だけ残す)。 +- A/B(Mixed 16–1024B, ws=400, iters=1M, front v3/LUT ON, route_fast=0, C7 v3 ON) + - header_v3=0: 44.29M ops/s, C7_PAGE_STATS prepare_calls=2446 + - header_v3=1 + SKIP_C7=1: 43.68M ops/s(約 -1.4%), prepare_calls=2446, v3 fallback/page_of_fail=0 +- 所感: 短尺の header スキップだけでは改善なし。free 側の header 依存を外す or header_light 再設計を別フェーズで検討。 diff --git a/docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md b/docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md index 76d65a90..34745a2b 100644 --- a/docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md +++ b/docs/design/TINY_HOTHEAP_V2_IMPLEMENTATION_GUIDE.md @@ -9,6 +9,23 @@ - `CURRENT_TASK.md` (Phase36 状況メモ) - ステータス: Phase36 で C7-only の Hot 部(current_page+freelist)を v2 で自前管理する暫定実装を投入済み。Superslab/Tier/Stats は v1 に委譲する lease/refill/retire 境界で A/B できる状態。 +### Phase65/66: C7-only フェーズ完了サマリ +- C7-only / Mixed 16–1024B の長尺プロファイル(ws=400, iters=1M, PROFILE=C7_SAFE)で、v2 ON/OFF いずれも `HEAP_STATS[7].slow≈1`・refill≈1 に収束し、性能は v1 比で ±5% 以内(むしろ数%プラス)。 +- 短尺ベンチ(20k/ws=64)で見える refill≒50 はウォームアップ由来と判断され、本命プロファイルに影響しないことを確認。 +- 運用上の扱い: + - デフォルト構成では `HAKMEM_TINY_HOTHEAP_V2=0`(C7 SAFE v1)を維持。 + - C7-only の bench/pro 用プロファイルでは `HAKMEM_TINY_HOTHEAP_V2=1 HAKMEM_TINY_HOTHEAP_CLASSES=0x80` を opt-in 推奨とし、v2 を本命候補の Hot Box として使う。 + - C6 v2 は構造こそ通電したものの perf 未達のため、引き続き研究箱(classes=0x40/0xC0 を明示したときだけ有効)に留める。 + +### Phase60 速報(C7-only 空ページ保持ポリシー) +- 追加したもの: + - `max_partial_pages`(C7 デフォルト 2)と `partial_count` を Hot Box に追加し、free で `used==0` の page は一度 partial へ温存。上限超のみ retire を呼ぶ。 + - partial push/pop/peak と retire_v2 を v2 stats に追加(`HAKMEM_TINY_HOTHEAP_V2_STATS=1` で確認)。 +- 初期ベンチ: + - C7-only v2 ON で slow_prepare は `alloc_lease` と一致(≈48 回)。live ブロックが多く page が空にならないため partial/retire は未発火。 + - Mixed でも v2 ON は +2〜3% だが slow≈refill(42)。空ページを作れる workload またはページ容量/lease の見直しが次課題。 +- 運用: v2 は引き続き研究箱(デフォルト OFF)。C7-only で安定後に Mixed への適用を検討する。 + --- ## 0. ゴールと非ゴール @@ -396,3 +413,181 @@ Step 7-6: 再ベンチと判定基準 次フェーズで C6/C5 への拡張や C7 超ホットレーン(B案)を検討できる。 - 満たせない場合は v2 を引き続き **デフォルト OFF / 研究用箱** としたまま、 current_page / retire ポリシーの再修正に戻る。 + +--- + +## 8. Phase58: TinyColdIface 導入後の課題整理と次の実装指示 + +現状ステータス(Phase58 時点) +------------------------------ +- `core/box/tiny_cold_iface_v1.h` に `TinyColdIface` が導入され、v1 TinyHeap の + - `tiny_heap_prepare_page()` → `refill_page(cold_ctx, class_idx)` + - `tiny_heap_page_becomes_empty()` → `retire_page(cold_ctx, class_idx, page)` + としてラップ済み。Hot Box 側からはこの IF だけを見れば Superslab/Tier/Stats に触れられる状態になっている。 +- C7 v2 の Cold 境界は次のように接続済み: + - `tiny_hotheap_v2_refill_slow()`: + - `TinyColdIface cold = tiny_cold_iface_v1();` + - `tiny_heap_ctx_t* cold_ctx = tiny_heap_ctx_for_thread();` + - `ipage = cold.refill_page(cold_ctx, class_idx)` で v1 から `tiny_heap_page_t` を 1 枚借りる。 + - v2 側の `tiny_hotheap_page_v2` ノードを確保し、`base/capacity/meta/ss/slab_idx` をコピー。 + - freelist が無い場合は v2 が `used=0` にリセットし、`tiny_hotheap_v2_build_freelist()` で carve する。 + - freelist がある場合は `lease_page->meta->freelist` を v2 freelist で更新。 + - `hc->current_page` に必ず freelist 付きページが入るよう Fail-Fast チェックを追加。 + - `tiny_hotheap_v2_page_retire_slow()`: + - v2 の `current_page` / `partial_pages` / `full_pages` から対象ページを unlink。 + - `cold.retire_page(cold_ctx, class_idx, page->lease_page)` を呼んで v1 側へ返却。 + - `storage_page` 以外は `free(page)`、storage は `tiny_hotheap_v2_page_reset()` で初期化。 +- TLS 初期化 (`tiny_hotheap_v2_tls_get()`) では、`tiny_hotheap_ctx_v2` を `calloc` し、各クラスの `storage_page` を reset、`stride` を `tiny_stride_for_class(i)` で事前設定するようにした。 +- v2 Stats: + - `g_tiny_hotheap_v2_c7_*` に `alloc_calls/route_hits/alloc_fast/alloc_refill/alloc_fallback_v1/free_*` や + `cold_refill_fail` / `cold_retire_calls` を追加し、`HAKMEM_TINY_HOTHEAP_V2_STATS=1` で destructor ダンプ。 + +残っている問題(なぜ v2 ON で SEGV するか) +----------------------------------------- +- `tiny_hotheap_v2_try_pop()` は依然として v1 TinyHeap の page API に強く依存している: + - `candidate->lease_page` を `ipage` として取り出し、 + - `v1hcls->current_page = ipage;` + - `tiny_heap_page_pop(v1hcls, 7, ipage);` + - 必要なら `tiny_heap_page_mark_full(v1hcls, ipage);` + を呼んだうえで、 + - `candidate->freelist = ipage->free_list;` + - `candidate->used = ipage->used;` + と v2 側 state にもコピーしている。 +- これは Box Theory 的には + > Hot Box (v2) が Cold Box (v1 TinyHeap) の内部状態 (`free_list/used`) を直接操作している + ことになり、境界が二重になっている。 +- refill で v2 が独自に carve した freelist と、v1 の `tiny_heap_page_pop()` が更新する `free_list/used/meta` がずれていくと、 + - 無効な `ipage->base` / `capacity` / `meta` を deref したり + - v2 の `candidate->freelist` が壊れた状態で次の pop に進んでしまう + 可能性がある。現状の SIGSEGV はこのあたりの不整合に起因していると考えられる。 + +Phase58 でやるべきこと(実装指示) +--------------------------------- + +### 8-1. `tiny_hotheap_v2_try_pop()` から v1 依存を外し、v2 freelist だけで完結させる + +目的: +- Hot Box v2 は「自分の freelist を自分で pop する箱」とし、Cold Box v1 には refill/retire でしか触れないようにする。 + +作業指示: +- `tiny_hotheap_v2_try_pop()` を次の方針で書き換える: + - 引数から `tiny_heap_class_t* v1hcls` を削除し、`TinyColdIface` / v1 TinyHeap の型に依存しない形にする。 + - `candidate` から直接 freelist を pop: + + ```c + static inline void* tiny_hotheap_v2_try_pop(tiny_hotheap_page_v2* candidate, + TinyHeapClassStats* stats, + int stats_on) { + if (!candidate || !candidate->freelist || candidate->used >= candidate->capacity) { + return NULL; + } + void* user = candidate->freelist; + candidate->freelist = *(void**)user; + candidate->used++; + 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); + } + return tiny_region_id_write_header(user, 7); + } + ``` + + - v1 TinyHeap (`tiny_heap_page_pop` / `tiny_heap_page_mark_full` / `v1hcls->current_page` など) の呼び出しは完全に削除する。 + - `candidate->lease_page` は **Cold 側に返すための token** として保持するだけにし、Hot path では一切触らない。 + +### 8-2. Cold 境界 (`refill_page` / `retire_page`) を「イベント 2 箇所だけ」に限定する + +目的: +- Hot Box v2 と Cold Box v1 の境界を「page を借りる」「page を返す」の 2 箇所だけにし、meta/used/freelist の二重管理をやめる。 + +作業指示: +- `tiny_hotheap_v2_refill_slow()`: + - `TinyColdIface.refill_page()` から返ってきた `tiny_heap_page_t* ipage` については、 + - `base` / `capacity` / `slab_idx` / `meta` / `ss` など **geometry 情報だけ** を読む。 + - `ipage->free_list` / `ipage->used` は Hot path では信頼しない(v2 が自前で carve した freelist を使う)。 + - v2 側の `tiny_hotheap_page_v2` に freelist を構築したら、以降の alloc/free は v2 freelist だけで完結させる。 + - どうしても meta に初期値を書き戻す必要がある場合は、Cold Stats Box 経由の更新に寄せる(直接 meta->used に触らない方向を優先)。 +- `tiny_hotheap_v2_page_retire_slow()`: + - `page->used == 0` かつ v2 内のリストから unlink した段階でのみ `TinyColdIface.retire_page()` を呼ぶ。 + - 返却後は `page->lease_page` を含めて v2 側 state を破棄または reset し、同じ token を Hot path から再利用しない。 + +### 8-3. 安全側のガードと Stats での確認ポイント + +目的: +- v2 を再度 ON にした際、segv や Cold IF への過剰依存がないかをすぐに検知できるようにする。 + +作業指示: +- `tiny_hotheap_v2_alloc()` / `tiny_hotheap_v2_free()` の入口で: + - `class_idx != 7` は即 `NULL` or fallback(C7-only 実験箱のまま)。 + - `v2ctx == NULL` / `hc == NULL` / `hc->stride == 0` などは Fail-Fast(abort)で早期検出。 +- `tiny_hotheap_v2_try_pop()`: + - `candidate->capacity == 0` や `candidate->used > candidate->capacity` の場合は即 `NULL` を返す(デバッグビルドでは 1 回だけログ)。 +- Stats: + - C7-only ベンチ (`ws=64, iters=20k, PROFILE=C7_SAFE`) で: + - v2 OFF: 既存どおり `HEAP_STATS[7] fast≈11015 slow≈1`。 + - v2 ON: + - `alloc_fast` が 1 回目の page refill 後に `route_hits` と同程度まで増えていくこと。 + - `cold_refill_fail` が常に 0 付近であること。 + - `alloc_fallback_v1` / `free_fallback_v1` が 0〜ごく少数に収まっていること。 + +備考 +---- +- Phase58 の目的は **「TinyColdIface を導入したうえで、v2 Hot Box と v1 TinyHeap の境界を 2 箇所 (refill/retire) に絞ること」** であり、 + この段階では性能よりも安定性(segv しないこと)と Box 境界の明確化を優先する。 +- C7-only で v2 が v1 と同等レベルで安定したら、次フェーズで C6/C5 への拡張や perf チューニング(分岐削減・命令数削減)に進む。 + +--- + +## 9. Phase60/61: C7-only フェーズ完了と次のターゲット (C6 拡張) メモ + +現状まとめ(C7 v2 フェーズの着地) +--------------------------------- +- C7-only: + - v2 OFF (C7 SAFE v1) ≈ 42M ops/s / slow≈1。 + - v2 ON (C7-only, classes=0x80) ≈ 50M ops/s / slow≈40〜50 → 空ページ保持ポリシー導入前の状態。 + - 空ページ保持ポリシー導入 (`max_partial_pages` / `partial_count` 追加) 後も、C7-only ではページが実質 full のまま回るため partial/retire はほぼ発火せず、slow は主に refill 回数として現れる。 +- Mixed 16–1024B: + - 短尺 (ws=256, iters=20k, PROFILE=C7_SAFE): + - v2 OFF: ≈43.3M ops/s, HEAP_STATS[7] fast=5691 slow=1。 + - v2 ON: ≈44.6M ops/s(約 +3%), HEAP_STATS[7] fast=5691 slow=1, fail/fallback=0。 + - 長尺 (ws=400, iters=1M, PROFILE=C7_SAFE): + - v2 OFF: ≈41.6M ops/s, HEAP_STATS[7] fast≈283k slow=1。 + - v2 ON: ≈42.4M ops/s(約 +2%), HEAP_STATS[7] fast≈283k slow=1, fail/fallback=0。 +- 境界: + - Cold IF (`TinyColdIface`) 経由の refill/retire は安定しており、`cold_refill_fail` / fallback_v1 は 0 に張り付き。 + - C7 v2 の Hot パスは `tiny_hotheap_page_v2.freelist` のみで完結し、v1 TinyHeap の page/pop は参照しない構造になった。 + +結論(C7 フェーズ) +------------------- +- C7 v2 Hot Box は: + - C7-only / Mixed / 短尺・長尺のいずれでも v1 C7 SAFE を上回るスループットを達成。 + - slow_prepare は v2 OFF/ON ともに 1(長尺)またはワークロード由来の refill 回数程度に収まり、Cold IF の異常ではない。 + - fail/fallback=0 で、v2→v1 フォールバックに頼らずに Hot Box 単体で完結できている。 +- 運用上の位置づけ: + - 標準デフォルト: 引き続き `HAKMEM_TINY_HOTHEAP_V2=0`(どの環境でも即 v1 C7 SAFE に戻せるようにする)。 + - bench/研究プロファイル: C7 クラスに限り `HAKMEM_TINY_HOTHEAP_V2=1` / `HAKMEM_TINY_HOTHEAP_CLASSES=0x80` を推奨設定とし、C7 v2 を本命候補として扱う。 + +次のターゲット候補 (C6/C5 拡張の入り口) +-------------------------------------- +- 目的: + - TinyHotHeap v2 の設計ゴールどおり、C7-only から C6/C5 へ段階的に適用範囲を広げて、Mixed 16–1024B 全体の性能を押し上げる。 +- Gate/Policy 側の前提: + - PolicySnapshot / Route LUT に `tiny_heap_version[class_idx]` または `HAKMEM_TINY_HOTHEAP_CLASSES` のマスクを持ち、 + - C7: まず v2 を有効(研究/bench向け)。 + - C6: 将来的に `classes |= 0x40` で C6 を v2 に昇格させる余地を残す。 + - C5: 同様に `classes |= 0x20` で C5 を v2 に載せる最終フェーズ。 +- C6 拡張時に検討すべきポイント(設計メモのみ、実装は別フェーズ): + - ワークロード分布: Mixed 16–1024B で C6 がどの程度トラフィックを持つか(既存 FRONT_CLASS stats を参考にする)。 + - 以前の Tiny C6 v1 実験では Mixed 回帰があったため、C6 v2 では: + - Route/Gate で C6 の Hot path を C7 と同じ 1 LUT + 1 switch に揃える。 + - Unified Cache / WarmPool / Superslab へのアクセス回数が増えないようにする(pure Hot Box 化を徹底)。 + - rollout 戦略: + 1. C6-only bench プロファイルで C6 v2 を ON にし、C6-heavy なベンチで C6 v1 と A/B(少なくとも回帰なし)。 + 2. Mixed 16–1024B で C7 v2 + C6 v2 構成と C7 v2 のみ構成を比較し、±5% 以内 or 微プラスであれば「C6 v2 を本命候補」とする。 + 3. いずれも満たせない場合は、C6 v2 を C7 v2 と同様に研究箱(ENV で個別に OFF 可能)として維持し、本線は C7 v2 + C6 v1 のままにする。 + +メモ: +- 本ガイドは C7 フェーズまでの実装指針とログを含んでおり、C6/C5 拡張は「同じ TinyHotHeapBox v2 の枠内で class を増やす」作業として扱う。 +- C6/C5 拡張の具体的な API 変更や stats 追加は、今後の Phase (例: Phase62 以降) で別途「C6 v2 指示書」として追記する想定。*** diff --git a/hakmem.d b/hakmem.d index 07b79f3b..d161be97 100644 --- a/hakmem.d +++ b/hakmem.d @@ -93,6 +93,12 @@ hakmem.o: core/hakmem.c core/hakmem.h core/hakmem_build_flags.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/smallobject_hotbox_v3_box.h \ + core/box/../front/../box/tiny_geometry_box.h \ + core/box/../front/../box/../hakmem_tiny_superslab_internal.h \ + core/box/../front/../box/../superslab/superslab_inline.h \ + core/box/../front/../box/smallobject_hotbox_v3_env_box.h \ + core/box/../front/../box/tiny_front_v3_env_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 \ @@ -264,6 +270,12 @@ 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/smallobject_hotbox_v3_box.h: +core/box/../front/../box/tiny_geometry_box.h: +core/box/../front/../box/../hakmem_tiny_superslab_internal.h: +core/box/../front/../box/../superslab/superslab_inline.h: +core/box/../front/../box/smallobject_hotbox_v3_env_box.h: +core/box/../front/../box/tiny_front_v3_env_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: diff --git a/hakmem_batch.d b/hakmem_batch.d index cb95f6a2..9b182e7c 100644 --- a/hakmem_batch.d +++ b/hakmem_batch.d @@ -1,11 +1,12 @@ hakmem_batch.o: core/hakmem_batch.c core/hakmem_batch.h core/hakmem_sys.h \ - core/hakmem_whale.h core/hakmem_env_cache.h core/hakmem_internal.h \ - core/hakmem.h core/hakmem_build_flags.h core/hakmem_config.h \ - core/hakmem_features.h core/box/ptr_type_box.h + core/hakmem_whale.h core/hakmem_env_cache.h core/box/ss_os_acquire_box.h \ + core/hakmem_internal.h core/hakmem.h core/hakmem_build_flags.h \ + core/hakmem_config.h core/hakmem_features.h core/box/ptr_type_box.h core/hakmem_batch.h: core/hakmem_sys.h: core/hakmem_whale.h: core/hakmem_env_cache.h: +core/box/ss_os_acquire_box.h: core/hakmem_internal.h: core/hakmem.h: core/hakmem_build_flags.h: diff --git a/hakmem_l25_pool.d b/hakmem_l25_pool.d index 70391b4c..82bfb0ad 100644 --- a/hakmem_l25_pool.d +++ b/hakmem_l25_pool.d @@ -1,9 +1,10 @@ hakmem_l25_pool.o: core/hakmem_l25_pool.c core/hakmem_l25_pool.h \ core/hakmem_config.h core/hakmem_features.h core/hakmem_internal.h \ core/hakmem.h core/hakmem_build_flags.h core/hakmem_sys.h \ - core/hakmem_whale.h core/box/ptr_type_box.h core/hakmem_syscall.h \ - core/box/pagefault_telemetry_box.h core/page_arena.h core/hakmem_prof.h \ - core/hakmem_debug.h core/hakmem_policy.h + core/hakmem_whale.h core/box/ptr_type_box.h core/box/ss_os_acquire_box.h \ + core/hakmem_syscall.h core/box/pagefault_telemetry_box.h \ + core/page_arena.h core/hakmem_prof.h core/hakmem_debug.h \ + core/hakmem_policy.h core/hakmem_l25_pool.h: core/hakmem_config.h: core/hakmem_features.h: @@ -13,6 +14,7 @@ core/hakmem_build_flags.h: core/hakmem_sys.h: core/hakmem_whale.h: core/box/ptr_type_box.h: +core/box/ss_os_acquire_box.h: core/hakmem_syscall.h: core/box/pagefault_telemetry_box.h: core/page_arena.h: diff --git a/hakmem_pool.d b/hakmem_pool.d index a5ee752a..9858eef9 100644 --- a/hakmem_pool.d +++ b/hakmem_pool.d @@ -2,14 +2,17 @@ hakmem_pool.o: core/hakmem_pool.c core/hakmem_pool.h \ core/box/hak_lane_classify.inc.h core/hakmem_config.h \ core/hakmem_features.h core/hakmem_internal.h core/hakmem.h \ core/hakmem_build_flags.h core/hakmem_sys.h core/hakmem_whale.h \ - core/box/ptr_type_box.h core/hakmem_syscall.h core/hakmem_prof.h \ - core/hakmem_policy.h core/hakmem_debug.h core/box/pool_tls_types.inc.h \ - core/box/pool_mid_desc.inc.h core/box/pool_mid_tc.inc.h \ - core/box/pool_mf2_types.inc.h core/box/pool_mf2_helpers.inc.h \ - core/box/pool_mf2_adoption.inc.h core/box/pool_tls_core.inc.h \ - core/box/pool_refill.inc.h core/box/pool_init_api.inc.h \ - core/box/pool_stats.inc.h core/box/pool_api.inc.h \ - core/box/pagefault_telemetry_box.h + core/box/ptr_type_box.h core/box/pool_hotbox_v2_header_box.h \ + core/hakmem_syscall.h core/box/pool_hotbox_v2_box.h core/hakmem_pool.h \ + core/hakmem_prof.h core/hakmem_policy.h core/hakmem_debug.h \ + core/box/pool_tls_types.inc.h core/box/pool_mid_desc.inc.h \ + core/box/pool_mid_tc.inc.h core/box/pool_mf2_types.inc.h \ + core/box/pool_mf2_helpers.inc.h core/box/pool_mf2_adoption.inc.h \ + core/box/pool_tls_core.inc.h core/box/pool_refill.inc.h \ + core/box/pool_init_api.inc.h core/box/pool_stats.inc.h \ + core/box/pool_api.inc.h core/box/pagefault_telemetry_box.h \ + core/box/pool_hotbox_v2_box.h core/box/tiny_heap_env_box.h \ + core/box/c7_hotpath_env_box.h core/hakmem_pool.h: core/box/hak_lane_classify.inc.h: core/hakmem_config.h: @@ -20,7 +23,10 @@ core/hakmem_build_flags.h: core/hakmem_sys.h: core/hakmem_whale.h: core/box/ptr_type_box.h: +core/box/pool_hotbox_v2_header_box.h: core/hakmem_syscall.h: +core/box/pool_hotbox_v2_box.h: +core/hakmem_pool.h: core/hakmem_prof.h: core/hakmem_policy.h: core/hakmem_debug.h: @@ -36,3 +42,6 @@ core/box/pool_init_api.inc.h: core/box/pool_stats.inc.h: core/box/pool_api.inc.h: core/box/pagefault_telemetry_box.h: +core/box/pool_hotbox_v2_box.h: +core/box/tiny_heap_env_box.h: +core/box/c7_hotpath_env_box.h: diff --git a/hakmem_sys.d b/hakmem_sys.d index 8d6d7659..aeb0e49d 100644 --- a/hakmem_sys.d +++ b/hakmem_sys.d @@ -1,3 +1,6 @@ -hakmem_sys.o: core/hakmem_sys.c core/hakmem_sys.h core/hakmem_debug.h +hakmem_sys.o: core/hakmem_sys.c core/hakmem_sys.h core/hakmem_debug.h \ + core/hakmem_env_cache.h core/box/ss_os_acquire_box.h core/hakmem_sys.h: core/hakmem_debug.h: +core/hakmem_env_cache.h: +core/box/ss_os_acquire_box.h: