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

38 KiB
Raw Blame History

HAKMEM Tiny Pool 再設計実装チェックリスト

作成日: 2025-11-28 目的: ChatGPT 設計レビューに基づく段階的な再設計の実装計画


目次

  1. 背景と設計方針
  2. P0: 守りの修正(即座)
  3. P1: 根本設計の一部導入(短期)
  4. P2: TLS SLL 再設計(中期)
  5. 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)

変更内容

// 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. ビルド確認
    make clean && make hakmem.so
    
  2. サイズルックアップテスト
    # 1-16 byte  → class 0
    # 17-32 byte → class 1
    HAKMEM_TINY_SLL_DIAG=1 ./bench/bench_tiny 16
    
  3. Larson ベンチマークdouble-free チェック)
    HAKMEM_TINY_SLL_DIAG=1 ./bench/larson 2 10000 8 1000 1
    

完了条件

  • C0 が 16B に昇格され、サイズクラスが {16,32,64,128,256,512,1024,2048} に整列している
  • 1-16 byte の割り当てが class 0、17-32 byte の割り当てが class 1 にマップされる
  • Larson ベンチマークが正常終了double-free なし)
  • 性能劣化なしthroughput ±5% 以内)

工数

2-3時間


P0.2: tiny_next_off を全クラス 1 に統一

目的: C0/C7 の特殊ケースを排除し、header 管理を統一

変更ファイル

  • /mnt/workdisk/public_share/hakmem/core/tiny_nextptr.h (L47-59)

変更内容

// BEFORE:
static inline size_t tiny_next_off(int class_idx) {
#if HAKMEM_TINY_HEADER_CLASSIDX
    // C0, C7 → offset 0 (header 潰す)
    // C1-C6 → offset 1 (header 保持)
    return (class_idx == 0 || class_idx == 7) ? 0u : 1u;
#else
    return 0u;
#endif
}

// AFTER:
static inline size_t tiny_next_off(int class_idx) {
#if HAKMEM_TINY_HEADER_CLASSIDX
    // 全クラス → offset 1 (header 常に保持)
    // NOTE: P0.1 で C0 を 16B に昇格済みのため、C0 でも offset 1 が使用可能
    (void)class_idx;
    return 1u;
#else
    (void)class_idx;
    return 0u;
#endif
}

追加変更

  • /mnt/workdisk/public_share/hakmem/core/box/tls_sll_drain_box.h (L185-189)

    • Header 復元ロジックを簡素化C0/C7 特殊ケース削除)
    // BEFORE:
    if (class_idx != 0 && class_idx != 7) {
        *(uint8_t*)base = HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK);
    }
    
    // AFTER:
    *(uint8_t*)base = HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK);
    
  • /mnt/workdisk/public_share/hakmem/core/box/tls_sll_box.h (L340-400)

    • Safe header モードの条件分岐を削除

ドキュメント更新

  • /mnt/workdisk/public_share/hakmem/core/tiny_nextptr.h (L3-26)
    • コメントを更新: C0/C7 特殊ケースの記述を削除

テスト方法

  1. 全クラスで next ポインタのアライメントを確認
    HAKMEM_TINY_SLL_DIAG=1 ./bench/bench_tiny_all_classes
    
  2. C7 (2048B) の TLS SLL 動作確認
    HAKMEM_TINY_SLL_DIAG=1 ./bench/bench_tiny 2048
    
  3. Larson マルチスレッド
    HAKMEM_TINY_SLL_DIAG=1 ./bench/larson 4 10000 8 1000 1
    

完了条件

  • tiny_next_off() がすべてのクラスで 1 を返す
  • C7 で user data corruption が発生しない
  • TLS SLL の header 復元ロジックが統一される
  • すべてのベンチマークが正常終了

工数

2-3時間


P0.3: Slab 再利用時の TLS SLL Drain ガード

目的: Slab が再初期化される前に、TLS SLL を強制的に drain する

背景

現在の問題:

  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」を追加
// hakmem_tiny_tls_ops.h もしくは対応する実装ファイル側:

