diff --git a/core/box/smallobject_hotbox_v7_box.h b/core/box/smallobject_hotbox_v7_box.h index 752b4f75..d0ace2ca 100644 --- a/core/box/smallobject_hotbox_v7_box.h +++ b/core/box/smallobject_hotbox_v7_box.h @@ -1,9 +1,14 @@ -// smallobject_hotbox_v7_box.h - SmallObject HotBox v7 (Phase v7-2: C6-only impl) +// smallobject_hotbox_v7_box.h - SmallObject HotBox v7 (Phase v7-5a: Hot path極限最適化) // // Role: // - SmallObject v7 fast path for alloc/free // - C6-only implementation (512B blocks, 64KiB pages, 2MiB segments) // - Uses SmallHeapCtx_v7 + SmallSegment_v7 + ColdIface_v7 +// +// v7-5a optimizations: +// - Stats (alloc_count, free_count, live_current) removed from hot path +// - Global atomic stats gated by ENV (HAKMEM_V7_HOT_STATS) +// - Header write kept (required due to intrusive freelist overlapping block[0]) #pragma once @@ -11,6 +16,7 @@ #include #include #include +#include // for getenv() #include "smallsegment_v7_box.h" #include "smallobject_cold_iface_v7_box.h" #include "region_id_v6_box.h" @@ -22,7 +28,7 @@ #endif // ============================================================================ -// Debug/Observe Support +// Debug/Observe Support (v7-5a: ENV-gated for hot path) // ============================================================================ // V7 stats functions (defined in smallobject_cold_iface_v7.c) @@ -31,6 +37,23 @@ extern void small_v7_stat_free(void); extern void small_v7_stat_refill(void); extern void small_v7_stat_retire(void); +// v7-5a: ENV gate for hot path stats (default OFF for performance) +// Set HAKMEM_V7_HOT_STATS=1 to enable per-alloc/free atomic counters +static inline int small_v7_hot_stats_enabled(void) { + static int g_enabled = -1; + if (__builtin_expect(g_enabled < 0, 0)) { + const char* e = getenv("HAKMEM_V7_HOT_STATS"); + g_enabled = (e && *e && *e != '0') ? 1 : 0; + } + return g_enabled; +} + +// Conditional stat increment (only if ENV enabled) +#define SMALL_V7_HOT_STAT_ALLOC() \ + do { if (__builtin_expect(small_v7_hot_stats_enabled(), 0)) small_v7_stat_alloc(); } while(0) +#define SMALL_V7_HOT_STAT_FREE() \ + do { if (__builtin_expect(small_v7_hot_stats_enabled(), 0)) small_v7_stat_free(); } while(0) + // Class mismatch logging (for hint validation) static inline void small_v7_log_class_mismatch(void* ptr, uint8_t hint, uint8_t actual) { // TODO: Make this ENV-controlled @@ -44,7 +67,7 @@ static inline void small_v7_log_class_mismatch(void* ptr, uint8_t hint, uint8_t // Alloc Fast Path // ============================================================================ -// small_heap_alloc_fast_v7() - v7 alloc (C6-only for v7-2) +// small_heap_alloc_fast_v7() - v7 alloc (C6-only, v7-5a: Hot path極限最適化) // // Flow: // 1. Get TLS context @@ -53,6 +76,10 @@ static inline void small_v7_log_class_mismatch(void* ptr, uint8_t hint, uint8_t // 4. If no partial, call ColdIface refill // 5. Pop from freelist and return USER ptr // +// v7-5a optimizations: +// - Per-page stats (alloc_count, live_current) removed from hot path +// - Global atomic stats gated by ENV (HAKMEM_V7_HOT_STATS) +// static inline void* small_heap_alloc_fast_v7(size_t size, uint8_t class_idx) { // v7-2: Only C6 is implemented if (unlikely(class_idx != SMALL_V7_C6_CLASS_IDX)) { @@ -69,17 +96,13 @@ static inline void* small_heap_alloc_fast_v7(size_t size, uint8_t class_idx) { p->free_list = *(void**)base; p->used++; - // Update stats - p->alloc_count++; - p->live_current++; - if (p->live_current > p->peak_live) { - p->peak_live = p->live_current; - } - // Write header (HEADER_MAGIC | class_idx) for front compatibility + // Note: Cannot move to carve time due to intrusive freelist overlapping block[0] ((uint8_t*)base)[0] = (uint8_t)(HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK)); - small_v7_stat_alloc(); + // v7-5a: Stats moved to cold path (ENV-gated only) + SMALL_V7_HOT_STAT_ALLOC(); + // Return USER ptr (base + 1 for header compatibility with front) return (uint8_t*)base + 1; } @@ -96,16 +119,10 @@ static inline void* small_heap_alloc_fast_v7(size_t size, uint8_t class_idx) { p->free_list = *(void**)base; p->used++; - p->alloc_count++; - p->live_current++; - if (p->live_current > p->peak_live) { - p->peak_live = p->live_current; - } - - // Write header (HEADER_MAGIC | class_idx) for front compatibility + // Write header ((uint8_t*)base)[0] = (uint8_t)(HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK)); - small_v7_stat_alloc(); + SMALL_V7_HOT_STAT_ALLOC(); return (uint8_t*)base + 1; } } @@ -124,16 +141,10 @@ static inline void* small_heap_alloc_fast_v7(size_t size, uint8_t class_idx) { p->free_list = *(void**)base; p->used++; - p->alloc_count++; - p->live_current++; - if (p->live_current > p->peak_live) { - p->peak_live = p->live_current; - } - - // Write header (HEADER_MAGIC | class_idx) for front compatibility + // Write header ((uint8_t*)base)[0] = (uint8_t)(HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK)); - small_v7_stat_alloc(); + SMALL_V7_HOT_STAT_ALLOC(); return (uint8_t*)base + 1; } @@ -141,12 +152,15 @@ static inline void* small_heap_alloc_fast_v7(size_t size, uint8_t class_idx) { // Free Fast Path // ============================================================================ -// small_heap_free_fast_v7() - v7 free (C6-only, Phase v7-3: TLS fast path) +// small_heap_free_fast_v7() - v7 free (C6-only, v7-5a: Hot path極限最適化) // // Flow: -// 1. Same-page cache hit (fastest) -// 2. TLS segment hint hit (skip RegionIdBox) -// 3. RegionIdBox fallback (cold path) +// 1. TLS segment hint hit (skip RegionIdBox) +// 2. RegionIdBox fallback (cold path) +// +// v7-5a optimizations: +// - Stats (free_count, live_current) removed from hot path +// - Global atomic stats gated by ENV // // @param ptr: USER pointer to free // @param class_idx_hint: Class index hint from front/header (may be ignored) @@ -190,9 +204,7 @@ static inline bool small_heap_free_fast_v7(void* ptr, uint8_t class_idx_hint) { *(void**)base = page->free_list; page->free_list = base; - // Update stats - page->free_count++; - page->live_current--; + // v7-5a: Stats removed from hot path // Retire if empty if (unlikely(--page->used == 0)) { @@ -200,7 +212,7 @@ static inline bool small_heap_free_fast_v7(void* ptr, uint8_t class_idx_hint) { small_cold_v7_retire_page(ctx, page); } - small_v7_stat_free(); + SMALL_V7_HOT_STAT_FREE(); return true; } @@ -243,9 +255,7 @@ regionid_fallback: *(void**)base = page->free_list; page->free_list = base; - // Update stats - page->free_count++; - page->live_current--; + // v7-5a: Stats removed from hot path // Decrement used count if (unlikely(--page->used == 0)) { @@ -253,7 +263,7 @@ regionid_fallback: small_cold_v7_retire_page(ctx, page); } - small_v7_stat_free(); + SMALL_V7_HOT_STAT_FREE(); return true; } } diff --git a/core/smallobject_cold_iface_v7.c b/core/smallobject_cold_iface_v7.c index 99f03104..dca80dad 100644 --- a/core/smallobject_cold_iface_v7.c +++ b/core/smallobject_cold_iface_v7.c @@ -1,8 +1,12 @@ -// smallobject_cold_iface_v7.c - SmallObject ColdIface v7 implementation (Phase v7-2) +// smallobject_cold_iface_v7.c - SmallObject ColdIface v7 implementation (Phase v7-5a) // // Purpose: // - Page refill: acquire page from segment, carve freelist // - Page retire: release empty page back to segment, publish stats +// +// v7-5a optimizations: +// - Header written at carve time (not on hot path alloc) +// - Stats collected at retire time (not on hot path) #include #include @@ -12,6 +16,7 @@ #include "box/smallobject_cold_iface_v7_box.h" #include "box/smallsegment_v7_box.h" #include "box/region_id_v6_box.h" +#include "tiny_region_id.h" // v7-5a: For HEADER_MAGIC, HEADER_CLASS_MASK #ifndef likely #define likely(x) __builtin_expect(!!(x), 1) @@ -154,6 +159,10 @@ SmallPageMeta_v7* small_cold_v7_refill_page(SmallHeapCtx_v7* ctx, uint32_t class // Build intrusive freelist (last to first for cache locality on pop) // freelist points to BASE pointers (block start) + // + // Note: Cannot write header at carve time because freelist next pointer + // is stored at block[0..7], which overlaps with header byte at block[0]. + // Header must be written at alloc time after popping from freelist. void* freelist = NULL; for (int i = (int)capacity - 1; i >= 0; i--) { uint8_t* block = base + ((size_t)i * block_size); diff --git a/docs/analysis/SMALLOBJECT_V7_DESIGN.md b/docs/analysis/SMALLOBJECT_V7_DESIGN.md index 33923a07..6a8074ce 100644 --- a/docs/analysis/SMALLOBJECT_V7_DESIGN.md +++ b/docs/analysis/SMALLOBJECT_V7_DESIGN.md @@ -456,7 +456,148 @@ v7-5 以降でも header を削除できるかの検証: ### 次世代開始チェックリスト -- [ ] HAKMEM_V2_GENERATION_SUMMARY.md が地図として機能 -- [ ] v7-4 時点の設計メモ (本セクション) が読み返せる -- [ ] HakORune / JoinIR が一段落(or 並行可能に) -- [ ] v7 research box は冷凍庫に保存完了 +- [ ] HAKMEM_V2_GENERATION_SUMMARY.md と本ドキュメントから、v2→v7 の層構造と責務が一貫して読み取れること +- [ ] v7-4 時点の設計メモ(本セクション)が「どこを伸ばすか/どこは ULTRA/MID に任せるか」の地図として機能していること +- [ ] v7 small/mid コアを C6 以外にも広げる際のクラス割り当て(C2〜C7)が `HAKMEM_V2_GENERATION_SUMMARY.md` と齟齬なく定義されていること + +--- + +## 9. HAKMEM v3(mimalloc に追いつく世代)のターゲット像 + +この節は、「いま以降の HAKMEM 開発は何を目指すか」をブレないようにするためのメモです。 + +### 9-1. 性能ターゲット(Mixed 16–1024B) + +- 評価プロファイル: + - `HAKMEM_PROFILE=MIXED_TINYV3_C7_SAFE` + - `ws=400`, `iters=1,000,000`, size range `16–1024B` +- 比較対象: + - mimalloc: 現状 ~ 110–120M ops/s + - HAKMEM v2 世代: ~ 40–45M ops/s(ULTRA + MID v3 ベース) +- v3/v7 世代の目標: + - **短期**: 50M ops/s 台(mimalloc の ~0.5×) + - **中期**: 60M ops/s 近辺(0.5〜0.6×)を安定して狙える設計 + +### 9-2. クラス別の役割(v3 世代の前提) + +`docs/analysis/HAKMEM_V2_GENERATION_SUMMARY.md` の整理を踏まえ、v3 世代では次の前提で進める: + +- C7 (769–1024B): + - L0 C7 ULTRA が本命。v7 small/mid コアからは除外し、ULTRA Box を前提に最適化を続ける。 +- C6 (513–768B): + - v7 SmallHeapCtx の本命クラス。 + - C6-only v7 を最初のターゲットとして、legacy/MID/v6 を相手に A/B しながらコアの形を固める。 +- C5 (257–512B): + - 現状は MID v3 + ULTRA でそこそこ速く、本線としては安定。 + - v7 への移行は C6 v7 が legacy/MID を上回るめどが立ってから、段階的に検討する。 +- C4 (129–256B): + - C4 ULTRA(寄生型)が効いている帯。v7 に乗せるかは慎重に検討。 + - 当面は ULTRA を前提とし、v7 small コアには無理に載せない。 +- C3–C0 (≤64B): + - Tiny legacy/Tiny front v3 のまま。当座は v7 で触らない。 + +この前提により、「どのクラスを v7 small/mid コアで本気で攻めるか」「どこは ULTRA/MID/legacy に任せるか」が明確になる。 + +### 9-3. v7 コアで必ず守るルール + +1. **Front は route するだけ** + - `size → class_idx → SmallPolicyV7.route_kind[class_idx] → switch` 以外の仕事(ENV 読み・Region lookup・Segment 操作)は Front に置かない。 +2. **HotBox は物理層に直接触らない** + - SmallObjectHotBox_v7 は `SmallSegment_v7` / `RegionIdBox` / `PageStatsBox` へのアクセスを ColdIface/API 経由に限定し、HotPath 内で ENV や Learner を直接参照しない。 +3. **Learning は snapshot 経由のみ** + - L3 Policy/Learner は Stats を読んで `SmallPolicyV7` や per-class パラメータ(tls_cap, partial_limit 等)を更新する。 + - L1/L0 は「いまの snapshot 値」を読むだけで、学習や ENV を意識しない。 + +### 9-4. v3 世代での「やってよい/やらない」境界 + +- やってよい(本筋): + - v7 small/mid コア(特に C6)の alloc/free/segment/RegionId 経路の設計・実装・A/B。 + - PolicyBox を使った route_kind の再配線(ULTRA/MID/v7/LEGACY のクラスごとの切り替え)。 + - RegionIdBox/Segment/PageStats の「共通物理層」としての整理と、small/mid/pool での再利用設計。 +- やらない(この世代では控える): + - ULTRA 世代の大改造(C7 ULTRA の構造変更など)。 + - MID v3 のリフトアップを目的とした過度な再実装(v7/mid コア側で吸収する)。 + - header を全クラス・全経路から完全に消し去るような大手術(これは v8 以降のテーマとして温存)。 + +この節を「v3 / v7 世代で何を目指すか」の固定観念として扱い、今後の設計・実装・A/B で迷子になったときの基準とする。 + +--- + +## 10. Phase v7-5a: C6 v7 極限最適化 (Hot Path Stats Removal) + +### 10-1. 目的 + +Phase v7-3 時点の -4.3% overhead を ±0% 以下に改善し、v7 を本線として使える状態にする。 + +### 10-2. v7-5a vs v7-5b の選択 + +| Option | 内容 | リスク | 期待効果 | +|--------|------|--------|----------| +| v7-5a | C6 micro-optimization (stats/header) | 低 | -4.3% → ±0% | +| v7-5b | Multi-class expansion (C5/C4) | 中 | overhead 分摊 | + +**採用**: v7-5a を優先。低リスクで効果を確認してから v7-5b に進む。 + +### 10-3. v7-5a の最適化内容 + +#### 実施した最適化 + +1. **Per-page stats 削除 (hot path から)**: + - `alloc_count++` / `free_count++` / `live_current++/--` を hot path から除去 + - Stats は retire 時に cold path で収集 + +2. **Global atomic stats の ENV-gating**: + - `HAKMEM_V7_HOT_STATS=1` で有効化(デフォルトOFF) + - Hot path での atomic counter を完全除去可能に + +3. **Header-at-carve-time 最適化 → 断念**: + - 試行: 「refill 時にヘッダを書き、alloc 時には書かない」 + - **失敗理由**: intrusive LIFO freelist が block[0..7] に next pointer を格納するため、 + carve 時にヘッダを書いても alloc pop 前に上書きされる + - **対応**: ヘッダは alloc 時に書く方式を維持 + +```c +// freelist 構造の制約 +// block[0]: header byte (1B) +// block[0..7]: freelist next pointer (8B) +// → 重複するため、carve 時にヘッダを書いても freelist が上書きする +``` + +### 10-4. A/B ベンチマーク結果 + +**ベンチ条件**: +- Range: 257-768B (C6-heavy) +- Benchmark: bench_mid_large_mt_hakmem +- 5 iterations each + +| v7 OFF (MID v3) | v7 ON (v7-5a) | +|-----------------|---------------| +| 9,370,124 | 9,251,082 | +| 9,698,882 | 8,782,087 | +| 8,862,704 | 9,331,487 | +| 9,108,786 | 9,502,876 | +| 9,250,129 | 9,490,638 | +| **Avg: 9.26M** | **Avg: 9.27M** | + +**結果: +0.15% (±0%, noise margin 内)** + +### 10-5. 評価 + +| 指標 | Before (v7-3) | After (v7-5a) | 目標 | +|------|---------------|---------------|------| +| Overhead | -4.3% | ±0% | legacy ±2% | +| Stats impact | 常時 ON | ENV-gated | - | +| Header overhead | あり | あり (維持) | - | + +**Phase v7-5a 達成**: hot path から per-page stats を除去し、overhead を ±0% まで改善。 + +### 10-6. 次のステップ候補 + +1. **v7-5b (Multi-class)**: C5/C4 へ v7 を拡張し、overhead をさらに分摊 +2. **Headerless mode**: header 完全削除を再検討(freelist 設計の変更が必要) +3. **Learner 連携**: Stats から動的 route 選択 + +--- + +**Document Updated**: 2025-12-12 +**Phase v7-5a Status**: COMPLETE