Files
hakmem/docs/TINY_REDESIGN_CHECKLIST.md

1274 lines
38 KiB
Markdown
Raw Normal View History

Tiny Pool redesign: P0.1, P0.3, P1.1, P1.2 - Out-of-band class_idx lookup 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>
2025-11-28 13:42:39 +09:00
# 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 なら EMPTYTLS 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/SuperSlab2MB あたり)。無視できる。 |
| **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 (推奨)