// ENV: HAKMEM_TINY_SLAB_REUSE_GUARD=1/0 (default: 1)
static inline int slab_reuse_guard_enabled(void) {
    static int g_guard = -1;
    if (__builtin_expect(g_guard == -1, 0)) {
        const char* env = getenv("HAKMEM_TINY_SLAB_REUSE_GUARD");
        g_guard = (env && *env == '0') ? 0 : 1;  // デフォルト ON
        fprintf(stderr, "[SLAB_REUSE_GUARD] %s\n", g_guard ? "ENABLED" : "DISABLED");
    }
    return g_guard;
}

// Box: EMPTY slab を再利用する直前に、該当 SuperSlab に紐づく TLS SLL を Drain する
// NOTE: 実装詳細(どのクラス/スレッドを対象にするか)は Tiny Box 内で完結させる。
void tiny_tls_slab_reuse_guard(SuperSlab* ss) {
    if (!slab_reuse_guard_enabled()) return;

    g_slab_reuse_guard_calls++;

    // 例: 現スレッドの TLS SLL を走査し、ss に属するブロックだけ drain するBox 内部ロジック)
    for (int c = 0; c < TINY_NUM_CLASSES; c++) {
        extern __thread TinyTLSSLL g_tls_sll[TINY_NUM_CLASSES];
        if (g_tls_sll[c].count > 0) {
            extern uint32_t tiny_tls_sll_drain_for_ss(int class_idx, SuperSlab* ss);
            uint32_t drained = tiny_tls_sll_drain_for_ss(c, ss);
            g_slab_reuse_guard_drained += drained;
            if (drained > 0) {
                fprintf(stderr,
                        "[SLAB_REUSE_GUARD] Drained %u blocks from TLS SLL (class=%d) before reusing EMPTY slab (ss=%p)\n",
                        drained, c, (void*)ss);
            }
        }
    }
}
Stage 2: shared_pool 側から Box を呼び出す(境界 1 箇所)
// BEFORE (shared_pool_acquire_slab, L800付近):
if (ss->empty_mask) {
    int empty_idx = __builtin_ctz(ss->empty_mask);
    // ... 即座に slab を取得
}

// AFTER:
if (ss->empty_mask) {
    // EMPTY slab 再利用境界で Tiny Box を 1 回だけ呼ぶ
    tiny_tls_slab_reuse_guard(ss);

    int empty_idx = __builtin_ctz(ss->empty_mask);
    // ... slab を取得
}
Stage 3: ENV ゲートと診断カウンタBox 内)
  • /mnt/workdisk/public_share/hakmem/core/hakmem_tiny_tls_ops.h または実装ファイル
// TLS 診断カウンタ
static __thread uint64_t g_slab_reuse_guard_calls = 0;
static __thread uint64_t g_slab_reuse_guard_drained = 0;

// Destructor で統計出力
static void slab_reuse_guard_stats(void) __attribute__((destructor));
static void slab_reuse_guard_stats(void) {
    if (g_slab_reuse_guard_calls > 0) {
        fprintf(stderr, "[SLAB_REUSE_GUARD_STATS] Guards=%lu Drained=%lu Avg=%.2f\n",
                g_slab_reuse_guard_calls, g_slab_reuse_guard_drained,
                (double)g_slab_reuse_guard_drained / g_slab_reuse_guard_calls);
    }
}

テスト方法

  1. Larson ベンチマークSlab 再利用が頻繁に発生)
    HAKMEM_TINY_SLAB_REUSE_GUARD=1 HAKMEM_TINY_SLL_DIAG=1 ./bench/larson 4 100000 8 1000 1
    
  2. Guard を無効化して回帰確認double-free が再現するはず)
    HAKMEM_TINY_SLAB_REUSE_GUARD=0 ./bench/larson 4 100000 8 1000 1
    
  3. 性能ベンチマークguard の overhead を測定)
    ./bench/bench_fastpath_multi
    

完了条件

  • EMPTY slab 取得前に TLS SLL が drain される
  • Larson ベンチマークで double-free が発生しない
  • ENV で guard を無効化できる
  • 診断カウンタが正しく記録される
  • 性能劣化が 3% 以内

