# HAKMEM Tiny Pool 再設計実装チェックリスト **作成日**: 2025-11-28 **目的**: ChatGPT 設計レビューに基づく段階的な再設計の実装計画 --- ## 目次 1. [背景と設計方針](#背景と設計方針) 2. [P0: 守りの修正(即座)](#p0-守りの修正即座) 3. [P1: 根本設計の一部導入(短期)](#p1-根本設計の一部導入短期) 4. [P2: TLS SLL 再設計(中期)](#p2-tls-sll-再設計中期) 5. [P3: Header 完全 Out-of-band(長期)](#p3-header-完全-out-of-band長期) 6. [依存関係図](#依存関係図) 7. [工数サマリー](#工数サマリー) 8. [テスト計画](#テスト計画) 9. [リスクと軽減策](#リスクと軽減策) --- ## 背景と設計方針 ### 核心の問題 現在の HAKMEM Tiny Pool には以下の構造的問題があります: 1. **In-band header による複雑性** - TLS SLL の next ポインタと header が絡まる - `meta->used` と TLS SLL の同期不整合 - クラス 0/7 と 1-6 で異なる `next_offset` (0 vs 1) 2. **TLS SLL と meta->used の乖離** - Fast free path では `meta->used` が更新されない - Periodic drain (2048 frees) まで slab が "full" に見える - Slab 再利用時に TLS SLL がクリアされない 3. **業界標準との乖離** - jemalloc, tcmalloc, mimalloc は **out-of-band header** を採用 - 彼らは「ポインタ箱」と「カウント箱」を完全分離 ### 解決策(段階的アプローチ) **P0(即座)**: 最もリスクの高い箇所を防御的に修正 **P1(短期)**: Out-of-band header の基盤を準備 **P2(中期)**: TLS SLL を「箱」モデルに再設計 **P3(長期)**: Header を完全に out-of-band 化 --- ## P0: 守りの修正(即座) **目標**: 現在のバグ・不整合を最小変更で修正し、安定性を確保する **所要時間**: 2-3日 **コミット戦略**: 各タスクを独立したコミットとして実施 ### P0.1: C0(8B) を 16B に昇格 **目的**: C0 の header/next ポインタの複雑性を排除し、「16B から開始する素直なサイズクラス」に揃える #### 変更ファイル - [ ] `/mnt/workdisk/public_share/hakmem/core/hakmem_tiny_config_box.inc` (L17-27) #### 変更内容 ```c // BEFORE: const size_t g_tiny_class_sizes[TINY_NUM_CLASSES] = { 8, // Class 0: 8B total = [Header 1B][Data 7B] 16, // Class 1: 16B total = [Header 1B][Data 15B] 32, 64, 128, 256, 512, 1024, 2048, }; // AFTER: const size_t g_tiny_class_sizes[TINY_NUM_CLASSES] = { 16, // Class 0: 16B total = [Header 1B][Data 15B] (旧8B→16Bに昇格) 32, // Class 1: 32B total 64, // Class 2: 64B total 128, // Class 3: 128B total 256, // Class 4: 256B total 512, // Class 5: 512B total 1024, // Class 6: 1KB total 2048, // Class 7: 2KB total }; ``` #### 追加変更 - [ ] `/mnt/workdisk/public_share/hakmem/core/hakmem_tiny.h` (L106-117) - `g_size_to_class_lut_2k` を更新: `1..16 → class 0`, `17..32 → class 1`, `33..64 → class 2` ... に統一 - [ ] `/mnt/workdisk/public_share/hakmem/core/hakmem_tiny_superslab.h` (L52) - `class_sizes[8]` を更新: `{16, 32, 64, 128, 256, 512, 1024, 2048}` - [ ] `/mnt/workdisk/public_share/hakmem/core/hakmem_tiny.h` (L132-141) - `g_tiny_blocks_per_slab` を更新: C0 も 4096 blocks に #### テスト方法 1. ビルド確認 ```bash make clean && make hakmem.so ``` 2. サイズルックアップテスト ```bash # 1-16 byte → class 0 # 17-32 byte → class 1 HAKMEM_TINY_SLL_DIAG=1 ./bench/bench_tiny 16 ``` 3. Larson ベンチマーク(double-free チェック) ```bash HAKMEM_TINY_SLL_DIAG=1 ./bench/larson 2 10000 8 1000 1 ``` #### 完了条件 - [ ] C0 が 16B に昇格され、サイズクラスが `{16,32,64,128,256,512,1024,2048}` に整列している - [ ] 1-16 byte の割り当てが class 0、17-32 byte の割り当てが class 1 にマップされる - [ ] Larson ベンチマークが正常終了(double-free なし) - [ ] 性能劣化なし(throughput ±5% 以内) #### 工数 **小(2-3時間)** --- ### P0.2: tiny_next_off を全クラス 1 に統一 **目的**: C0/C7 の特殊ケースを排除し、header 管理を統一 #### 変更ファイル - [ ] `/mnt/workdisk/public_share/hakmem/core/tiny_nextptr.h` (L47-59) #### 変更内容 ```c // BEFORE: static inline size_t tiny_next_off(int class_idx) { #if HAKMEM_TINY_HEADER_CLASSIDX // C0, C7 → offset 0 (header 潰す) // C1-C6 → offset 1 (header 保持) return (class_idx == 0 || class_idx == 7) ? 0u : 1u; #else return 0u; #endif } // AFTER: static inline size_t tiny_next_off(int class_idx) { #if HAKMEM_TINY_HEADER_CLASSIDX // 全クラス → offset 1 (header 常に保持) // NOTE: P0.1 で C0 を 16B に昇格済みのため、C0 でも offset 1 が使用可能 (void)class_idx; return 1u; #else (void)class_idx; return 0u; #endif } ``` #### 追加変更 - [ ] `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_drain_box.h` (L185-189) - Header 復元ロジックを簡素化(C0/C7 特殊ケース削除) ```c // BEFORE: if (class_idx != 0 && class_idx != 7) { *(uint8_t*)base = HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK); } // AFTER: *(uint8_t*)base = HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK); ``` - [ ] `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_box.h` (L340-400) - Safe header モードの条件分岐を削除 #### ドキュメント更新 - [ ] `/mnt/workdisk/public_share/hakmem/core/tiny_nextptr.h` (L3-26) - コメントを更新: C0/C7 特殊ケースの記述を削除 #### テスト方法 1. 全クラスで next ポインタのアライメントを確認 ```bash HAKMEM_TINY_SLL_DIAG=1 ./bench/bench_tiny_all_classes ``` 2. C7 (2048B) の TLS SLL 動作確認 ```bash HAKMEM_TINY_SLL_DIAG=1 ./bench/bench_tiny 2048 ``` 3. Larson マルチスレッド ```bash HAKMEM_TINY_SLL_DIAG=1 ./bench/larson 4 10000 8 1000 1 ``` #### 完了条件 - [ ] `tiny_next_off()` がすべてのクラスで `1` を返す - [ ] C7 で user data corruption が発生しない - [ ] TLS SLL の header 復元ロジックが統一される - [ ] すべてのベンチマークが正常終了 #### 工数 **小(2-3時間)** --- ### P0.3: Slab 再利用時の TLS SLL Drain ガード **目的**: Slab が再初期化される前に、TLS SLL を強制的に drain する #### 背景 現在の問題: 1. Slab が `meta->used == 0` で EMPTY と判定される 2. しかし TLS SLL には古いポインタが残っている 3. Slab が再初期化されて別クラスで再利用される 4. 古いポインタが再度 allocate されて double-free が発生 #### 変更ファイル - [ ] `/mnt/workdisk/public_share/hakmem/core/hakmem_tiny_tls_ops.h`(または `hakmem_tiny.c` 内の TLS Box) - [ ] `/mnt/workdisk/public_share/hakmem/core/hakmem_shared_pool.c` (L700-1300) #### 変更内容 ##### Stage 1: Tiny 側に「Slab 再利用ガード Box」を追加 ```c // hakmem_tiny_tls_ops.h もしくは対応する実装ファイル側: // ENV: HAKMEM_TINY_SLAB_REUSE_GUARD=1/0 (default: 1) static inline int slab_reuse_guard_enabled(void) { static int g_guard = -1; if (__builtin_expect(g_guard == -1, 0)) { const char* env = getenv("HAKMEM_TINY_SLAB_REUSE_GUARD"); g_guard = (env && *env == '0') ? 0 : 1; // デフォルト ON fprintf(stderr, "[SLAB_REUSE_GUARD] %s\n", g_guard ? "ENABLED" : "DISABLED"); } return g_guard; } // Box: EMPTY slab を再利用する直前に、該当 SuperSlab に紐づく TLS SLL を Drain する // NOTE: 実装詳細(どのクラス/スレッドを対象にするか)は Tiny Box 内で完結させる。 void tiny_tls_slab_reuse_guard(SuperSlab* ss) { if (!slab_reuse_guard_enabled()) return; g_slab_reuse_guard_calls++; // 例: 現スレッドの TLS SLL を走査し、ss に属するブロックだけ drain する(Box 内部ロジック) for (int c = 0; c < TINY_NUM_CLASSES; c++) { extern __thread TinyTLSSLL g_tls_sll[TINY_NUM_CLASSES]; if (g_tls_sll[c].count > 0) { extern uint32_t tiny_tls_sll_drain_for_ss(int class_idx, SuperSlab* ss); uint32_t drained = tiny_tls_sll_drain_for_ss(c, ss); g_slab_reuse_guard_drained += drained; if (drained > 0) { fprintf(stderr, "[SLAB_REUSE_GUARD] Drained %u blocks from TLS SLL (class=%d) before reusing EMPTY slab (ss=%p)\n", drained, c, (void*)ss); } } } } ``` ##### Stage 2: shared_pool 側から Box を呼び出す(境界 1 箇所) ```c // BEFORE (shared_pool_acquire_slab, L800付近): if (ss->empty_mask) { int empty_idx = __builtin_ctz(ss->empty_mask); // ... 即座に slab を取得 } // AFTER: if (ss->empty_mask) { // EMPTY slab 再利用境界で Tiny Box を 1 回だけ呼ぶ tiny_tls_slab_reuse_guard(ss); int empty_idx = __builtin_ctz(ss->empty_mask); // ... slab を取得 } ``` ##### Stage 3: ENV ゲートと診断カウンタ(Box 内) - [ ] `/mnt/workdisk/public_share/hakmem/core/hakmem_tiny_tls_ops.h` または実装ファイル ```c // TLS 診断カウンタ static __thread uint64_t g_slab_reuse_guard_calls = 0; static __thread uint64_t g_slab_reuse_guard_drained = 0; // Destructor で統計出力 static void slab_reuse_guard_stats(void) __attribute__((destructor)); static void slab_reuse_guard_stats(void) { if (g_slab_reuse_guard_calls > 0) { fprintf(stderr, "[SLAB_REUSE_GUARD_STATS] Guards=%lu Drained=%lu Avg=%.2f\n", g_slab_reuse_guard_calls, g_slab_reuse_guard_drained, (double)g_slab_reuse_guard_drained / g_slab_reuse_guard_calls); } } ``` #### テスト方法 1. Larson ベンチマーク(Slab 再利用が頻繁に発生) ```bash HAKMEM_TINY_SLAB_REUSE_GUARD=1 HAKMEM_TINY_SLL_DIAG=1 ./bench/larson 4 100000 8 1000 1 ``` 2. Guard を無効化して回帰確認(double-free が再現するはず) ```bash HAKMEM_TINY_SLAB_REUSE_GUARD=0 ./bench/larson 4 100000 8 1000 1 ``` 3. 性能ベンチマーク(guard の overhead を測定) ```bash ./bench/bench_fastpath_multi ``` #### 完了条件 - [ ] EMPTY slab 取得前に TLS SLL が drain される - [ ] Larson ベンチマークで double-free が発生しない - [ ] ENV で guard を無効化できる - [ ] 診断カウンタが正しく記録される - [ ] 性能劣化が 3% 以内 #### 工数 **中(半日〜1日)** --- ### P0.4: ENV Cleanup - Slab Reuse Guard **目的**: P0.3 で追加した ENV 変数を ENV Cleanup タスクに登録 #### 変更ファイル - [ ] `/mnt/workdisk/public_share/hakmem/docs/status/ENV_CLEANUP_TASK.md` #### 変更内容 Phase 4b セクションに以下を追加: ```markdown ### Phase 4b: Slab Reuse Guard (P0.3) **Variable**: `HAKMEM_TINY_SLAB_REUSE_GUARD` **Default**: 1 (enabled) **Purpose**: Force TLS SLL drain before reusing EMPTY slabs to prevent double-free **File**: `core/hakmem_shared_pool.c` **Function**: `slab_reuse_guard_enabled()` **Status**: ✅ Implemented in P0.3 ``` #### 完了条件 - [ ] ENV ドキュメントに追加される - [ ] Phase 4b タスクリストに含まれる #### 工数 **小(15分)** --- ## P1: 根本設計の一部導入(短期) **目標**: Out-of-band header の基盤を準備し、TLS SLL と meta の同期問題を緩和 **所要時間**: 3-5日 **コミット戦略**: 機能ごとに独立したコミット ### P1.1: Superslab に class_map[slab_idx] 追加 **目的**: 各 slab がどのクラスに属するかを SuperSlab 側で管理(Out-of-band の第一歩) #### 現状の問題 - `TinySlabMeta` に `class_idx` が埋め込まれている(in-band) - Free path で header から class_idx を読む必要がある #### 変更ファイル - [ ] `/mnt/workdisk/public_share/hakmem/core/superslab/superslab_types.h` (L11-18) #### 変更内容 ##### Stage 1: SuperSlab 構造体に class_map 追加 ```c // BEFORE (superslab_types.h:50-92): typedef struct SuperSlab { uint32_t magic; uint8_t lg_size; uint8_t _pad0[3]; // ... TinySlabMeta slabs[SLABS_PER_SUPERSLAB_MAX]; } SuperSlab; // AFTER: typedef struct SuperSlab { uint32_t magic; uint8_t lg_size; uint8_t _pad0[3]; // P1.1: Out-of-band class map (slab_idx → class_idx) // NOTE: TinySlabMeta.class_idx は互換性のために残すが、徐々に class_map に移行 uint8_t class_map[SLABS_PER_SUPERSLAB_MAX]; // 32 bytes (max 32 slabs) // ... TinySlabMeta slabs[SLABS_PER_SUPERSLAB_MAX]; } SuperSlab; ``` ##### Stage 2: Slab 初期化時に class_map を更新 - [ ] `/mnt/workdisk/public_share/hakmem/core/hakmem_shared_pool.c` (superslab_init_slab) ```c // BEFORE: meta->class_idx = class_idx; // AFTER: meta->class_idx = class_idx; // 互換性のために残す ss->class_map[slab_idx] = (uint8_t)class_idx; // P1.1: Out-of-band mapping ``` ##### Stage 3: Inline helper 追加 - [ ] `/mnt/workdisk/public_share/hakmem/core/superslab/superslab_inline.h` ```c // P1.1: Out-of-band class lookup static inline int ss_slab_class(const SuperSlab* ss, int slab_idx) { if (__builtin_expect(slab_idx < 0 || slab_idx >= SLABS_PER_SUPERSLAB_MAX, 0)) { return -1; } return (int)ss->class_map[slab_idx]; } ``` #### テスト方法 1. SuperSlab 初期化時の class_map 検証 ```bash HAKMEM_TINY_SLL_DIAG=1 ./bench/bench_tiny_all_classes ``` 2. class_map と meta->class_idx の一致確認 ```c // テストコード追加(hakmem_shared_pool.c) assert(ss->class_map[slab_idx] == meta->class_idx); ``` #### 完了条件 - [ ] SuperSlab に `class_map[32]` フィールドが追加される - [ ] Slab 初期化時に class_map が更新される - [ ] `ss_slab_class()` helper が動作する - [ ] メモリレイアウト確認(SuperSlab サイズが想定通り) #### 工数 **小(2-3時間)** --- ### P1.2: Free Fast Path での Header-Based Class 判定をオプション化 **目的**: P1.1 の class_map を free path で使えるようにし、header 依存を減らす #### 現状の問題 - Free path で header から class_idx を読む - Header が壊れると class_idx が不正になり、crash する #### 変更ファイル - [ ] `/mnt/workdisk/public_share/hakmem/core/hakmem_build_flags.h` - [ ] `/mnt/workdisk/public_share/hakmem/core/tiny_free_fast_v2.inc.h` #### 変更内容 ##### Stage 1: Build flag 追加 ```c // hakmem_build_flags.h:60-70 付近に追加 // P1.2: Use out-of-band class_map for free path (instead of header) // Default: OFF (keep header-based for backward compatibility) // Enable: -DHAKMEM_TINY_FREE_USE_CLASSMAP=1 #ifndef HAKMEM_TINY_FREE_USE_CLASSMAP # define HAKMEM_TINY_FREE_USE_CLASSMAP 0 #endif ``` ##### Stage 2: Free path で class_map を使用 ```c // tiny_free_fast_v2.inc.h (L100-150 付近) // BEFORE: #if HAKMEM_TINY_HEADER_CLASSIDX uint8_t hdr = *(const uint8_t*)base; int class_idx = (int)(hdr & HEADER_CLASS_MASK); #else // ... SuperSlab lookup でクラス判定 #endif // AFTER: #if HAKMEM_TINY_FREE_USE_CLASSMAP // P1.2: Out-of-band class lookup via class_map SuperSlab* ss = hak_super_lookup(ptr); if (__builtin_expect(!ss || ss->magic != SUPERSLAB_MAGIC, 0)) { goto slow_path; } int slab_idx = slab_index_for(ss, base); if (__builtin_expect(slab_idx < 0, 0)) { goto slow_path; } int class_idx = ss_slab_class(ss, slab_idx); if (__builtin_expect(class_idx < 0, 0)) { goto slow_path; } #elif HAKMEM_TINY_HEADER_CLASSIDX // Legacy: Header-based class lookup uint8_t hdr = *(const uint8_t*)base; int class_idx = (int)(hdr & HEADER_CLASS_MASK); #else // Fallback: Full SuperSlab lookup // ... #endif ``` #### テスト方法 1. Header-based モード(デフォルト) ```bash make clean && make hakmem.so ./bench/larson 4 100000 8 1000 1 ``` 2. Class-map モード ```bash make clean && make CFLAGS="-DHAKMEM_TINY_FREE_USE_CLASSMAP=1" hakmem.so ./bench/larson 4 100000 8 1000 1 ``` 3. A/B 性能比較 ```bash ./bench/bench_fastpath_multi # 両モードで実施 ``` #### 完了条件 - [ ] `HAKMEM_TINY_FREE_USE_CLASSMAP=0` でビルドが通る(デフォルト) - [ ] `HAKMEM_TINY_FREE_USE_CLASSMAP=1` でビルドが通る - [ ] 両モードで Larson ベンチマークが正常終了 - [ ] 性能差が ±5% 以内 #### 工数 **中(半日〜1日)** --- ### P1.3: meta->active の追加と TLS alloc/free での更新 **目的**: `meta->used` を「TLS SLL を含む真の使用中ブロック数」に正確化 #### 現状の問題 - `meta->used` は slab freelist からの alloc/free 時のみ更新される - TLS SLL に入っているブロックは `meta->used` にカウントされたまま - → Slab が "full" に見える → 再利用されない #### 設計方針 - `meta->used`: slab freelist から alloc した数(変更なし) - `meta->active`: **真の使用中ブロック数** = `meta->used - TLS SLL count` - TLS alloc 時に increment - TLS free (TLS SLL push) 時に decrement - Empty 判定は `meta->active == 0` で行う #### 変更ファイル - [ ] `/mnt/workdisk/public_share/hakmem/core/superslab/superslab_types.h` - [ ] `/mnt/workdisk/public_share/hakmem/core/tiny_alloc_fast_inline.h` - [ ] `/mnt/workdisk/public_share/hakmem/core/tiny_free_fast_v2.inc.h` #### 変更内容 ##### Stage 1: TinySlabMeta に active フィールド追加 ```c // superslab_types.h:11-18 typedef struct TinySlabMeta { _Atomic(void*) freelist; _Atomic uint16_t used; // slab freelist から alloc した数 _Atomic uint16_t active; // P1.3: 真の使用中ブロック数 (used - TLS SLL count) uint16_t capacity; uint8_t class_idx; uint8_t carved; uint8_t owner_tid_low; } TinySlabMeta; ``` ##### Stage 2: TLS alloc 時に active を increment ```c // tiny_alloc_fast_inline.h (tls_sll_pop 呼び出し後) void* ptr = tls_sll_pop(class_idx); if (ptr) { // P1.3: TLS SLL から取り出した → active をインクリメント TinyTLSSlab* tls = &g_tls_slabs[class_idx]; if (tls->meta) { atomic_fetch_add_explicit(&tls->meta->active, 1, memory_order_relaxed); } return ptr; } ``` ##### Stage 3: TLS free 時に active を decrement ```c // tiny_free_fast_v2.inc.h (tls_sll_push 呼び出し後) if (tls_sll_push(class_idx, base)) { // P1.3: TLS SLL に push した → active をデクリメント TinyTLSSlab* tls = &g_tls_slabs[class_idx]; if (tls->meta) { uint16_t old_active = atomic_fetch_sub_explicit(&tls->meta->active, 1, memory_order_relaxed); // IMPORTANT: active が 0 になったら EMPTY マーク if (old_active == 1) { // old_active - 1 = 0 ss_mark_slab_empty(tls->ss, tls->slab_idx); } } return; } ``` ##### Stage 4: Empty 判定を active ベースに変更 ```c // ss_hot_cold_box.h:37-39 static inline bool ss_is_slab_empty(const TinySlabMeta* meta) { // P1.3: active が 0 なら EMPTY(TLS SLL も含めて全ブロック free) uint16_t act = atomic_load_explicit(&meta->active, memory_order_relaxed); return (meta->capacity > 0 && act == 0); } ``` #### テスト方法 1. meta->active の動作確認 ```bash HAKMEM_TINY_SLL_DIAG=1 ./bench/bench_tiny 32 # TLS SLL push/pop で active が増減することを確認 ``` 2. Empty 検出の正確性テスト ```bash # すべてのブロックを free → active == 0 → EMPTY マーク ./test/test_empty_detection ``` 3. Larson マルチスレッド ```bash ./bench/larson 4 100000 8 1000 1 ``` #### 完了条件 - [ ] `TinySlabMeta` に `_Atomic uint16_t active` が追加される - [ ] TLS alloc で `active++` - [ ] TLS free で `active--` - [ ] `active == 0` で EMPTY マークされる - [ ] Larson ベンチマークが正常終了 - [ ] メモリリークなし #### 工数 **中(1日)** --- ### P1.4: ENV Cleanup - Free Use Classmap **目的**: P1.2 で追加した build flag を ENV 変数化(runtime 切り替え可能に) #### 変更ファイル - [ ] `/mnt/workdisk/public_share/hakmem/core/hakmem_build_flags.h`(削除) - [ ] `/mnt/workdisk/public_share/hakmem/core/tiny_free_fast_v2.inc.h` #### 変更内容 ```c // P1.4: Runtime ENV for class lookup strategy static inline int free_use_classmap_enabled(void) { static int g_use_classmap = -1; if (__builtin_expect(g_use_classmap == -1, 0)) { const char* env = getenv("HAKMEM_TINY_FREE_USE_CLASSMAP"); g_use_classmap = (env && *env != '0') ? 1 : 0; // デフォルト OFF fprintf(stderr, "[FREE_CLASSMAP] %s\n", g_use_classmap ? "ENABLED" : "DISABLED"); } return g_use_classmap; } ``` #### 完了条件 - [ ] Build flag が ENV 変数に置き換えられる - [ ] Runtime で切り替え可能 - [ ] ENV ドキュメントに追加 #### 工数 **小(1-2時間)** --- ## P2: TLS SLL 再設計(中期) **目標**: TLS SLL を「ポインタ箱」と「カウント箱」に明確に分離し、meta との同期を改善 **所要時間**: 1-2週間 **依存**: P1.3 完了後 ### P2.1: TLS Cache Box の定義(設計ドキュメント) **目的**: 新しい TLS SLL アーキテクチャを文書化 #### 成果物 - [ ] `/mnt/workdisk/public_share/hakmem/docs/design/TLS_CACHE_BOX_DESIGN.md` #### 内容 ```markdown # TLS Cache Box 設計 ## 概要 TLS SLL を 3 つの独立した「箱」に分離: 1. **Pointer Box**: ポインタのリスト(next ポインタで繋がる) 2. **Count Box**: 要素数カウンタ(atomic) 3. **Meta Sync Box**: meta->active との同期層 ## 設計原則 - **単一責任**: 各 Box は 1 つの役割のみを持つ - **明示的同期**: meta->active 更新は Sync Box の専任 - **境界明確化**: Box 間のインターフェースを厳密に定義 ## データ構造 ```c typedef struct TinyTLSCacheBox { void* head; // Pointer Box: SLL head _Atomic uint32_t count; // Count Box: element count _Atomic uint32_t meta_delta; // Sync Box: pending meta->active updates } TinyTLSCacheBox; ``` ## 操作フロー ### Alloc (Pop) 1. Pointer Box から pop 2. Count Box を decrement 3. Sync Box: `meta->active++` ### Free (Push) 1. Pointer Box に push 2. Count Box を increment 3. Sync Box: `meta->active--` ### Drain 1. Pointer Box から batch pop 2. Count Box を batch decrement 3. Sync Box: `meta_delta` を使って meta->active を一括調整 ``` #### 完了条件 - [ ] 設計ドキュメントが完成 - [ ] レビュー(ChatGPT)を実施 #### 工数 **小(2-3時間)** --- ### P2.2: meta->tls_cached フィールドの導入 **目的**: TLS SLL にキャッシュされているブロック数を meta 側で管理 #### 現状の問題 - TLS SLL count は TLS 側にしかない - meta 側から「何個が TLS にキャッシュされているか」が分からない #### 変更ファイル - [ ] `/mnt/workdisk/public_share/hakmem/core/superslab/superslab_types.h` #### 変更内容 ```c // superslab_types.h:11-18 typedef struct TinySlabMeta { _Atomic(void*) freelist; _Atomic uint16_t used; // slab freelist から alloc した数 _Atomic uint16_t active; // 真の使用中ブロック数 _Atomic uint16_t tls_cached; // P2.2: TLS SLL にキャッシュされている数 uint16_t capacity; uint8_t class_idx; uint8_t carved; uint8_t owner_tid_low; } TinySlabMeta; ``` #### 更新タイミング ```c // TLS SLL push 時 void tls_sll_push(int class_idx, void* ptr) { // ... push logic // P2.2: meta->tls_cached を increment TinyTLSSlab* tls = &g_tls_slabs[class_idx]; if (tls->meta) { atomic_fetch_add_explicit(&tls->meta->tls_cached, 1, memory_order_relaxed); } } // TLS SLL pop 時 void* tls_sll_pop(int class_idx) { void* ptr = /* ... pop logic */; if (ptr) { // P2.2: meta->tls_cached を decrement TinyTLSSlab* tls = &g_tls_slabs[class_idx]; if (tls->meta) { atomic_fetch_sub_explicit(&tls->meta->tls_cached, 1, memory_order_relaxed); } } return ptr; } ``` #### 検証 ```c // Invariant check (debug ビルド) // NOTE: remote_pending など Remote Box 側のカウンタを将来導入する場合は、 // ここに + remote_pending を加えた形に拡張する(現状 remote_pending==0 前提)。 assert(meta->active + meta->tls_cached == meta->used - freelist_count); ``` #### 完了条件 - [ ] `TinySlabMeta` に `tls_cached` フィールド追加 - [ ] Push/Pop で `tls_cached` が更新される - [ ] Invariant が保たれる #### 工数 **中(半日〜1日)** --- ### P2.3: meta->used を Deprecated に(段階的移行) **目的**: `meta->active` を主要指標とし、`meta->used` は互換性のために残す #### 変更内容 ##### Stage 1: Empty 判定を active ベースに統一 ```c // すべての empty 判定箇所を変更 // BEFORE: if (meta->used == 0) { /* ... */ } // AFTER: if (atomic_load_explicit(&meta->active, memory_order_relaxed) == 0) { /* ... */ } ``` ##### Stage 2: コメント追加 ```c typedef struct TinySlabMeta { _Atomic uint16_t used; // DEPRECATED: Use 'active' instead (kept for compatibility) _Atomic uint16_t active; // PREFERRED: True allocated block count _Atomic uint16_t tls_cached; // Cached in TLS SLL // ... } TinySlabMeta; ``` #### 完了条件 - [ ] すべての empty 判定が `meta->active` ベース - [ ] `meta->used` には DEPRECATED コメント - [ ] ビルド・テストが通る #### 工数 **中(1日)** --- ## P3: Header 完全 Out-of-band(長期) **目標**: Header を完全に SuperSlab 側に移動し、TLS SLL を簡素化 **所要時間**: 2-3週間 **依存**: P2 完了後 ### P3.1: Header Out-of-band 化の設計ドキュメント **目的**: mimalloc/tcmalloc スタイルの header 管理を設計 #### 成果物 - [ ] `/mnt/workdisk/public_share/hakmem/docs/design/HEADER_OUTOFBAND_DESIGN.md` #### 内容 ```markdown # Header Out-of-band 設計 ## 目標 - User data から header を完全に分離 - Pointer → Header のマッピングを O(1) で実現 ## アプローチ ### Option A: SuperSlab 内に header 配列 ```c typedef struct SuperSlab { // ... uint8_t slab_headers[SLABS_PER_SUPERSLAB_MAX][MAX_BLOCKS_PER_SLAB]; } SuperSlab; ``` ### Option B: Separate header region (mimalloc style) - Header を別の mmap 領域に配置 - Pointer masking で header region を計算 ## 推奨: Option A(シンプル、キャッシュ局所性) ``` #### 工数 **小(3-4時間)** --- ### P3.2: SuperSlab に Header Array 追加(実装) **目的**: Option A を実装 #### 変更ファイル - [ ] `/mnt/workdisk/public_share/hakmem/core/superslab/superslab_types.h` #### 変更内容 ```c typedef struct SuperSlab { // ... // P3.2: Out-of-band headers (per slab, per block) // NOTE: C0-C6 は小ブロック → header array 必要 // C7 (2048B) は大きいので header なしでも可 uint8_t* header_arrays[SLABS_PER_SUPERSLAB_MAX]; // Per-slab header pointers TinySlabMeta slabs[SLABS_PER_SUPERSLAB_MAX]; } SuperSlab; ``` // SuperSlab 初期化時に header 配列を 1 回だけ確保し、SuperSlab 解放時に必ず free する: // size_t blocks = blocks_per_slab_for_tiny_class(class_idx); // ss->header_arrays[slab_idx] = malloc(blocks * sizeof(uint8_t)); // memset(ss->header_arrays[slab_idx], 0, blocks); // … // // SuperSlab 破棄時: // free(ss->header_arrays[slab_idx]); #### Alloc 時の header 設定 ```c void* ptr = /* ... carve or pop */; int block_idx = (ptr - slab_base) / block_size; ss->header_arrays[slab_idx][block_idx] = HEADER_MAGIC | class_idx; ``` #### 完了条件 - [ ] SuperSlab に header_arrays 追加 - [ ] header_arrays[slab_idx] の確保/解放パスが SuperSlab のライフサイクルに紐づいている - [ ] Alloc 時に header 設定 - [ ] Free 時に header 読み取り #### 工数 **大(2-3日)** --- ### P3.3: TLS SLL の next ポインタを offset 0 に統一 **目的**: Header が out-of-band になったので、next ポインタを常に base[0] に格納 #### 変更ファイル - [ ] `/mnt/workdisk/public_share/hakmem/core/tiny_nextptr.h` #### 変更内容 ```c static inline size_t tiny_next_off(int class_idx) { // P3.3: Header out-of-band → 全クラス offset 0 (void)class_idx; return 0u; } ``` #### 完了条件 - [ ] `tiny_next_off()` が常に `0` を返す - [ ] TLS SLL 操作が簡素化される - [ ] すべてのテストが通る #### 工数 **小(2-3時間)** --- ### P3.4: HAKMEM_TINY_HEADER_CLASSIDX フラグの削除 **目的**: Out-of-band 化が完了したので、古いフラグを削除 #### 変更ファイル - [ ] `/mnt/workdisk/public_share/hakmem/core/hakmem_build_flags.h`(削除) - [ ] すべての `#if HAKMEM_TINY_HEADER_CLASSIDX` を削除 #### 完了条件 - [ ] フラグが完全に削除される - [ ] ビルドが通る - [ ] 全ベンチマークが正常動作 #### 工数 **中(1日)** --- ## 依存関係図 ``` P0.1 (C0 → 16B) │ v P0.2 (next_off 統一) ─────┐ │ │ v v P0.3 (Slab Reuse Guard) P1.1 (class_map) │ │ v v P0.4 (ENV Doc) P1.2 (Free Classmap) ─┐ │ │ v │ P1.3 (meta->active) ───┤ │ │ v v P1.4 (ENV) P2.1 (Design Doc) │ v P2.2 (tls_cached) │ v P2.3 (Deprecate used) │ v P3.1 (Design Doc) │ v P3.2 (Header Array) │ v P3.3 (next_off = 0) │ v P3.4 (Flag Cleanup) ``` ### クリティカルパス ``` P0.1 → P0.2 → P0.3 → P1.1 → P1.2 → P1.3 → P2.2 → P2.3 → P3.2 → P3.3 → P3.4 ``` **Total**: 約 3-4 週間 ### 並行可能なタスク - P0.4 (ENV Doc) は P0.3 と並行可能 - P1.4 (ENV) は P1.2 と並行可能 - P2.1 (Design) は P1.3 と並行可能 - P3.1 (Design) は P2.2 と並行可能 --- ## 工数サマリー | Phase | タスク | 工数 | 累積 | |-------|--------|------|------| | **P0** | | | | | P0.1 | C0 → 16B | 小(2-3h) | 3h | | P0.2 | next_off 統一 | 小(2-3h) | 6h | | P0.3 | Slab Reuse Guard | 中(0.5-1d) | 1.5d | | P0.4 | ENV Doc | 小(15m) | 1.5d | | **P0 合計** | | **1.5-2日** | | | **P1** | | | | | P1.1 | class_map | 小(2-3h) | 2d | | P1.2 | Free Classmap | 中(0.5-1d) | 3d | | P1.3 | meta->active | 中(1d) | 4d | | P1.4 | ENV | 小(1-2h) | 4.5d | | **P1 合計** | | **3-5日** | | | **P2** | | | | | P2.1 | Design Doc | 小(2-3h) | 5d | | P2.2 | tls_cached | 中(0.5-1d) | 6d | | P2.3 | Deprecate used | 中(1d) | 7d | | **P2 合計** | | **1-2週間** | | | **P3** | | | | | P3.1 | Design Doc | 小(3-4h) | 7.5d | | P3.2 | Header Array | 大(2-3d) | 10.5d | | P3.3 | next_off = 0 | 小(2-3h) | 11d | | P3.4 | Flag Cleanup | 中(1d) | 12d | | **P3 合計** | | **2-3週間** | | | **総合計** | | **4-6週間** | | --- ## テスト計画 ### Phase 完了時のテストマトリクス | テスト | P0 | P1 | P2 | P3 | |--------|----|----|----|----| | **ビルド確認** | ✓ | ✓ | ✓ | ✓ | | `make clean && make hakmem.so` | ✓ | ✓ | ✓ | ✓ | | **単体テスト** | | | | | | `bench_tiny` (全クラス) | ✓ | ✓ | ✓ | ✓ | | `bench_tiny_all_classes` | ✓ | ✓ | ✓ | ✓ | | **統合テスト** | | | | | | Larson (2 threads) | ✓ | ✓ | ✓ | ✓ | | Larson (4 threads) | ✓ | ✓ | ✓ | ✓ | | cache-scratch | ✓ | ✓ | ✓ | ✓ | | **性能ベンチマーク** | | | | | | bench_fastpath_multi | ✓ | ✓ | ✓ | ✓ | | bench_fastpath_sll | ✓ | ✓ | ✓ | ✓ | | **診断** | | | | | | `HAKMEM_TINY_SLL_DIAG=1` | ✓ | ✓ | ✓ | ✓ | | Valgrind memcheck | - | ✓ | ✓ | ✓ | | **回帰テスト** | | | | | | mimalloc-bench suite | - | - | ✓ | ✓ | ### 性能ベースライン 各 Phase 開始前に以下を記録: ```bash # Baseline 記録 ./bench/bench_fastpath_multi > baseline_pN.txt ./bench/larson 4 100000 8 1000 1 > baseline_larson_pN.txt # Phase 完了後に比較 ./bench/bench_fastpath_multi > result_pN.txt diff baseline_pN.txt result_pN.txt ``` **許容性能劣化**: ±5% 以内 --- ## リスクと軽減策 ### P0 リスク | リスク | 影響 | 確率 | 軽減策 | |--------|------|------|--------| | **P0.1: C0 昇格で性能劣化** | 中 | 低 | 8B 割り当てが少ない(大半は 16B+)ため影響小。ベンチマークで確認。 | | **P0.2: C7 で user data corruption** | 高 | 低 | P0.1 で C0 を 16B に昇格済み。C7 も 2048B で十分な余裕あり。 | | **P0.3: Drain overhead** | 中 | 中 | ENV で無効化可能。Drain 頻度は低い(EMPTY slab 取得時のみ)。 | ### P1 リスク | リスク | 影響 | 確率 | 軽減策 | |--------|------|------|--------| | **P1.1: メモリオーバーヘッド** | 低 | 低 | class_map は 32 bytes/SuperSlab(2MB あたり)。無視できる。 | | **P1.2: SuperSlab lookup overhead** | 中 | 中 | ENV で header-based に切り替え可能。A/B テストで検証。 | | **P1.3: meta->active 競合** | 中 | 中 | Atomic 操作は relaxed memory order で十分。Contention 低い。 | ### P2 リスク | リスク | 影響 | 確率 | 軽減策 | |--------|------|------|--------| | **P2.2: tls_cached オーバーヘッド** | 低 | 低 | Atomic increment/decrement のみ。Fast path で既に実施中。 | | **P2.3: meta->used 依存コード** | 中 | 中 | 段階的移行。互換性のために `used` を残す。 | ### P3 リスク | リスク | 影響 | 確率 | 軽減策 | |--------|------|------|--------| | **P3.2: Header array メモリ消費** | 中 | 中 | C0-C6 のみ header 必要。C7 は不要。メモリは negligible。 | | **P3.3: TLS SLL 互換性破壊** | 高 | 低 | P0.2 で next_off 統一済み。P3.3 は簡素化のみ。 | | **P3.4: 既存コードの破壊** | 高 | 中 | Flag 削除前に全検索。段階的にコミット。 | ### 全体リスク | リスク | 影響 | 確率 | 軽減策 | |--------|------|------|--------| | **Phase 間の互換性問題** | 高 | 中 | 各 Phase を独立したブランチで開発。十分なテスト後にマージ。 | | **性能劣化** | 中 | 中 | 各 Phase でベンチマーク。劣化があれば ENV で無効化。 | | **スケジュール遅延** | 中 | 高 | P0/P1 を優先。P2/P3 は optional とする。 | --- ## ロールバック計画 ### Git ブランチ戦略 ``` master │ ├─ p0-defensive-fixes │ ├─ p0.1-c0-upgrade │ ├─ p0.2-next-off-unify │ ├─ p0.3-slab-reuse-guard │ └─ p0.4-env-doc │ ├─ p1-outofband-foundation │ ├─ p1.1-class-map │ ├─ p1.2-free-classmap │ ├─ p1.3-meta-active │ └─ p1.4-env │ ├─ p2-tls-redesign │ ├─ p2.1-design │ ├─ p2.2-tls-cached │ └─ p2.3-deprecate-used │ └─ p3-header-outofband ├─ p3.1-design ├─ p3.2-header-array ├─ p3.3-next-off-zero └─ p3.4-flag-cleanup ``` ### 各 Phase のロールバック手順 #### P0 ロールバック ```bash git checkout master git branch -D p0-defensive-fixes # 既存の設定に戻る(変更なし) ``` #### P1 ロールバック ```bash git checkout master git branch -D p1-outofband-foundation # ENV で無効化 export HAKMEM_TINY_FREE_USE_CLASSMAP=0 ``` #### P2 ロールバック ```bash git checkout master git branch -D p2-tls-redesign # meta->active を無視し、meta->used を使用(コード変更不要) ``` #### P3 ロールバック ```bash git checkout master git branch -D p3-header-outofband # HAKMEM_TINY_HEADER_CLASSIDX=1 に戻す ``` --- ## 次のステップ ### 即座に開始可能 1. [ ] P0.1: C0(8B) を 16B に昇格 2. [ ] P0.2: tiny_next_off を全クラス 1 に ### P0 完了後 3. [ ] Larson ベンチマークで double-free が解消されるか確認 4. [ ] P1.1 に進む ### P1 完了後 5. [ ] mimalloc-bench suite で性能回帰チェック 6. [ ] P2 設計レビュー(ChatGPT) ### マイルストーン - **Week 1**: P0 完了 → Larson 安定化 - **Week 2-3**: P1 完了 → Out-of-band 基盤確立 - **Week 4-5**: P2 完了 → TLS SLL 再設計 - **Week 6+**: P3 開始 → Header 完全分離 --- ## 参考資料 ### 内部ドキュメント - `/mnt/workdisk/public_share/hakmem/docs/analysis/TLS_SLL_ARCHITECTURE_INVESTIGATION.md` - TLS SLL の現状分析と問題点 - `/mnt/workdisk/public_share/hakmem/HAKMEM_ARCHITECTURE_OVERVIEW.md` - 全体アーキテクチャ ### 外部参考 - **mimalloc**: Out-of-band header の実装例 - **tcmalloc**: Per-thread cache の設計 - **jemalloc**: Arena + slab の階層設計 --- **最終更新**: 2025-11-28 **作成者**: Claude (Anthropic) **レビュー**: ChatGPT Pro (推奨)