Files
hakmem/docs/TINY_REDESIGN_CHECKLIST.md
Moe Charm (CI) dc9e650db3 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

1274 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 (推奨)