工数

半日〜1日


P0.4: ENV Cleanup - Slab Reuse Guard

目的: P0.3 で追加した ENV 変数を ENV Cleanup タスクに登録

変更ファイル

  • /mnt/workdisk/public_share/hakmem/docs/status/ENV_CLEANUP_TASK.md

変更内容

Phase 4b セクションに以下を追加:

### Phase 4b: Slab Reuse Guard (P0.3)

**Variable**: `HAKMEM_TINY_SLAB_REUSE_GUARD`
**Default**: 1 (enabled)
**Purpose**: Force TLS SLL drain before reusing EMPTY slabs to prevent double-free
**File**: `core/hakmem_shared_pool.c`
**Function**: `slab_reuse_guard_enabled()`
**Status**: ✅ Implemented in P0.3

完了条件

  • ENV ドキュメントに追加される
  • Phase 4b タスクリストに含まれる

工数

15分


P1: 根本設計の一部導入(短期)

目標: Out-of-band header の基盤を準備し、TLS SLL と meta の同期問題を緩和 所要時間: 3-5日 コミット戦略: 機能ごとに独立したコミット

P1.1: Superslab に class_map[slab_idx] 追加

目的: 各 slab がどのクラスに属するかを SuperSlab 側で管理Out-of-band の第一歩)

現状の問題

  • TinySlabMetaclass_idx が埋め込まれているin-band
  • Free path で header から class_idx を読む必要がある

変更ファイル

  • /mnt/workdisk/public_share/hakmem/core/superslab/superslab_types.h (L11-18)

変更内容

Stage 1: SuperSlab 構造体に class_map 追加
// BEFORE (superslab_types.h:50-92):
typedef struct SuperSlab {
    uint32_t magic;
    uint8_t  lg_size;
    uint8_t  _pad0[3];
    // ...
    TinySlabMeta slabs[SLABS_PER_SUPERSLAB_MAX];
} SuperSlab;

// AFTER:
typedef struct SuperSlab {
    uint32_t magic;
    uint8_t  lg_size;
    uint8_t  _pad0[3];

    // P1.1: Out-of-band class map (slab_idx → class_idx)
    // NOTE: TinySlabMeta.class_idx は互換性のために残すが、徐々に class_map に移行
    uint8_t  class_map[SLABS_PER_SUPERSLAB_MAX];  // 32 bytes (max 32 slabs)

    // ...
    TinySlabMeta slabs[SLABS_PER_SUPERSLAB_MAX];
} SuperSlab;
Stage 2: Slab 初期化時に class_map を更新
  • /mnt/workdisk/public_share/hakmem/core/hakmem_shared_pool.c (superslab_init_slab)
// BEFORE:
meta->class_idx = class_idx;

// AFTER:
meta->class_idx = class_idx;  // 互換性のために残す
ss->class_map[slab_idx] = (uint8_t)class_idx;  // P1.1: Out-of-band mapping
Stage 3: Inline helper 追加
  • /mnt/workdisk/public_share/hakmem/core/superslab/superslab_inline.h
// P1.1: Out-of-band class lookup
static inline int ss_slab_class(const SuperSlab* ss, int slab_idx) {
    if (__builtin_expect(slab_idx < 0 || slab_idx >= SLABS_PER_SUPERSLAB_MAX, 0)) {
        return -1;
    }
    return (int)ss->class_map[slab_idx];
}

テスト方法

  1. SuperSlab 初期化時の class_map 検証
    HAKMEM_TINY_SLL_DIAG=1 ./bench/bench_tiny_all_classes
    
  2. class_map と meta->class_idx の一致確認
    // テストコード追加hakmem_shared_pool.c
    assert(ss->class_map[slab_idx] == meta->class_idx);
    

完了条件

  • SuperSlab に class_map[32] フィールドが追加される
  • Slab 初期化時に class_map が更新される
  • ss_slab_class() helper が動作する
  • メモリレイアウト確認SuperSlab サイズが想定通り)

工数

2-3時間


