diff --git a/AGENTS.md b/AGENTS.md index 1037d3b9..9fa7fa28 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -43,6 +43,11 @@ - Remote Free Queue, Partial SS Adopt, TLS Bind/Unbind を独立した“箱”として定義 - 箱の API は最小・明確(init/publish/adopt/drain/bind など) +- 参照してほしい設計ドキュメント + - `HAKMEM_V2_GENERATION_SUMMARY.md`(ULTRA + MID v3 + v6/v7 研究箱までの“第1章”まとめ) + - `docs/analysis/SMALLOBJECT_V7_DESIGN.md`(SmallObject v7 / HAKMEM v3 コアの層構造と箱割り) + - `docs/analysis/V7_ARCHITECTURE_DECISION_MATRIX.md`(v7 世代の設計オプションと意思決定マトリクス) + - 境界は1箇所 - Superslab 再利用の境界は `superslab_refill()` に集約(publish/adopt の接点) - Free の境界は “same-thread / cross-thread” の判定1回 @@ -287,6 +292,27 @@ Do / Don’t(壊れやすいパターンの禁止) --- +## V7 世代の設計メモへの導線(mimalloc に追いつく箱を作るとき) + +v7 / HAKMEM v3 の設計・実装を触るときは、必ず次の設計ドキュメントから読み始めてください。 + +- `docs/analysis/HAKMEM_V2_GENERATION_SUMMARY.md` + - v2 世代(ULTRA + MID v3 + v6/v7 研究箱)のまとめと L0〜L3 の層構造。 + - 「ここまでが第1世代・ここから先を v3/v7 で攻める」という地図。 + +- `docs/analysis/SMALLOBJECT_V7_DESIGN.md` + - SmallObjectHotBox_v7 / SmallSegment_v7 / RegionIdBox の型と責務。 + - v7-0〜v7-4 のフェーズと、「v3 世代でどのクラスを誰が担当するか」の前提。 + +- `docs/analysis/V7_ARCHITECTURE_DECISION_MATRIX.md` + - mimalloc と v7 の比較表(Free List / Size Class / Lookup / Header 等)。 + - v7-5a(C6 極限最適化)と v7-5b(multi-class 拡張)どちらから攻めるかの判断基準。 + - Intrusive LIFO / headerless / Stats cold path など、次世代で検討する論点一覧。 + +これら 3 つを「設計の正」として扱い、v7 をいじるときは必ず A/B 用プロファイルと Box 境界を先に決めてからコードを触ってください。 + +--- + ## Tiny/ULTRA 層の「完成世代」宣言(2025-12-11) **Tiny/ULTRA 層は C4–C7 ULTRA + v3 backend を前提とした「完成世代」として扱う。** diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index bcdd1586..b13a4658 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -5,6 +5,188 @@ --- +## いまの本筋タスク(HAKMEM v3 / v7 向けメモ) + +- **HAKMEM v2 世代は「第1章の完成版」**(ULTRA + MID v3 + v6/v7 研究箱)。ここから先は v2 をベースラインにしつつ、v3/v7 でさらに攻めるフェーズ。 +- いま以降の「v3 / v7 本筋タスク」はこの 3 つだけ: + 1. **SmallObjectHotBox_v7 を small/mid 向けコアとして再設計する** + - 既存 C6-only v7 実装と `SMALLOBJECT_V7_DESIGN.md` を読み返し、small〜mid 全体を 1 個の SmallHeapCtx_v7 で見る設計を固める(ULTRA は L0 のまま維持)。 + 2. **PolicyBox v7 を全クラスに拡張する** + - いまは C6 v7 用 stub だけ Policy 経由。将来は C2〜C7 すべてを `route_kind[class]` で決めるようにし、`tiny_route_env_box.h` を段階的に縮退させる。 + 3. **RegionId / Segment / PageStats の共通「物理層」を small/mid/pool に展開する方針を決める** + - v6/v7 で作った RegionIdBox/SmallSegment/PageStats のパターンを、mid/pool v3 にどう再利用するかを設計ノートに落とす(実装は別フェーズ)。 +- それ以外の最適化(ULTRA の微調整や MID v3 / v7 の枝刈り)は「おまけタスク」として扱い、上の 3 つが揃うまでは **第2世代(v3 コア)の本筋を動かし過ぎない**。 + +--- + +## Phase V7-5a: C6 v7 極限最適化(Hot path stats 削除, 2025-12-12) + +### 目的 + +- C6-only SmallObjectHotBox_v7 の -4.3% overhead(Phase v7-3 時点)を、Hot path からの stats 更新削除だけで ±0% 付近まで戻す。 + +### 実施内容 + +- v7 C6 Hot path から per-page stats 更新を削除。 + - `alloc_count++ / free_count++ / live_current++/--` を ColdIface 経路(refill/retire 時)に移動。 + - Hot path での stats は `HAKMEM_V7_HOT_STATS=1` のときだけ ENV ゲートで有効(デフォルト OFF)。 +- Header-at-carve-time 完全移行は見送り。 + - freelist が block[0] を next pointer として使っており、「carve 時だけ header write」は v7 現行構造では安全にできないため、ヘッダは引き続き alloc 時に 1 byte 書く前提を維持。 + +### 結果(C6-heavy ベンチ) + +| Metric | v7 OFF (MID v3) | v7 ON (v7-5a) | +|---------|-----------------|---------------| +| Throughput (avg) | 9.26M ops/s | 9.27M ops/s | +| 差分 | baseline | +0.15% | + +- 目標だった `-4.3% → ±2%` を満たし、C6-only v7 は MID v3 とほぼ同等(±0%)まで改善。 +- v7 C6 コアは「性能的に MID v3 と張り合える研究箱」として、次フェーズ(multi-class 拡張 / headerless 再検討 / Learner 連携)の土台になった。 + +--- + +## Phase V7-5b: C5+C6 multi-class 拡張(2025-12-12) + +### 目的 + +- C6-only v7 で確保した ±0% 近辺の性能を維持したまま、C5 を v7 small/mid コアに載せて C5 帯の性能を底上げする。 + +### 実施内容 + +- SmallSegment_v7 / ColdIface_v7 / HotBox_v7 を C5+C6 対応に拡張。 + - `SMALL_V7_CLASS_SUPPORTED()` macro に C5 を追加。 + - `small_v7_block_size()` を C5/C6 の両クラスを扱う switch に拡張。 + - HotBox 側の alloc/free の class validation を C5+C6 両方に対応。 +- TLS 構造は C6 lane を維持しつつ、C5 については v7 small コアに乗せるが TLS bloat が発生しない形に留める(fast path の C6 を守る)。 + +### 結果(C6-heavy / C5+C6 v7 プロファイル) + +| Config | Avg Throughput | Delta | +|------------|----------------|----------| +| C6-only v7 | 7.64M ops/s | baseline | +| C5+C6 v7 | 7.97M ops/s | +4.3% | + +採用基準: +- C6 性能維持: ✅(C6-only v7 と比べて劣化なし) +- C5 net positive: ✅(+4.3%) +- TLS bloat: ✅ なし(C6 lane のキャッシュヒット率に悪影響なし) + +→ v7 small/mid コアは C5+C6 2 クラス対応でも破綻せず、C5 帯で +数% の改善が確認できた。次は C4 を v7 に載せるかどうかを慎重に評価するか、Learner 連携や Mixed 16–1024B での A/B に進むフェーズ。 + +--- + +## Phase V7-6 構想: Mixed A/B + Learner 連携設計 + +### 方針(v7-5c より先にやること) + +- C4 v7 拡張は、v4/v5 世代で TLS bloat を経験していることもあり、**C5+C6 v7 の成果を Mixed でちゃんと測ってから** 判断する。 +- そのために、次の 2 本を「v7-6 の本筋」として進める: + 1. Mixed 16–1024B で v7 OFF(MID v3 + ULTRA) vs v7 C5+C6 ON の A/B を取り、v7 の利益が本線 Mixed プロファイルでどのくらい出ているか確認する。 + 2. SmallPolicyV7 と Stats/Learner のインタフェースを設計し、「Stats → Learner → Policy.route_kind[] 更新」のデータフローを doc に落とす。 + +### 具体タスク(設計フェーズ) + +- Mixed A/B: + - プロファイル: `HAKMEM_PROFILE=MIXED_TINYV3_C7_SAFE` をベースに、C5+C6 v7 ON/OFF の 2 パターンで 16–1024B ベンチを実行。 + - 記録: ops/s, free/alloc 経路内訳(ULTRA / v7 / MID v3 / LEGACY)、C5/C6 の hit 率を docs/analysis 側に追記。 +- Learner 連携設計: + - SmallPolicyV7 に対する更新 API(例: `small_policy_v7_update_from_learner(...)`)のシグネチャと責務を決める。 + - SmallPageStatsV7(または既存 Stats Box)から Learner が読むべきフィールドと、どのタイミングで snapshot を差し替えるか(L3 のみ、L1/L0 は snapshot を読むだけ)を `SMALLOBJECT_V7_DESIGN.md` に追記。 + +このフェーズはコード変更よりも設計と A/B の整理がメインで、C4 v7 拡張や Intrusive LIFO といった大きな構造変更は、その結果を見てから判断する。 + +--- + +## Phase V7-6: Mixed A/B 結果と C5 ルート方針(2025-12-12) + +### Mixed / C5+C6 専用での A/B 結果 + +| Workload | C6-only v7 | C5+C6 v7 | 推奨設定 | +|----------------------|------------|----------|----------------| +| C5+C6 専用 (257–768B) | baseline | +4.3% | `CLASSES=0x60` | +| Mixed 16–1024B | +0.5% | -8.0% | `CLASSES=0x40` | + +- 専用 C5+C6 ワークロード(257–768B だけを回すベンチ)では、C5+C6 v7 で +4.3% の改善が出る。 +- 一方、Mixed 16–1024B では C5 まで v7 に載せると全体で -8% の回帰になり、C6-only v7 の方が安全。 + +### 結論: C5 の route は workload-dependent + +- C5 は「C5-heavy」なら v7 の方が得だが、「Mixed」では MID v3 + ULTRA の方がトータルで速い。 +- 固定の ENV だけで C5 の route を決めるのは持続可能ではなく、**Learner で動的に切り替えられるようにする**のが設計的に素直。 + +### Learner 連携の方向性(v7-7 以降の種) + +- データフロー: + - `SmallPageStatsV7`(ColdIface 在庫) + → 集約構造 `SmallLearnerStatsV7`(per-class alloc/free/remote 等) + → PolicyLearner(C5 帯が heavy かどうかを判定) + → `SmallPolicyV7.route_kind[C5]` を `SMALL_ROUTE_V7` / `SMALL_ROUTE_MID_V3` のどちらかに更新。 +- 最小構成: + - C5 だけを対象に、「C5 alloc 比率が一定閾値(例: 30%)を超えたら C5→v7、そうでなければ MID v3」に切り替えるシンプルなルールから始める。 + - L3 で snapshot を差し替え、L1/L0 は snapshot を読むだけ、という Box Theory を守る。 + +このフェーズの結果、「C5 は固定ルートではなく、Learner で選び分けるべきクラス」という位置づけがはっきりした。次の大きなタスクは、v7-7 でこの Learner 経路を一気通貫で通す設計・実装を進めるかどうかの判断になる。 + +--- + +## Phase V7-8: C5 Learner Mixed A/B(2025-12-11) + +### 目的 + +- C5 Learner 付き v7 が、C5/C6 専用ワークロードと Mixed 16–1040B でどう振る舞うかを A/B で確認し、「C5 を本線で v7 に載せるか」「Learner を研究箱に留めるか」を判断する。 + +### ベンチ結果(要約) + +- C5/C6 集中プロファイル(200–500B 帯) + - v7 OFF: 約 19M ops/s + - v7+Learner: 約 43M ops/s(+126%) + - Learner は C5-heavy と判定し、C5 を V7 route に維持。 + +- 全範囲 Mixed(16–1040B) + - v7 OFF: 約 27M ops/s + - v7+Learner: 約 25M ops/s(約 -7%) + - C5 比率 ≈ 28% < 閾値 30% となり、Learner は C5 route を `V7 → MID_V3` に切り替え。 + +### 所感 / 今の判断 + +- C5/C6 専用ワークロードでは v7+Learner が非常に強い(2.2×)ので、C5/C6 専用ベンチでは「C5+C6 v7 + Learner ON」を研究用プリセットとして維持する価値がある。 +- 一方、全範囲 Mixed では route 切り替え後でも約 -7% のままで、現時点では `MIXED_TINYV3_C7_SAFE` 本線プロファイルに v7+Learner を常時 ON するのは見送り。 +- 当面の本線: + - Mixed: ULTRA + MID v3 基準(v7 は C6-only を含めて OFF、C5 Learner も OFF)。 + - C5/C6 専用プロファイル: `HAKMEM_SMALL_HEAP_V7_ENABLED=1, HAKMEM_SMALL_HEAP_V7_CLASSES=0x60` + Learner ON を研究箱として opt-in。 + + +--- + +## Phase V7-7: C5 Learner 実装(動的 route 切り替え) + +### 実装内容 + +- SmallLearnerStatsV7 型と API を追加(`smallobject_policy_v7_box.h`): + - `SmallLearnerClassStatsV7`: per-class の `v7_allocs`, `v7_retires`, `sample_count` 等。 + - `SmallLearnerStatsV7`: 上記の per-class 配列。 + - API 群: `record_refill()`, `record_retire()`, `evaluate()`, `stats_snapshot()` など、L3 から呼ぶインタフェース。 +- ColdIface_v7 に stats hook を追加(`smallobject_cold_iface_v7.c`): + - `refill_page()` で `record_refill(class_idx, capacity)` を呼び、v7 refill イベントを集計。 + - `retire_page()` で `record_retire(class_idx, capacity)` を呼び、v7 retire イベントを集計。 +- PolicyBox v7 から C5 route を動的に切り替え(`smallobject_policy_v7.c`): + - C5 の v7 利用比率(C5 v7 alloc / 全体)を Learner から受け取り、閾値で判定。 + - 閾値: C5 ratio < 30% のとき C5 を `SMALL_ROUTE_MID_V3` へ切り替え、それ以外は `SMALL_ROUTE_V7` のまま。 + - 評価間隔: v7 refill 100 回ごとに `evaluate()` を実行。 + - route_kind 更新は version ベースで行い、TLS 側のキャッシュと整合を取る。 + +### 挙動確認 + +- C5+C6 専用シナリオ(50/50 混在): + - C5 ratio ≈ 50% → C5 は v7 維持(期待どおり)。 +- C6-heavy シナリオ(C6 90%, C5 10% 程度): + - C5 ratio ≈ 12% → Learner が C5 route を `V7 → MID_V3` に切り替え。 + - ログ例: `[LEARNER_V7] C5 route switch: V7 → MID_V3 (C5 ratio=12%, threshold=30%)` + +このフェーズで、「C5-heavy なら v7、小さい C5 混入なら MID v3」という切り替えが、自動・一気通貫の経路として通電した。今後はこの Learner を Mixed 16–1024B 本線プロファイルでどこまで活かせるか、A/B とチューニングで詰めていくフェーズに入る。 + +--- + ## Phase ULTRA 総括(2025-12-11) ### Tiny/ULTRA 層は「完成世代」として固定化 diff --git a/core/box/smallobject_hotbox_v3_env_box.h b/core/box/smallobject_hotbox_v3_env_box.h index cf9d8809..e13037ab 100644 --- a/core/box/smallobject_hotbox_v3_env_box.h +++ b/core/box/smallobject_hotbox_v3_env_box.h @@ -10,42 +10,18 @@ #include "../hakmem_tiny_config.h" +// v10: v3 deprecated - always disabled, routes to LEGACY 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; + return 0; } static inline uint32_t small_heap_v3_class_mask(void) { - 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; - } - return (uint32_t)g_mask; + return 0; // No classes routed to v3 } static inline int small_heap_v3_class_enabled(uint8_t class_idx) { - if (!small_heap_v3_enabled()) return 0; - if (class_idx >= TINY_NUM_CLASSES) return 0; - uint32_t mask = small_heap_v3_class_mask(); - return (mask & (1u << class_idx)) != 0; + (void)class_idx; // unused + return 0; // v3 disabled in Phase v10 } static inline int small_heap_v3_c7_enabled(void) { diff --git a/core/box/smallobject_hotbox_v4_env_box.h b/core/box/smallobject_hotbox_v4_env_box.h index b347ba65..4bee7d39 100644 --- a/core/box/smallobject_hotbox_v4_env_box.h +++ b/core/box/smallobject_hotbox_v4_env_box.h @@ -7,37 +7,14 @@ #include "../hakmem_tiny_config.h" +// v10: v4 deprecated - always disabled, routes to LEGACY static inline int small_heap_v4_enabled(void) { - static int g_enable = -1; - if (__builtin_expect(g_enable == -1, 0)) { - const char* e = getenv("HAKMEM_SMALL_HEAP_V4_ENABLED"); - if (e && *e) { - g_enable = (*e != '0') ? 1 : 0; - } else { - // v4 は研究箱。明示しない限り OFF - g_enable = 0; - } - } - return g_enable; + return 0; } static inline int small_heap_v4_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_V4_CLASSES"); - if (e && *e) { - unsigned v = (unsigned)strtoul(e, NULL, 0); - g_mask = v & 0xFFu; - } else { - // デフォルトは全クラス OFF - g_mask = 0; - } - g_parsed = 1; - } - if (!small_heap_v4_enabled()) return 0; - if (class_idx >= TINY_NUM_CLASSES) return 0; - return (g_mask & (1u << class_idx)) != 0; + (void)class_idx; // unused + return 0; // v4 disabled in Phase v10 } static inline int small_heap_v4_c7_enabled(void) { diff --git a/core/box/smallobject_v5_env_box.h b/core/box/smallobject_v5_env_box.h index 8f56fb22..e2f308d1 100644 --- a/core/box/smallobject_v5_env_box.h +++ b/core/box/smallobject_v5_env_box.h @@ -21,36 +21,18 @@ enum small_route_priority { ROUTE_PRIORITY_AUTO = 2, }; -// small_heap_v5_enabled() - グローバル v5 enable check +// v10: v5 deprecated - always disabled, routes to LEGACY static inline int small_heap_v5_enabled(void) { - static int g_enabled = ENV_UNINIT; - if (__builtin_expect(g_enabled == ENV_UNINIT, 0)) { - const char* e = getenv("HAKMEM_SMALL_HEAP_V5_ENABLED"); - g_enabled = (e && *e && *e != '0') ? ENV_ENABLED : ENV_DISABLED; - } - return (g_enabled == ENV_ENABLED); + return 0; } -// small_heap_v5_class_mask() - v5 対象クラスのビットマスク static inline uint32_t small_heap_v5_class_mask(void) { - static int g_mask = ENV_UNINIT; // sentinel - if (__builtin_expect(g_mask == ENV_UNINIT, 0)) { - const char* e = getenv("HAKMEM_SMALL_HEAP_V5_CLASSES"); - if (e && *e) { - g_mask = (int)strtoul(e, NULL, 0); - } else { - g_mask = 0x0; // default: OFF - } - } - return (uint32_t)g_mask; + return 0; // No classes routed to v5 } -// small_heap_v5_class_enabled() - 指定クラスが v5 有効か static inline int small_heap_v5_class_enabled(uint32_t class_idx) { - if (class_idx >= 8) return 0; - if (!small_heap_v5_enabled()) return 0; - uint32_t mask = small_heap_v5_class_mask(); - return (mask & (1u << class_idx)) ? 1 : 0; + (void)class_idx; // unused + return 0; // v5 disabled in Phase v10 } // 便利関数 diff --git a/core/front/malloc_tiny_fast.h b/core/front/malloc_tiny_fast.h index 11338189..72a8e71a 100644 --- a/core/front/malloc_tiny_fast.h +++ b/core/front/malloc_tiny_fast.h @@ -161,10 +161,10 @@ static inline void* malloc_tiny_fast(size_t size) { 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_SMALL_HEAP_V4 && route != TINY_ROUTE_SMALL_HEAP_V5 && + route != TINY_ROUTE_HOTHEAP_V2 && route != TINY_ROUTE_SMALL_HEAP_V6 && route != TINY_ROUTE_SMALL_HEAP_V7) { // Phase ALLOC-GATE-OPT-1: カウンタ散布 (3. route_for_class 呼び出し) + // Note: v3/v4/v5 removed in Phase v10 ALLOC_GATE_STAT_INC(route_for_class_calls); route = tiny_route_for_class((uint8_t)class_idx); } @@ -256,34 +256,7 @@ static inline void* malloc_tiny_fast(size_t size) { } __attribute__((fallthrough)); } - 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_SMALL_HEAP_V4: { - void* v4p = small_heap_alloc_fast_v4(small_heap_ctx_v4_get(), class_idx); - if (TINY_HOT_LIKELY(v4p != NULL)) { - return v4p; - } - so_v3_record_alloc_fallback((uint8_t)class_idx); - // fallthrough to v5/v2/v1 - __attribute__((fallthrough)); - } - case TINY_ROUTE_SMALL_HEAP_V5: { - // Phase v5-1: C6-only route stub (v1/pool fallback) - SmallHeapCtxV5* ctx = small_heap_ctx_v5(); - void* v5p = small_alloc_fast_v5(size, (uint32_t)class_idx, ctx); - if (TINY_HOT_LIKELY(v5p != NULL)) { - return v5p; - } - // fallthrough to v2/v1 - __attribute__((fallthrough)); - } + // Phase v10: v3/v4/v5 removed - routes now handled as LEGACY case TINY_ROUTE_HOTHEAP_V2: { void* v2p = tiny_hotheap_v2_alloc((uint8_t)class_idx); if (TINY_HOT_LIKELY(v2p != NULL)) { @@ -525,25 +498,7 @@ static inline int free_tiny_fast(void* ptr) { } break; // fallthrough to legacy } - case TINY_ROUTE_SMALL_HEAP_V5: { - // Phase v5-2: C6-only full implementation - // Phase FREE-LEGACY-BREAKDOWN-1: v5 は研究箱なので skip - SmallHeapCtxV5* ctx = small_heap_ctx_v5(); - small_free_fast_v5(base, (uint32_t)class_idx, ctx); - return 1; - } - case TINY_ROUTE_SMALL_HEAP_V4: - if (class_idx == 7 || class_idx == 6 || class_idx == 5) { - small_heap_free_fast_v4(small_heap_ctx_v4_get(), class_idx, base); - // Phase FREE-LEGACY-BREAKDOWN-1: v4 は研究箱なので skip - return 1; - } - break; // fallthrough to default - case TINY_ROUTE_SMALL_HEAP_V3: - so_free((uint32_t)class_idx, base); - // Phase FREE-LEGACY-BREAKDOWN-1: カウンタ散布 (8. v3 route) - FREE_PATH_STAT_INC(smallheap_v3_fast); - return 1; + // Phase v10: v3/v4/v5 removed - routes now handled as LEGACY case TINY_ROUTE_HOTHEAP_V2: tiny_hotheap_v2_free((uint8_t)class_idx, base, meta); // Phase FREE-LEGACY-BREAKDOWN-1: カウンタ散布 (v2 は tiny_heap_v1 にカウント) @@ -570,9 +525,8 @@ static inline int free_tiny_fast(void* ptr) { // fallback: lookup failed but TinyHeap front is ON → use generic TinyHeap free if (route == TINY_ROUTE_HOTHEAP_V2) { tiny_hotheap_v2_record_free_fallback((uint8_t)class_idx); - } else if (route == TINY_ROUTE_SMALL_HEAP_V3 || route == TINY_ROUTE_SMALL_HEAP_V4) { - so_v3_record_free_fallback((uint8_t)class_idx); } + // Phase v10: v3/v4 removed - no special fallback tiny_heap_free_class_fast(tiny_heap_ctx_for_thread(), class_idx, ptr); return 1; } diff --git a/docs/analysis/HAKMEM_V2_GENERATION_SUMMARY.md b/docs/analysis/HAKMEM_V2_GENERATION_SUMMARY.md index 35cee665..979dacca 100644 --- a/docs/analysis/HAKMEM_V2_GENERATION_SUMMARY.md +++ b/docs/analysis/HAKMEM_V2_GENERATION_SUMMARY.md @@ -147,7 +147,7 @@ L3: Policy Box + Stats + Learner (将来) - TLS cache はレイヤーごとに独立 - Future: Learner で L3 ← stats をフィードバック -## 6. 次世代 v7 への橋渡し(設計メモ) +## 6. 次世代 v7 / v3 への橋渡し(設計メモ) ### v7 第2章の候補 @@ -196,26 +196,19 @@ L3: Policy Box + Stats + Learner (将来) ### 次世代開始の条件 - [ ] v7 が ±0% overhead 達成(multi-class 拡張) -- [ ] または HakORune / JoinIR 一段落後に再開 -- [ ] v2 設計ドキュメントが凍結状態で読み返せること +- [ ] v3 / v7 コアの SPEC がこの総括+各 DESIGN ドキュメントから一貫して読み取れること -## 7. 凍結方針 +## 7. v2 世代の位置づけ -v2 世代はここで一旦完成とする。 +v2 世代はここで一旦「第1章の完成版」とする。 -**凍結対象**: -- ULTRA (C4-C7): 改造禁止(FROZEN) -- MID v3: バグ修正のみ(拡張禁止) -- v7 (C6-only): 設計確定、性能改善研究中(code freeze) -- Policy Box: route 決定一元化完了 +**扱い方**: +- ULTRA (C4-C7): 大きな構造変更はせず、第2世代でも L0 の完成箱として前提にする。 +- MID v3: 257–768B 帯の本線箱として維持しつつ、将来 v7/mid コアに統合する際の比較対象・退避先として使う。 +- v7 (C6-only): 設計・実装パターンは確立済みの研究箱として残し、第2世代の v7/mid コアを作るときの参照実装にする。 +- Policy Box: route 決定の一元化パターンとして、そのまま v3 世代でも使い続ける。 -**HakORune / JoinIR優先**: -- HAKMEM は凍結 -- 次世代 v7 再開は JoinIR ノーマライズ一段落後 - -**再開条件**: -- この総括ドキュメントが地図として機能すること -- v7-4 の設計メモ (6-1/6-2/6-3) が実装ガイド足り得ること +この総括は「ここまでが第1世代で、ここから先は v3 / v7 世代でさらに芯を削る」というための地図として扱う。 ## 8. 謝辞・振り返り diff --git a/docs/analysis/SMALLOBJECT_V7_DESIGN.md b/docs/analysis/SMALLOBJECT_V7_DESIGN.md index 5de59d97..62607033 100644 --- a/docs/analysis/SMALLOBJECT_V7_DESIGN.md +++ b/docs/analysis/SMALLOBJECT_V7_DESIGN.md @@ -523,6 +523,69 @@ v7-5 以降でも header を削除できるかの検証: --- +## 10. C5 Learner 連携の最小設計(v7-7 用メモ) + +v7-5b/v7-6 の結果から、C5 の route は workload-dependent(C5-heavy では v7 が有利だが、Mixed では MID v3+ULTRA が有利)であることが分かった。 +そのため、C5 の route_kind は固定ではなく Learner で動的に切り替えられるように設計する。 + +### 10-1. データフロー: Stats → Learner → Policy + +1. ColdIface_v7 / PageStatsBox: + - C5/C6 v7 管理ページの retire/refill 時に `SmallPageStatsV7` を生成し、L3 に publish する。 + - 必要なフィールド(例): + - `class_idx` + - `alloc_count`, `free_count` + - `remote_free_count` + - `live_at_retire`, `peak_live`(optional) + +2. Learner 集約構造: + ```c + typedef struct SmallLearnerClassStatsV7 { + uint64_t total_allocs; + uint64_t total_frees; + uint64_t total_remote_frees; + } SmallLearnerClassStatsV7; + + typedef struct SmallLearnerStatsV7 { + SmallLearnerClassStatsV7 per_class[8]; // C0–C7 + } SmallLearnerStatsV7; + ``` + +3. Policy 更新 API: + ```c + void small_policy_v7_update_from_learner( + const SmallLearnerStatsV7* stats, + SmallPolicyV7* policy_out); + ``` + + - L3 の Learner が一定間隔(例: N ページ retire / N ミリ秒ごと)で呼び出し、C5 の route_kind を `SMALL_ROUTE_V7` / `SMALL_ROUTE_MID_V3` のどちらかに切り替える。 + - L1/L0(ULTRA / v7 / MID v3)は snapshot を読むだけで、Learner の存在を意識しない。 + +### 10-2. C5 route 切り替えの簡易ルール案 + +- 入力: `SmallLearnerStatsV7.per_class[5].total_allocs` と全クラス合計 alloc 数。 +- ルール例: + - `C5_share = total_allocs[C5] / total_allocs_all` + - もし `C5_share >= 0.3` なら: + - `route_kind[C5] = SMALL_ROUTE_V7;` // C5-heavy → v7 small/mid コア + - それ以外なら: + - `route_kind[C5] = SMALL_ROUTE_MID_V3;` // Mixed 寄り → MID v3 + ULTRA に任せる + +このルールは最初の近似であり、後続フェーズでしきい値やヒステリシス(しばらく動作を維持するクールダウン)を追加してもよい。 + +### 10-3. Box Theory 上の位置づけ + +- Learner / Policy 更新処理は **L3 Box** に閉じ込める。 + - StatsBox / Learner / PolicyBox は L3。 + - L2 以下(Segment / RegionId / HotBox / ULTRA)は snapshot を読むだけ。 +- C5 の route_kind 更新は snapshot 差し替え経由でのみ行う。 + - 直接 front の分岐を書き換えず、常に `SmallPolicyV7` から読む。 +- これにより: + - Hot path は `route_kind` の読み取りだけで終わり、 + - 「いつ C5 を v7 にするか?」という意思決定は上層(L3)に閉じ込められる。 + +--- + ## 10. Phase v7-5a: C6 v7 極限最適化 (Hot Path Stats Removal) ### 10-1. 目的 diff --git a/docs/analysis/V7_ARCHITECTURE_DECISION_MATRIX.md b/docs/analysis/V7_ARCHITECTURE_DECISION_MATRIX.md index 9c8df60b..7eb2a62f 100644 --- a/docs/analysis/V7_ARCHITECTURE_DECISION_MATRIX.md +++ b/docs/analysis/V7_ARCHITECTURE_DECISION_MATRIX.md @@ -78,7 +78,7 @@ return (uint8_t*)base + 1; // 2 ns: USER ptr ## 2. v7-5 方向性の決定基準 -### 2-1. v7-5a vs v7-5b 比較表 +### 2-1. v7-5a vs v7-5b 比較表(実績付き) | 項目 | v7-5a (C6 極限最適化) | v7-5b (Multi-class 拡張) | |------|----------------------|-------------------------| @@ -101,7 +101,7 @@ return (uint8_t*)base + 1; // 2 ns: USER ptr 2. C5/C4 の allocation が実 workload で significant 3. overhead 分摊の理論値 (-4.3% → -2%) が魅力的 -### 2-3. 推奨: **v7-5a を先に試す** +### 2-3. 実績: v7-5a 完了(C6 v7 Hot path stats 削除) **理由**: 1. **低コスト**: 1-2 日で結果が出る @@ -109,17 +109,72 @@ return (uint8_t*)base + 1; // 2 ns: USER ptr 3. **失敗しても学び**: 限界が分かれば v7-5b へ移行 4. **成功すれば**: C6 で mimalloc 同等 → 強力な武器 -**v7-5a タスク**: -``` -1. Header write を条件付きにする (ENV: HAKMEM_V7_HEADERLESS=1) -2. small_v7_stat_alloc() を #ifdef HAKMEM_V7_STATS で囲む -3. p->used++ を ColdIface retire 時に計算 -4. A/B bench: C6-heavy (400-510B) で v7 ON/OFF -``` +**実装内容 (Phase v7-5a, 完了済み)**: +1. C6 v7 Hot path から per-page stats 更新を削除。 + - `alloc_count++ / free_count++ / live_current++/--` を ColdIface 経路(refill/retire 時)に移動。 + - Hot path での stats は `HAKMEM_V7_HOT_STATS=1` のときだけ ENV ゲートで有効(デフォルト OFF)。 +2. Header-at-carve-time の完全移行は見送り。 + - freelist が block[0] を next pointer として使っており、「carve 時だけ header write」は v7 現行構造では安全にできないため、ヘッダは引き続き alloc 時に 1 byte 書く前提を維持。 -**成功基準**: -- C6-heavy: v7 ON が legacy ±2% 以内 -- Mixed: v7 ON が legacy と同等以上 +**A/B 結果 (C6-heavy)**: + +| Metric | v7 OFF (MID v3) | v7 ON (v7-5a) | +|---------|-----------------|---------------| +| Throughput (avg) | 9.26M ops/s | 9.27M ops/s | +| Delta | baseline | +0.15% | + +→ 目標だった `-4.3% → ±2%` を達成し、C6-only v7 は MID v3 とほぼ同等(±0%)になった。 + +**次の判断軸**: +- v7-5b(Multi-class 拡張)に進むかどうかは、「C5/C4 を v7 に載せて overhead を分摊する価値があるか」の評価次第。 +- Intrusive LIFO / headerless / Stats cold path などは v7-5a の結果を踏まえて、別フェーズで改めて検討する。 + +### 2-4. 実績: v7-5b 完了(C5+C6 multi-class 拡張) + +**実装内容 (Phase v7-5b, 完了済み)**: +- SmallSegment_v7 / SmallObjectColdIface_v7 / SmallObjectHotBox_v7 を C5+C6 の 2 クラス対応に拡張。 + - `SMALL_V7_CLASS_SUPPORTED()` に C5 を追加。 + - `small_v7_block_size()` を C5/C6 両クラスを扱う switch に拡張。 + - HotBox 側の alloc/free class validation を C5+C6 対応に更新。 +- TLS 構造は C6 lane を維持しつつ、C5 については v7 small コアに乗せるが、C6 TLS のキャッシュ線を肥大化させない形をキープ。 + +**A/B 結果 (C5+C6 v7 プロファイル)**: + +| Config | Avg Throughput | Delta | +|------------|----------------|----------| +| C6-only v7 | 7.64M ops/s | baseline | +| C5+C6 v7 | 7.97M ops/s | +4.3% | + +- C6 性能維持: ✅(C6-only v7 と比べて劣化なし) +- C5 net positive: ✅(+4.3%) +- TLS bloat: ✅ なし(C6 TLS lane のヒット率に悪影響なし) + +**今後の方向性**: +- C4 は現在 ULTRA 本線で十分速いため、v7 に載せるかどうかは別途慎重に判断(Phase v7-5c 候補)。 +- v7 small/mid コアは C5+C6 2 クラス対応でも破綻しないことが分かったので、次は Mixed 16–1024B の A/B や Learner 連携(動的 route 選択)の設計・検証に進む。 + +--- + +## 3. C5 ルート選択と Learner 連携の必要性 + +Mixed / C5+C6 専用での A/B から、C5 v7 の利得が workload-dependent であることが判明した: + +| Workload | C6-only v7 | C5+C6 v7 | 推奨設定 | +|----------------------|------------|----------|----------------| +| C5+C6 専用 (257–768B) | baseline | +4.3% | `CLASSES=0x60` | +| Mixed 16–1024B | +0.5% | -8.0% | `CLASSES=0x40` | + +結論: +- C5 は C5-heavy な専用 workload では v7 が有利だが、一般的な Mixed では MID v3+ULTRA の方が全体として速い。 +- 「ENV で C5 を常に v7 にする / 常に MID v3 にする」ような固定方針は現実的でなく、**Learner で C5 の route を動的に切り替える設計**が必要。 + +設計メモ(v7-7 以降): +- Stats → Learner → Policy route_kind 更新の最小パス: + - `SmallPageStatsV7`(ColdIface で retire 時に生成) + → `SmallLearnerStatsV7`(per-class 累積 alloc/free/remote) + → PolicyLearner(C5 alloc 比率が閾値を超えているか判定) + → `SmallPolicyV7.route_kind[C5]` を `SMALL_ROUTE_V7` or `SMALL_ROUTE_MID_V3` に更新。 +- 最初の版では C5 だけを対象とし、他クラス(C4/C6 など)の動的ルート選択は後続フェーズで検討する。 ---