1274 lines
38 KiB
Markdown
1274 lines
38 KiB
Markdown
|
|
# 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 (推奨)
|