P1.2: Free Fast Path での Header-Based Class 判定をオプション化

目的: P1.1 の class_map を free path で使えるようにし、header 依存を減らす

現状の問題

  • Free path で header から class_idx を読む
  • Header が壊れると class_idx が不正になり、crash する

変更ファイル

  • /mnt/workdisk/public_share/hakmem/core/hakmem_build_flags.h
  • /mnt/workdisk/public_share/hakmem/core/tiny_free_fast_v2.inc.h

変更内容

Stage 1: Build flag 追加
// hakmem_build_flags.h:60-70 付近に追加
// P1.2: Use out-of-band class_map for free path (instead of header)
// Default: OFF (keep header-based for backward compatibility)
// Enable: -DHAKMEM_TINY_FREE_USE_CLASSMAP=1
#ifndef HAKMEM_TINY_FREE_USE_CLASSMAP
#  define HAKMEM_TINY_FREE_USE_CLASSMAP 0
#endif
Stage 2: Free path で class_map を使用
// tiny_free_fast_v2.inc.h (L100-150 付近)

// BEFORE:
#if HAKMEM_TINY_HEADER_CLASSIDX
    uint8_t hdr = *(const uint8_t*)base;
    int class_idx = (int)(hdr & HEADER_CLASS_MASK);
#else
    // ... SuperSlab lookup でクラス判定
#endif

// AFTER:
#if HAKMEM_TINY_FREE_USE_CLASSMAP
    // P1.2: Out-of-band class lookup via class_map
    SuperSlab* ss = hak_super_lookup(ptr);
    if (__builtin_expect(!ss || ss->magic != SUPERSLAB_MAGIC, 0)) {
        goto slow_path;
    }
    int slab_idx = slab_index_for(ss, base);
    if (__builtin_expect(slab_idx < 0, 0)) {
        goto slow_path;
    }
    int class_idx = ss_slab_class(ss, slab_idx);
    if (__builtin_expect(class_idx < 0, 0)) {
        goto slow_path;
    }
#elif HAKMEM_TINY_HEADER_CLASSIDX
    // Legacy: Header-based class lookup
    uint8_t hdr = *(const uint8_t*)base;
    int class_idx = (int)(hdr & HEADER_CLASS_MASK);
#else
    // Fallback: Full SuperSlab lookup
    // ...
#endif

テスト方法

  1. Header-based モード(デフォルト)
    make clean && make hakmem.so
    ./bench/larson 4 100000 8 1000 1
    
  2. Class-map モード
    make clean && make CFLAGS="-DHAKMEM_TINY_FREE_USE_CLASSMAP=1" hakmem.so
    ./bench/larson 4 100000 8 1000 1
    
  3. A/B 性能比較
    ./bench/bench_fastpath_multi  # 両モードで実施
    

完了条件

  • HAKMEM_TINY_FREE_USE_CLASSMAP=0 でビルドが通る(デフォルト)
  • HAKMEM_TINY_FREE_USE_CLASSMAP=1 でビルドが通る
  • 両モードで Larson ベンチマークが正常終了
  • 性能差が ±5% 以内

工数

半日〜1日


P1.3: meta->active の追加と TLS alloc/free での更新

目的: meta->used を「TLS SLL を含む真の使用中ブロック数」に正確化

現状の問題

  • meta->used は slab freelist からの alloc/free 時のみ更新される
  • TLS SLL に入っているブロックは meta->used にカウントされたまま
  • → Slab が "full" に見える → 再利用されない

設計方針

  • meta->used: slab freelist から alloc した数(変更なし)
  • meta->active: 真の使用中ブロック数 = meta->used - TLS SLL count
    • TLS alloc 時に increment
    • TLS free (TLS SLL push) 時に decrement
    • Empty 判定は meta->active == 0 で行う

変更ファイル

  • /mnt/workdisk/public_share/hakmem/core/superslab/superslab_types.h
  • /mnt/workdisk/public_share/hakmem/core/tiny_alloc_fast_inline.h
  • /mnt/workdisk/public_share/hakmem/core/tiny_free_fast_v2.inc.h

