Incremental improvements: mid_desc cache, pool hotpath optimization, and doc updates
**Changes:** - core/box/pool_api.inc.h: Code organization and micro-optimizations - CURRENT_TASK.md: Updated Phase MD1 (mid_desc TLS cache: +3.2% for C6-heavy) - docs/analysis files: Various analysis and documentation updates - AGENTS.md: Agent role clarifications - TINY_FRONT_V3_FLATTENING_GUIDE.md: Flattening strategy documentation **Verification:** - random_mixed_hakmem: 44.8M ops/s (1M iterations, 400 working set) - No segfaults or assertions across all benchmark variants - Stable performance across multiple runs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -58,6 +58,9 @@
|
||||
- Fail-Fast
|
||||
- ENOMEM・整合性違反はマスクせず露出。フォールバックは“停止しないための最後の手段”に限定
|
||||
|
||||
- 運用ルール(Pool flatten)
|
||||
- Pool v1 flatten / Zero Mode は LEGACY mid/smallmid ベンチ専用の箱。C7_SAFE プロファイルでは flatten を触らない(安定性優先のため常時 OFF)。
|
||||
|
||||
---
|
||||
|
||||
## 実装規約(C向けの具体)
|
||||
|
||||
@ -957,3 +957,14 @@ export HAKMEM_POOL_ZERO_MODE=header
|
||||
- **mid_desc_init_once()**: すべてのモードで初期化保証(クラッシュ防止)
|
||||
|
||||
**所感**: mid_desc 初期化順序の修正は本線として常に有効。Flatten と Zero Mode は箱として組み込まれているが、デフォルト構成ではいずれも OFF。中規模 (100K) ワークロードで Combined が +8.8% 到達、1M 長尺で +2% 程度と、性能ではなく安全性が主要成果。
|
||||
|
||||
### Phase MD1: mid_desc_lookup TLS キャッシュ(mid/smallmid ベンチ専用)
|
||||
- 目的: C6-heavy / mid/smallmid で目立つ `mid_desc_lookup` self% を TLS キャッシュで 1 回に抑える(サイズ変化で miss → 従来 path)。
|
||||
- ENV: `HAKMEM_MID_DESC_CACHE_ENABLED=1` で opt-in(デフォルト OFF、標準プロファイルは挙動不変)。
|
||||
- A/B: `HAKMEM_PROFILE=C6_HEAVY_LEGACY_POOLV1`, 1M/400, flatten OFF/zero full → OFF: 28.90M ops/s / ON: 29.83M ops/s(約 +3.2%)。C7_SAFE/Mixed は未確認(±数%以内を期待)。
|
||||
- Mixed 16–1024B (`HAKMEM_PROFILE=MIXED_TINYV3_C7_SAFE`, 1M/400): OFF 44.83M → ON 44.94M(+0.3% 以内、挙動変化なし)。標準 Mixed では推奨は据え置き(mid専用オプション扱い)。
|
||||
|
||||
### Phase TG2: tiny_alloc_gate_box 再構成(回帰で廃止)
|
||||
- 内容: tiny_alloc_gate_box を LUT/route 先頭分岐に寄せる再構成+ malloc_tiny_fast_dispatch の分離を試験。
|
||||
- 結果: Mixed 16–1024B (MIXED_TINYV3_C7_SAFE, Release) で **約 -14%** (44.8M → 38.6M ops/s) の回帰。segv/assert は無し。
|
||||
- 対応: Phase TG2 変更は破棄済み(tiny_alloc_gate_box / malloc_tiny_fast を元の直線ロジックへ戻した)。今後は gate 全体を触らず、header / classify / ptr fast path など局所削減で攻める方針。
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include "box/pool_hotbox_v2_box.h"
|
||||
#include "box/tiny_heap_env_box.h" // TinyHeap profile (C7_SAFE では flatten を無効化)
|
||||
#include "box/pool_zero_mode_box.h" // Pool zeroing policy (env cached)
|
||||
#include <stdint.h>
|
||||
|
||||
// Pool v2 is experimental. Default OFF (use legacy v1 path).
|
||||
static inline int hak_pool_v2_enabled(void) {
|
||||
@ -63,6 +64,37 @@ static inline int hak_pool_v1_flatten_stats_enabled(void) {
|
||||
return g;
|
||||
}
|
||||
|
||||
// Mid desc lookup TLS cache (mid bench opt-in; default OFF)
|
||||
static inline int hak_mid_desc_cache_enabled(void) {
|
||||
static int g = -1;
|
||||
if (__builtin_expect(g == -1, 0)) {
|
||||
const char* e = getenv("HAKMEM_MID_DESC_CACHE_ENABLED");
|
||||
g = (e && *e && *e != '0') ? 1 : 0;
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
typedef struct MidDescCache {
|
||||
void* last_page;
|
||||
MidPageDesc* last_desc;
|
||||
} MidDescCache;
|
||||
|
||||
static __thread MidDescCache g_mid_desc_cache = {0};
|
||||
|
||||
static inline MidPageDesc* mid_desc_lookup_cached(void* addr) {
|
||||
if (!hak_mid_desc_cache_enabled()) return mid_desc_lookup(addr);
|
||||
void* page = (void*)((uintptr_t)addr & ~((uintptr_t)POOL_PAGE_SIZE - 1));
|
||||
if (g_mid_desc_cache.last_desc && g_mid_desc_cache.last_page == page) {
|
||||
return g_mid_desc_cache.last_desc;
|
||||
}
|
||||
MidPageDesc* d = mid_desc_lookup(addr);
|
||||
if (d) {
|
||||
g_mid_desc_cache.last_page = page;
|
||||
g_mid_desc_cache.last_desc = d;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
|
||||
typedef struct PoolV1FlattenStats {
|
||||
_Atomic uint64_t alloc_tls_hit;
|
||||
@ -422,7 +454,7 @@ static inline void hak_pool_free_v2_impl(void* ptr, size_t size, uintptr_t site_
|
||||
|
||||
void* raw = (char*)ptr - HEADER_SIZE;
|
||||
AllocHeader* hdr = (AllocHeader*)raw;
|
||||
MidPageDesc* d_desc = mid_desc_lookup(ptr);
|
||||
MidPageDesc* d_desc = mid_desc_lookup_cached(ptr);
|
||||
int mid_by_desc = d_desc != NULL;
|
||||
if (!mid_by_desc && g_hdr_light_enabled < 2) {
|
||||
if (hdr->magic != HAKMEM_MAGIC) { MF2_ERROR_LOG("Invalid magic 0x%X in pool_free, expected 0x%X", hdr->magic, HAKMEM_MAGIC); return; }
|
||||
@ -490,7 +522,7 @@ static inline void hak_pool_free_v2_impl(void* ptr, size_t size, uintptr_t site_
|
||||
|
||||
static inline int hak_pool_mid_lookup_v2_impl(void* ptr, size_t* out_size) {
|
||||
if (g_mf2_enabled) { MidPage* page = mf2_addr_to_page(ptr); if (page) { int c = (int)page->class_idx; if (c < 0 || c >= POOL_NUM_CLASSES) return 0; size_t sz = g_class_sizes[c]; if (sz == 0) return 0; if (out_size) *out_size = sz; return 1; } }
|
||||
MidPageDesc* d = mid_desc_lookup(ptr); if (!d) return 0; int c = (int)d->class_idx; if (c < 0 || c >= POOL_NUM_CLASSES) return 0; size_t sz = g_class_sizes[c]; if (sz == 0) return 0; if (out_size) *out_size = sz; return 1;
|
||||
MidPageDesc* d = mid_desc_lookup_cached(ptr); if (!d) return 0; int c = (int)d->class_idx; if (c < 0 || c >= POOL_NUM_CLASSES) return 0; size_t sz = g_class_sizes[c]; if (sz == 0) return 0; if (out_size) *out_size = sz; return 1;
|
||||
}
|
||||
|
||||
static inline void hak_pool_free_fast_v2_impl(void* ptr, uintptr_t site_id) {
|
||||
@ -499,7 +531,7 @@ static inline void hak_pool_free_fast_v2_impl(void* ptr, uintptr_t site_id) {
|
||||
MidPage* page = mf2_addr_to_page(ptr);
|
||||
if (page) { mf2_free(ptr); return; }
|
||||
}
|
||||
MidPageDesc* d = mid_desc_lookup(ptr);
|
||||
MidPageDesc* d = mid_desc_lookup_cached(ptr);
|
||||
if (!d) return;
|
||||
size_t sz = g_class_sizes[(int)d->class_idx];
|
||||
if (sz == 0) return;
|
||||
@ -803,7 +835,7 @@ static inline void hak_pool_free_v1_impl(void* ptr, size_t size, uintptr_t site_
|
||||
|
||||
void* raw = (char*)ptr - HEADER_SIZE;
|
||||
AllocHeader* hdr = (AllocHeader*)raw;
|
||||
int mid_by_desc = 0; MidPageDesc* d_desc = mid_desc_lookup(ptr);
|
||||
int mid_by_desc = 0; MidPageDesc* d_desc = mid_desc_lookup_cached(ptr);
|
||||
if (d_desc) mid_by_desc = 1;
|
||||
if (!mid_by_desc && g_hdr_light_enabled < 2) {
|
||||
if (hdr->magic != HAKMEM_MAGIC) { MF2_ERROR_LOG("Invalid magic 0x%X in pool_free, expected 0x%X", hdr->magic, HAKMEM_MAGIC); return; }
|
||||
@ -814,7 +846,7 @@ static inline void hak_pool_free_v1_impl(void* ptr, size_t size, uintptr_t site_
|
||||
PoolBlock* block = (PoolBlock*)raw;
|
||||
if (g_pool.tls_free_enabled) {
|
||||
int same_thread = 0;
|
||||
if (g_hdr_light_enabled >= 1) { MidPageDesc* d = mid_desc_lookup(raw); if (d && d->owner_tid != 0 && d->owner_tid == (uint64_t)(uintptr_t)pthread_self()) { same_thread = 1; } }
|
||||
if (g_hdr_light_enabled >= 1) { MidPageDesc* d = mid_desc_lookup_cached(raw); if (d && d->owner_tid != 0 && d->owner_tid == (uint64_t)(uintptr_t)pthread_self()) { same_thread = 1; } }
|
||||
else if (hdr->owner_tid != 0 && hdr->owner_tid == (uintptr_t)(uintptr_t)pthread_self()) { same_thread = 1; }
|
||||
if (same_thread) {
|
||||
PoolTLSRing* ring = &g_tls_bin[class_idx].ring;
|
||||
@ -846,7 +878,7 @@ static inline void hak_pool_free_v1_impl(void* ptr, size_t size, uintptr_t site_
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (g_tc_enabled) { uint64_t owner_tid = 0; if (g_hdr_light_enabled < 2) owner_tid = hdr->owner_tid; if (owner_tid == 0) { MidPageDesc* d = mid_desc_lookup(raw); if (d) owner_tid = d->owner_tid; } if (owner_tid != 0) { MidTC* otc = mid_tc_lookup_by_tid(owner_tid); if (otc) { mid_tc_push(otc, class_idx, block); return; } } }
|
||||
if (g_tc_enabled) { uint64_t owner_tid = 0; if (g_hdr_light_enabled < 2) owner_tid = hdr->owner_tid; if (owner_tid == 0) { MidPageDesc* d = mid_desc_lookup_cached(raw); if (d) owner_tid = d->owner_tid; } if (owner_tid != 0) { MidTC* otc = mid_tc_lookup_by_tid(owner_tid); if (otc) { mid_tc_push(otc, class_idx, block); return; } } }
|
||||
int shard = hak_pool_get_shard_index(site_id); uintptr_t old_head; HKM_TIME_START(t_remote_push2);
|
||||
do { old_head = atomic_load_explicit(&g_pool.remote_head[class_idx][shard], memory_order_acquire); block->next = (PoolBlock*)old_head; } while (!atomic_compare_exchange_weak_explicit(&g_pool.remote_head[class_idx][shard], &old_head, (uintptr_t)block, memory_order_release, memory_order_relaxed));
|
||||
atomic_fetch_add_explicit(&g_pool.remote_count[class_idx][shard], 1, memory_order_relaxed); HKM_TIME_END(HKM_CAT_POOL_REMOTE_PUSH, t_remote_push2); set_nonempty_bit(class_idx, shard);
|
||||
@ -898,7 +930,7 @@ static inline void hak_pool_free_v1_flat(void* ptr, size_t size, uintptr_t site_
|
||||
if (!hak_pool_is_poolable(size)) return;
|
||||
|
||||
void* raw = (char*)ptr - HEADER_SIZE;
|
||||
MidPageDesc* d_desc = mid_desc_lookup(ptr);
|
||||
MidPageDesc* d_desc = mid_desc_lookup_cached(ptr);
|
||||
if (!d_desc) {
|
||||
if (hak_pool_v1_flatten_stats_enabled()) {
|
||||
atomic_fetch_add_explicit(&g_pool_v1_flat_stats.free_fallback_v1, 1, memory_order_relaxed);
|
||||
@ -965,7 +997,7 @@ static inline void hak_pool_free_v1_flat(void* ptr, size_t size, uintptr_t site_
|
||||
|
||||
static inline int hak_pool_mid_lookup_v1_impl(void* ptr, size_t* out_size) {
|
||||
if (g_mf2_enabled) { MidPage* page = mf2_addr_to_page(ptr); if (page) { int c = (int)page->class_idx; if (c < 0 || c >= POOL_NUM_CLASSES) return 0; size_t sz = g_class_sizes[c]; if (sz == 0) return 0; if (out_size) *out_size = sz; return 1; } }
|
||||
MidPageDesc* d = mid_desc_lookup(ptr); if (!d) return 0; int c = (int)d->class_idx; if (c < 0 || c >= POOL_NUM_CLASSES) return 0; size_t sz = g_class_sizes[c]; if (sz == 0) return 0; if (out_size) *out_size = sz; return 1;
|
||||
MidPageDesc* d = mid_desc_lookup_cached(ptr); if (!d) return 0; int c = (int)d->class_idx; if (c < 0 || c >= POOL_NUM_CLASSES) return 0; size_t sz = g_class_sizes[c]; if (sz == 0) return 0; if (out_size) *out_size = sz; return 1;
|
||||
}
|
||||
|
||||
static inline void hak_pool_free_fast_v1_impl(void* ptr, uintptr_t site_id) {
|
||||
@ -974,7 +1006,7 @@ static inline void hak_pool_free_fast_v1_impl(void* ptr, uintptr_t site_id) {
|
||||
MidPage* page = mf2_addr_to_page(ptr);
|
||||
if (page) { mf2_free(ptr); return; }
|
||||
}
|
||||
MidPageDesc* d = mid_desc_lookup(ptr);
|
||||
MidPageDesc* d = mid_desc_lookup_cached(ptr);
|
||||
if (!d) return;
|
||||
size_t sz = g_class_sizes[(int)d->class_idx];
|
||||
if (sz == 0) return;
|
||||
|
||||
@ -64,6 +64,7 @@ HAKMEM_SMALL_HEAP_V3_CLASSES=0x80 # C7-only v3, C6 v3 は OFF
|
||||
HAKMEM_POOL_V2_ENABLED=0
|
||||
HAKMEM_POOL_V1_FLATTEN_ENABLED=0 # flatten は初回 OFF
|
||||
```
|
||||
- mid_desc_lookup TLS キャッシュを試すときだけ: `HAKMEM_MID_DESC_CACHE_ENABLED=1` を上乗せ(デフォルトは OFF)。
|
||||
|
||||
### Pool v1 flatten A/B 用(LEGACY 専用)
|
||||
```sh
|
||||
@ -72,6 +73,21 @@ HAKMEM_TINY_HEAP_PROFILE=LEGACY
|
||||
HAKMEM_POOL_V2_ENABLED=0
|
||||
HAKMEM_POOL_V1_FLATTEN_ENABLED=1
|
||||
HAKMEM_POOL_V1_FLATTEN_STATS=1
|
||||
|
||||
## Profile 2b: C6_HEAVY_LEGACY_POOLV1_FLATTEN(mid/smallmid LEGACY flatten ベンチ専用)
|
||||
|
||||
### 目的
|
||||
- LEGACY プロファイルで mid/smallmid の flatten + header-only zero をまとめて opt-in するベンチ専用セット。
|
||||
- C7_SAFE では使わないこと(安定性優先のため C7_SAFE は flatten 常時 OFF)。
|
||||
|
||||
### ENV(ベンチ専用)
|
||||
```sh
|
||||
HAKMEM_PROFILE=C6_HEAVY_LEGACY_POOLV1 # base を流用
|
||||
HAKMEM_POOL_V1_FLATTEN_ENABLED=1
|
||||
HAKMEM_POOL_ZERO_MODE=header
|
||||
HAKMEM_POOL_V1_FLATTEN_STATS=1
|
||||
```
|
||||
※ LEGACY 専用。C7_SAFE / C7_ULTRA_BENCH ではこのプリセットを使用しないこと。
|
||||
```
|
||||
- flatten は LEGACY 専用。C7_SAFE / C7_ULTRA_BENCH ではコード側で強制 OFF になる前提。
|
||||
|
||||
|
||||
@ -38,6 +38,15 @@
|
||||
- perf stat(同条件 1M/400): cycles=225,766,496、instructions=528,335,613、task-clock=57.88ms、ops/s≈25.7M。
|
||||
- 所感: fast-path整理だけでは効果薄く、むしろ低下。pool 内の memset/desc まわりやリング補充順序をより大胆に削らないと改善しない。次のステップとして追加の枝削減・キャッシュ導入を検討。
|
||||
|
||||
## Phase MD1(mid_desc_lookup TLS キャッシュ)
|
||||
- 目的: C6-heavy/mid で目立つ mid_desc_lookup を TLS 1 エントリでキャッシュし、サイズが同じリクエストが続くケースで self% を削る。
|
||||
- ENV ゲート: `HAKMEM_MID_DESC_CACHE_ENABLED=1`(デフォルト OFF)。miss 時は従来の `mid_desc_lookup` にフォールバック。
|
||||
- baseline self%: mid_desc_lookup≈3.8%(Phase54 perf, C7_SAFE)。
|
||||
- A/B(C6_HEAVY_LEGACY_POOLV1, 1M/400, flatten OFF, zero=full):
|
||||
- cache OFF: 28.90M ops/s
|
||||
- cache ON : 29.83M ops/s(**+3.2%**)segv/assert なし。
|
||||
- Mixed 16–1024B (C7_SAFE front v3/LUT/fast classify ON): cache OFF 44.83M → ON 44.94M(+0.3%)。perf (cycles:u, release) では mid_desc_lookup は上位に出ず、self% 影響はごく小さい。
|
||||
|
||||
## Phase57 回帰トリアージ(pool v2 をゲート化)
|
||||
- 変更: `HAKMEM_POOL_V2_ENABLED` を追加し、v1/v2 の pool 実装を env で切替。細分スイッチとして `HAKMEM_POOL_V2_BLOCK_TO_USER` / `HAKMEM_POOL_V2_TLS_FAST_PATH` を用意(デフォルト ON, v2 時のみ有効)。
|
||||
- ベンチ(C6-heavy, 1M/400, Release):
|
||||
|
||||
@ -78,6 +78,22 @@ Throughput: **12.39M ops/s**(DEBUG/-O0 相当)
|
||||
- そのほか: free/malloc/main が約 30% 強、header write 系は今回のデバッグログに埋もれて確認できず。
|
||||
|
||||
所感:
|
||||
|
||||
## Phase HF1(DEBUG, front v3+LUT+fast classify+mid_desc_cache ON)
|
||||
- ビルド: `CFLAGS='-O0 -g' USE_LTO=0 OPT_LEVEL=0 NATIVE=0`
|
||||
- ENV: `HAKMEM_PROFILE=MIXED_TINYV3_C7_SAFE`, `HAKMEM_MID_DESC_CACHE_ENABLED=1`
|
||||
- コマンド: `perf record -F 5000 --call-graph dwarf -e cycles:u -o perf.data.tiny_mixed_hf1 ./bench_random_mixed_hakmem 1000000 400 1`
|
||||
- self% 上位(perf_tiny_mixed_hf1.txt 抜粋):
|
||||
- tiny_alloc_gate_fast 16.85%
|
||||
- free 13.63% / malloc 13.34% / main 9.02%(ベンチ枠)
|
||||
- __memset_avx2_unaligned_erms 5.65%(初期化)
|
||||
- hak_super_registry_init 5.57%(初期化)
|
||||
- so_alloc_fast 2.41%, unified_cache_push 2.23%
|
||||
- tiny_front_v3_enabled 2.23%, tiny_front_v3_lut_lookup 2.21%
|
||||
- smallobject_hotbox_v3_can_own_c7 1.94%
|
||||
- tiny_region_id_write_header 1.82%
|
||||
- ss_map_lookup 1.61%, mid_desc_lookup_cached 0.98%, classify_ptr 0.65%
|
||||
所感: TF3 + mid_desc_cache 適用後、ss_map_lookup/self% は 1.6% まで沈み、tiny_region_id_write_header が引き続き ~1.8% で上位。次の削り候補は header 書き込み回数削減 or front前段の小枝刈り。
|
||||
- front v3 + LUT ON でも free 側の `ss_map_lookup` / `hak_super_lookup` が ~11% 程度残っており、ここを FAST classify で直叩きする余地が大きい。
|
||||
- `classify_ptr` は 1% 未満だが、`ss_map_lookup` とセットで落とせれば +5〜10% の目標に寄せられる見込み。
|
||||
|
||||
|
||||
@ -114,3 +114,7 @@ Mixed 16–1024B で C7 v3 を ON にしたときの前段ホットパスを薄
|
||||
- OFF: 33.9M ops/s → ON: 36.7M ops/s(約 +8.1%)。
|
||||
- DEBUG perf (cycles@5k, dwarf, gate=1): `ss_map_lookup` self が 7.3% → 0.9%、`hak_super_lookup` はトップ外へ。TLS 走査 (`smallobject_hotbox_v3_can_own_c7`) が ~5.5% に現れるが lookup 往復より低コスト。
|
||||
- ロールアウト案: Mixed 基準でプラスが安定しているため、front v3/LUT ON 前提では fast classify もデフォルトON候補。ENV=0 で即オフに戻せる構造は維持。
|
||||
|
||||
## メモ: gate 大規模再構成(TG2)は回帰で撤退
|
||||
- tiny_alloc_gate_box を「size→class→route 判定を前段に集約」する形に再構成し、malloc_tiny_fast_dispatch を導入したが、Mixed 16–1024B (MIXED_TINYV3_C7_SAFE, Release) で **約 -14%** の回帰を確認。
|
||||
- 変更は破棄し、gate は従来の薄いラッパに戻した。今後は gate 全体をいじらず、ヘッダ書き込み削減・ptr classify・header/route snapshot 等の局所的枝刈りで進める。
|
||||
|
||||
BIN
perf.data.mid_cache_on
Normal file
BIN
perf.data.mid_cache_on
Normal file
Binary file not shown.
BIN
perf.data.tiny_mixed_hf1
Normal file
BIN
perf.data.tiny_mixed_hf1
Normal file
Binary file not shown.
Reference in New Issue
Block a user