This commit implements the first phase of Tiny Pool redesign based on
ChatGPT architecture review. The goal is to eliminate Header/Next pointer
conflicts by moving class_idx lookup out-of-band (to SuperSlab metadata).
## P0.1: C0(8B) class upgraded to 16B
- Size table changed: {16,32,64,128,256,512,1024,2048} (8 classes)
- LUT updated: 1..16 → class 0, 17..32 → class 1, etc.
- tiny_next_off: C0 now uses offset 1 (header preserved)
- Eliminates edge cases for 8B allocations
## P0.3: Slab reuse guard Box (tls_slab_reuse_guard_box.h)
- New Box for draining TLS SLL before slab reuse
- ENV gate: HAKMEM_TINY_SLAB_REUSE_GUARD=1
- Prevents stale pointers when slabs are recycled
- Follows Box theory: single responsibility, minimal API
## P1.1: SuperSlab class_map addition
- Added uint8_t class_map[SLABS_PER_SUPERSLAB_MAX] to SuperSlab
- Maps slab_idx → class_idx for out-of-band lookup
- Initialized to 255 (UNASSIGNED) on SuperSlab creation
- Set correctly on slab initialization in all backends
## P1.2: Free fast path uses class_map
- ENV gate: HAKMEM_TINY_USE_CLASS_MAP=1
- Free path can now get class_idx from class_map instead of Header
- Falls back to Header read if class_map returns invalid value
- Fixed Legacy Backend dynamic slab initialization bug
## Documentation added
- HAKMEM_ARCHITECTURE_OVERVIEW.md: 4-layer architecture analysis
- TLS_SLL_ARCHITECTURE_INVESTIGATION.md: Root cause analysis
- PTR_LIFECYCLE_TRACE_AND_ROOT_CAUSE_ANALYSIS.md: Pointer tracking
- TINY_REDESIGN_CHECKLIST.md: Implementation roadmap (P0-P3)
## Test results
- Baseline: 70% success rate (30% crash - pre-existing issue)
- class_map enabled: 70% success rate (same as baseline)
- Performance: ~30.5M ops/s (unchanged)
## Next steps (P1.3, P2, P3)
- P1.3: Add meta->active for accurate TLS/freelist sync
- P2: TLS SLL redesign with Box-based counting
- P3: Complete Header out-of-band migration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
38 KiB
HAKMEM Tiny Pool 再設計実装チェックリスト
作成日: 2025-11-28 目的: ChatGPT 設計レビューに基づく段階的な再設計の実装計画
目次
- 背景と設計方針
- P0: 守りの修正(即座)
- P1: 根本設計の一部導入(短期)
- P2: TLS SLL 再設計(中期)
- P3: Header 完全 Out-of-band(長期)
- 依存関係図
- 工数サマリー
- テスト計画
- リスクと軽減策
背景と設計方針
核心の問題
現在の HAKMEM Tiny Pool には以下の構造的問題があります:
-
In-band header による複雑性
- TLS SLL の next ポインタと header が絡まる
meta->usedと TLS SLL の同期不整合- クラス 0/7 と 1-6 で異なる
next_offset(0 vs 1)
-
TLS SLL と meta->used の乖離
- Fast free path では
meta->usedが更新されない - Periodic drain (2048 frees) まで slab が "full" に見える
- Slab 再利用時に TLS SLL がクリアされない
- Fast free path では
-
業界標準との乖離
- 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)
変更内容
// 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 に
テスト方法
- ビルド確認
make clean && make hakmem.so - サイズルックアップテスト
# 1-16 byte → class 0 # 17-32 byte → class 1 HAKMEM_TINY_SLL_DIAG=1 ./bench/bench_tiny 16 - Larson ベンチマーク(double-free チェック)
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)
変更内容
// 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 特殊ケース削除)
// 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 特殊ケースの記述を削除
テスト方法
- 全クラスで next ポインタのアライメントを確認
HAKMEM_TINY_SLL_DIAG=1 ./bench/bench_tiny_all_classes - C7 (2048B) の TLS SLL 動作確認
HAKMEM_TINY_SLL_DIAG=1 ./bench/bench_tiny 2048 - Larson マルチスレッド
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 する
背景
現在の問題:
- Slab が
meta->used == 0で EMPTY と判定される - しかし TLS SLL には古いポインタが残っている
- Slab が再初期化されて別クラスで再利用される
- 古いポインタが再度 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」を追加
// 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 箇所)
// 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または実装ファイル
// 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);
}
}
テスト方法
- Larson ベンチマーク(Slab 再利用が頻繁に発生)
HAKMEM_TINY_SLAB_REUSE_GUARD=1 HAKMEM_TINY_SLL_DIAG=1 ./bench/larson 4 100000 8 1000 1 - Guard を無効化して回帰確認(double-free が再現するはず)
HAKMEM_TINY_SLAB_REUSE_GUARD=0 ./bench/larson 4 100000 8 1000 1 - 性能ベンチマーク(guard の overhead を測定)
./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 セクションに以下を追加:
### 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 追加
// 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)
// 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
// 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];
}
テスト方法
- SuperSlab 初期化時の class_map 検証
HAKMEM_TINY_SLL_DIAG=1 ./bench/bench_tiny_all_classes - class_map と meta->class_idx の一致確認
// テストコード追加(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 追加
// 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 を使用
// 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
テスト方法
- Header-based モード(デフォルト)
make clean && make hakmem.so ./bench/larson 4 100000 8 1000 1 - Class-map モード
make clean && make CFLAGS="-DHAKMEM_TINY_FREE_USE_CLASSMAP=1" hakmem.so ./bench/larson 4 100000 8 1000 1 - A/B 性能比較
./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 フィールド追加
// 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
// 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
// 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 ベースに変更
// 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);
}
テスト方法
- meta->active の動作確認
HAKMEM_TINY_SLL_DIAG=1 ./bench/bench_tiny 32 # TLS SLL push/pop で active が増減することを確認 - Empty 検出の正確性テスト
# すべてのブロックを free → active == 0 → EMPTY マーク ./test/test_empty_detection - Larson マルチスレッド
./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
変更内容
// 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
内容
# 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)
- Pointer Box から pop
- Count Box を decrement
- Sync Box:
meta->active++
Free (Push)
- Pointer Box に push
- Count Box を increment
- Sync Box:
meta->active--
Drain
- Pointer Box から batch pop
- Count Box を batch decrement
- 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;
更新タイミング
// 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;
}
検証
// 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 ベースに統一
// すべての empty 判定箇所を変更
// BEFORE:
if (meta->used == 0) { /* ... */ }
// AFTER:
if (atomic_load_explicit(&meta->active, memory_order_relaxed) == 0) { /* ... */ }
Stage 2: コメント追加
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
内容
# 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 設定
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
変更内容
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 開始前に以下を記録:
# 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 ロールバック
git checkout master
git branch -D p0-defensive-fixes
# 既存の設定に戻る(変更なし)
P1 ロールバック
git checkout master
git branch -D p1-outofband-foundation
# ENV で無効化
export HAKMEM_TINY_FREE_USE_CLASSMAP=0
P2 ロールバック
git checkout master
git branch -D p2-tls-redesign
# meta->active を無視し、meta->used を使用(コード変更不要)
P3 ロールバック
git checkout master
git branch -D p3-header-outofband
# HAKMEM_TINY_HEADER_CLASSIDX=1 に戻す
次のステップ
即座に開始可能
- P0.1: C0(8B) を 16B に昇格
- P0.2: tiny_next_off を全クラス 1 に
P0 完了後
- Larson ベンチマークで double-free が解消されるか確認
- P1.1 に進む
P1 完了後
- mimalloc-bench suite で性能回帰チェック
- 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 (推奨)