変更内容

Stage 1: TinySlabMeta に active フィールド追加
// superslab_types.h:11-18
typedef struct TinySlabMeta {
    _Atomic(void*) freelist;
    _Atomic uint16_t used;      // slab freelist から alloc した数
    _Atomic uint16_t active;    // P1.3: 真の使用中ブロック数 (used - TLS SLL count)
    uint16_t capacity;
    uint8_t  class_idx;
    uint8_t  carved;
    uint8_t  owner_tid_low;
} TinySlabMeta;
Stage 2: TLS alloc 時に active を increment
// tiny_alloc_fast_inline.h (tls_sll_pop 呼び出し後)
void* ptr = tls_sll_pop(class_idx);
if (ptr) {
    // P1.3: TLS SLL から取り出した → active をインクリメント
    TinyTLSSlab* tls = &g_tls_slabs[class_idx];
    if (tls->meta) {
        atomic_fetch_add_explicit(&tls->meta->active, 1, memory_order_relaxed);
    }
    return ptr;
}
Stage 3: TLS free 時に active を decrement
// tiny_free_fast_v2.inc.h (tls_sll_push 呼び出し後)
if (tls_sll_push(class_idx, base)) {
    // P1.3: TLS SLL に push した → active をデクリメント
    TinyTLSSlab* tls = &g_tls_slabs[class_idx];
    if (tls->meta) {
        uint16_t old_active = atomic_fetch_sub_explicit(&tls->meta->active, 1, memory_order_relaxed);

        // IMPORTANT: active が 0 になったら EMPTY マーク
        if (old_active == 1) {  // old_active - 1 = 0
            ss_mark_slab_empty(tls->ss, tls->slab_idx);
        }
    }
    return;
}
Stage 4: Empty 判定を active ベースに変更
// ss_hot_cold_box.h:37-39
static inline bool ss_is_slab_empty(const TinySlabMeta* meta) {
    // P1.3: active が 0 なら EMPTYTLS SLL も含めて全ブロック free
    uint16_t act = atomic_load_explicit(&meta->active, memory_order_relaxed);
    return (meta->capacity > 0 && act == 0);
}

テスト方法

  1. meta->active の動作確認
    HAKMEM_TINY_SLL_DIAG=1 ./bench/bench_tiny 32
    # TLS SLL push/pop で active が増減することを確認
    
  2. Empty 検出の正確性テスト
    # すべてのブロックを free → active == 0 → EMPTY マーク
    ./test/test_empty_detection
    
  3. Larson マルチスレッド
    ./bench/larson 4 100000 8 1000 1
    

完了条件

  • TinySlabMeta_Atomic uint16_t active が追加される
  • TLS alloc で active++
  • TLS free で active--
  • active == 0 で EMPTY マークされる
  • Larson ベンチマークが正常終了
  • メモリリークなし

工数

1日


P1.4: ENV Cleanup - Free Use Classmap

目的: P1.2 で追加した build flag を ENV 変数化runtime 切り替え可能に)

変更ファイル

  • /mnt/workdisk/public_share/hakmem/core/hakmem_build_flags.h(削除)
  • /mnt/workdisk/public_share/hakmem/core/tiny_free_fast_v2.inc.h

変更内容

// P1.4: Runtime ENV for class lookup strategy
static inline int free_use_classmap_enabled(void) {
    static int g_use_classmap = -1;
    if (__builtin_expect(g_use_classmap == -1, 0)) {
        const char* env = getenv("HAKMEM_TINY_FREE_USE_CLASSMAP");
        g_use_classmap = (env && *env != '0') ? 1 : 0;  // デフォルト OFF
        fprintf(stderr, "[FREE_CLASSMAP] %s\n", g_use_classmap ? "ENABLED" : "DISABLED");
    }
    return g_use_classmap;
}

完了条件

  • Build flag が ENV 変数に置き換えられる
  • Runtime で切り替え可能
  • ENV ドキュメントに追加

工数

1-2時間


P2: TLS SLL 再設計(中期)

目標: TLS SLL を「ポインタ箱」と「カウント箱」に明確に分離し、meta との同期を改善 所要時間: 1-2週間 依存: P1.3 完了後

P2.1: TLS Cache Box の定義(設計ドキュメント)

目的: 新しい TLS SLL アーキテクチャを文書化

成果物

  • /mnt/workdisk/public_share/hakmem/docs/design/TLS_CACHE_BOX_DESIGN.md

内容

# TLS Cache Box 設計

## 概要
TLS SLL を 3 つの独立した「箱」に分離:
1. **Pointer Box**: ポインタのリストnext ポインタで繋がる)
2. **Count Box**: 要素数カウンタatomic
3. **Meta Sync Box**: meta->active との同期層

## 設計原則
- **単一責任**: 各 Box は 1 つの役割のみを持つ
- **明示的同期**: meta->active 更新は Sync Box の専任
- **境界明確化**: Box 間のインターフェースを厳密に定義

## データ構造
```c
typedef struct TinyTLSCacheBox {
    void*    head;             // Pointer Box: SLL head
    _Atomic uint32_t count;    // Count Box: element count
    _Atomic uint32_t meta_delta;  // Sync Box: pending meta->active updates
} TinyTLSCacheBox;

操作フロー

Alloc (Pop)

  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;

更新タイミング

// TLS SLL push 時
void tls_sll_push(int class_idx, void* ptr) {
    // ... push logic

    // P2.2: meta->tls_cached を increment
    TinyTLSSlab* tls = &g_tls_slabs[class_idx];
    if (tls->meta) {
        atomic_fetch_add_explicit(&tls->meta->tls_cached, 1, memory_order_relaxed);
    }
}

// TLS SLL pop 時
void* tls_sll_pop(int class_idx) {
    void* ptr = /* ... pop logic */;
    if (ptr) {
        // P2.2: meta->tls_cached を decrement
        TinyTLSSlab* tls = &g_tls_slabs[class_idx];
        if (tls->meta) {
            atomic_fetch_sub_explicit(&tls->meta->tls_cached, 1, memory_order_relaxed);
        }
    }
    return ptr;
}

検証

// Invariant check (debug ビルド)
// NOTE: remote_pending など Remote Box 側のカウンタを将来導入する場合は、
//       ここに + remote_pending を加えた形に拡張する(現状 remote_pending==0 前提)。
assert(meta->active + meta->tls_cached == meta->used - freelist_count);

完了条件

  • TinySlabMetatls_cached フィールド追加
  • Push/Pop で tls_cached が更新される
  • Invariant が保たれる

工数

半日〜1日


P2.3: meta->used を Deprecated に(段階的移行)

目的: meta->active を主要指標とし、meta->used は互換性のために残す

変更内容

Stage 1: Empty 判定を active ベースに統一
// すべての empty 判定箇所を変更
// BEFORE:
if (meta->used == 0) { /* ... */ }

// AFTER:
if (atomic_load_explicit(&meta->active, memory_order_relaxed) == 0) { /* ... */ }
Stage 2: コメント追加
typedef struct TinySlabMeta {
    _Atomic uint16_t used;       // DEPRECATED: Use 'active' instead (kept for compatibility)
    _Atomic uint16_t active;     // PREFERRED: True allocated block count
    _Atomic uint16_t tls_cached; // Cached in TLS SLL
    // ...
} TinySlabMeta;

完了条件

  • すべての empty 判定が meta->active ベース
  • meta->used には DEPRECATED コメント
  • ビルド・テストが通る

工数

1日


P3: Header 完全 Out-of-band長期

目標: Header を完全に SuperSlab 側に移動し、TLS SLL を簡素化 所要時間: 2-3週間 依存: P2 完了後

P3.1: Header Out-of-band 化の設計ドキュメント

目的: mimalloc/tcmalloc スタイルの header 管理を設計

成果物

  • /mnt/workdisk/public_share/hakmem/docs/design/HEADER_OUTOFBAND_DESIGN.md

内容

# Header Out-of-band 設計

## 目標
- User data から header を完全に分離
- Pointer → Header のマッピングを O(1) で実現

## アプローチ
### Option A: SuperSlab 内に header 配列
```c
typedef struct SuperSlab {
    // ...
    uint8_t slab_headers[SLABS_PER_SUPERSLAB_MAX][MAX_BLOCKS_PER_SLAB];
} SuperSlab;

Option B: Separate header region (mimalloc style)

  • Header を別の mmap 領域に配置
  • Pointer masking で header region を計算

推奨: Option Aシンプル、キャッシュ局所性


#### 工数
**小3-4時間**

---

### P3.2: SuperSlab に Header Array 追加(実装)

**目的**: Option A を実装

#### 変更ファイル
- [ ] `/mnt/workdisk/public_share/hakmem/core/superslab/superslab_types.h`

#### 変更内容
```c
typedef struct SuperSlab {
    // ...

    // P3.2: Out-of-band headers (per slab, per block)
    // NOTE: C0-C6 は小ブロック → header array 必要
    //       C7 (2048B) は大きいので header なしでも可
    uint8_t* header_arrays[SLABS_PER_SUPERSLAB_MAX];  // Per-slab header pointers

    TinySlabMeta slabs[SLABS_PER_SUPERSLAB_MAX];
} SuperSlab;

// SuperSlab 初期化時に header 配列を 1 回だけ確保し、SuperSlab 解放時に必ず free する: // size_t blocks = blocks_per_slab_for_tiny_class(class_idx); // ss->header_arrays[slab_idx] = malloc(blocks * sizeof(uint8_t)); // memset(ss->header_arrays[slab_idx], 0, blocks); // … // // SuperSlab 破棄時: // free(ss->header_arrays[slab_idx]);

Alloc 時の header 設定

void* ptr = /* ... carve or pop */;
int block_idx = (ptr - slab_base) / block_size;
ss->header_arrays[slab_idx][block_idx] = HEADER_MAGIC | class_idx;

完了条件

  • SuperSlab に header_arrays 追加
  • header_arrays[slab_idx] の確保/解放パスが SuperSlab のライフサイクルに紐づいている
  • Alloc 時に header 設定
  • Free 時に header 読み取り

工数

2-3日


P3.3: TLS SLL の next ポインタを offset 0 に統一

目的: Header が out-of-band になったので、next ポインタを常に base[0] に格納

変更ファイル

  • /mnt/workdisk/public_share/hakmem/core/tiny_nextptr.h

変更内容

static inline size_t tiny_next_off(int class_idx) {
    // P3.3: Header out-of-band → 全クラス offset 0
    (void)class_idx;
    return 0u;
}

完了条件

  • tiny_next_off() が常に 0 を返す
  • TLS SLL 操作が簡素化される
  • すべてのテストが通る

工数

2-3時間


P3.4: HAKMEM_TINY_HEADER_CLASSIDX フラグの削除

目的: Out-of-band 化が完了したので、古いフラグを削除

変更ファイル

  • /mnt/workdisk/public_share/hakmem/core/hakmem_build_flags.h(削除)
  • すべての #if HAKMEM_TINY_HEADER_CLASSIDX を削除

完了条件

  • フラグが完全に削除される
  • ビルドが通る
  • 全ベンチマークが正常動作

工数

1日


依存関係図

P0.1 (C0 → 16B)
  │
  v
P0.2 (next_off 統一) ─────┐
  │                        │
  v                        v
P0.3 (Slab Reuse Guard)  P1.1 (class_map)
  │                        │
  v                        v
P0.4 (ENV Doc)           P1.2 (Free Classmap) ─┐
                           │                     │
                           v                     │
                         P1.3 (meta->active) ───┤
                           │                     │
                           v                     v
                         P1.4 (ENV)            P2.1 (Design Doc)
                                                 │
                                                 v
                                               P2.2 (tls_cached)
                                                 │
                                                 v
                                               P2.3 (Deprecate used)
                                                 │
                                                 v
                                               P3.1 (Design Doc)
                                                 │
                                                 v
                                               P3.2 (Header Array)
                                                 │
                                                 v
                                               P3.3 (next_off = 0)
                                                 │
                                                 v
                                               P3.4 (Flag Cleanup)

クリティカルパス

P0.1 → P0.2 → P0.3 → P1.1 → P1.2 → P1.3 → P2.2 → P2.3 → P3.2 → P3.3 → P3.4

Total: 約 3-4 週間

並行可能なタスク

  • P0.4 (ENV Doc) は P0.3 と並行可能
  • P1.4 (ENV) は P1.2 と並行可能
  • P2.1 (Design) は P1.3 と並行可能
  • P3.1 (Design) は P2.2 と並行可能

工数サマリー

Phase タスク 工数 累積
P0
P0.1 C0 → 16B 2-3h 3h
P0.2 next_off 統一 2-3h 6h
P0.3 Slab Reuse Guard 0.5-1d 1.5d
P0.4 ENV Doc 15m 1.5d
P0 合計 1.5-2日
P1
P1.1 class_map 2-3h 2d
P1.2 Free Classmap 0.5-1d 3d
P1.3 meta->active 1d 4d
P1.4 ENV 1-2h 4.5d
P1 合計 3-5日
P2
P2.1 Design Doc 2-3h 5d
P2.2 tls_cached 0.5-1d 6d
P2.3 Deprecate used 1d 7d
P2 合計 1-2週間
P3
P3.1 Design Doc 3-4h 7.5d
P3.2 Header Array 2-3d 10.5d
P3.3 next_off = 0 2-3h 11d
P3.4 Flag Cleanup 1d 12d
P3 合計 2-3週間
総合計 4-6週間

テスト計画

Phase 完了時のテストマトリクス

テスト P0 P1 P2 P3
ビルド確認
make clean && make hakmem.so
単体テスト
bench_tiny (全クラス)
bench_tiny_all_classes
統合テスト
Larson (2 threads)
Larson (4 threads)
cache-scratch
性能ベンチマーク
bench_fastpath_multi
bench_fastpath_sll
診断
HAKMEM_TINY_SLL_DIAG=1
Valgrind memcheck -
回帰テスト
mimalloc-bench suite - -

性能ベースライン

各 Phase 開始前に以下を記録:

# Baseline 記録
./bench/bench_fastpath_multi > baseline_pN.txt
./bench/larson 4 100000 8 1000 1 > baseline_larson_pN.txt

# Phase 完了後に比較
./bench/bench_fastpath_multi > result_pN.txt
diff baseline_pN.txt result_pN.txt

許容性能劣化: ±5% 以内


リスクと軽減策

P0 リスク

リスク 影響 確率 軽減策
P0.1: C0 昇格で性能劣化 8B 割り当てが少ない(大半は 16B+)ため影響小。ベンチマークで確認。
P0.2: C7 で user data corruption P0.1 で C0 を 16B に昇格済み。C7 も 2048B で十分な余裕あり。
P0.3: Drain overhead ENV で無効化可能。Drain 頻度は低いEMPTY slab 取得時のみ)。

P1 リスク

リスク 影響 確率 軽減策
P1.1: メモリオーバーヘッド class_map は 32 bytes/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 ロールバック

git checkout master
git branch -D p0-defensive-fixes
# 既存の設定に戻る(変更なし)

P1 ロールバック

git checkout master
git branch -D p1-outofband-foundation

# ENV で無効化
export HAKMEM_TINY_FREE_USE_CLASSMAP=0

P2 ロールバック

git checkout master
git branch -D p2-tls-redesign

# meta->active を無視し、meta->used を使用(コード変更不要)

P3 ロールバック

git checkout master
git branch -D p3-header-outofband

# HAKMEM_TINY_HEADER_CLASSIDX=1 に戻す

次のステップ

即座に開始可能

  1. P0.1: C0(8B) を 16B に昇格
  2. P0.2: tiny_next_off を全クラス 1 に

P0 完了後

  1. Larson ベンチマークで double-free が解消されるか確認
  2. P1.1 に進む

P1 完了後

  1. mimalloc-bench suite で性能回帰チェック
  2. 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 (推奨)