Files
hakmem/docs/status/PHASE_6.23_RESULTS_2025_10_24.md
Moe Charm (CI) 52386401b3 Debug Counters Implementation - Clean History
Major Features:
- Debug counter infrastructure for Refill Stage tracking
- Free Pipeline counters (ss_local, ss_remote, tls_sll)
- Diagnostic counters for early return analysis
- Unified larson.sh benchmark runner with profiles
- Phase 6-3 regression analysis documentation

Bug Fixes:
- Fix SuperSlab disabled by default (HAKMEM_TINY_USE_SUPERSLAB)
- Fix profile variable naming consistency
- Add .gitignore patterns for large files

Performance:
- Phase 6-3: 4.79 M ops/s (has OOM risk)
- With SuperSlab: 3.13 M ops/s (+19% improvement)

This is a clean repository without large log files.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 12:31:14 +09:00

10 KiB
Raw Blame History

Phase 6.23: SuperSlab Allocation 実装

日付: 2025-10-24 ステータス: ⚠️ 実装完了、性能低下発生 目標: SuperSlab allocation path 実装 → +10-15% 期待 実績: -5.2% 性能低下4 threads


📋 Summary

Phase 6.23 では、SuperSlab の allocation path を実装し、mimalloc-style の高速 allocation を目指しました。しかし、ベンチマーク結果は 予想に反して -5.2% の性能低下 となりました。

実装内容

  1. SuperSlab allocation helper 関数実装
  2. hak_tiny_alloc() への SuperSlab fast path 統合
  3. Same-thread fast path 実装free
  4. 環境変数 HAKMEM_TINY_USE_SUPERSLAB=1 で切り替え可能
  5. ビルド成功、動作確認完了

Benchmark 結果

Single-threaded (1T)

Allocator Throughput vs Baseline
Phase 6.22-B (SuperSlab OFF) 76.47 M ops/sec baseline
Phase 6.23 (SuperSlab ON) 76.16 M ops/sec -0.4%

結論: Single-threaded では MagazineTLS cacheが効いているため、差はほぼ無し。

Multi-threaded (4T)

Allocator Throughput vs Baseline
Phase 6.22-B (SuperSlab OFF) 268.21 M ops/sec baseline
Phase 6.23 (SuperSlab ON) 254.15 M ops/sec -5.2%

結論: Multi-threaded で明確な性能低下が発生。


🔍 性能低下の原因分析

仮説 1: 2MB mmap のオーバーヘッド

問題: 毎回 2MBまたは 4MBを mmap している可能性

確認方法:

  • SuperSlab allocation の頻度を確認
  • slab の再利用率を測定

対策:

  • SuperSlab の再利用を改善
  • Global pool から SuperSlab を取得

仮説 2: freelist 初期化のオーバーヘッド

問題: superslab_init_slab() で freelist を構築するコストが高い

コード:

// hakmem_tiny_superslab.c:138-143
for (int i = capacity - 1; i >= 0; i--) {
    void* block = (char*)slab_start + (i * block_size);
    *(void**)block = freelist;  // intrusive linked list
    freelist = block;
}

対策:

  • Lazy initialization最初の allocation 時に freelist 構築)
  • Batch initialization複数 slab を一度に初期化)

仮説 3: TLS lookup のオーバーヘッド

問題: g_tls_superslab[class_idx]g_tls_slab_idx[class_idx] の二重アクセス

コード:

SuperSlab* ss = g_tls_superslab[class_idx];
int slab_idx = g_tls_slab_idx[class_idx];

対策:

  • TLS 変数を struct にまとめるcache locality 向上)
  • slab pointer を直接キャッシュ

仮説 4: Magazine との競合

問題: 既存の MagazineTLS cacheが十分高速で、SuperSlab の恩恵が無い

確認方法:

  • Magazine を無効化して測定
  • SuperSlab のみで測定

💻 実装詳細

1. TLS 変数追加

// Phase 6.23: SuperSlab support (hakmem_tiny.c:72-75)
static int g_use_superslab = 0;  // Runtime toggle
static __thread SuperSlab* g_tls_superslab[TINY_NUM_CLASSES];
static __thread int g_tls_slab_idx[TINY_NUM_CLASSES];

2. 環境変数読み込み

// hakmem_tiny.c:500-504
char* superslab_env = getenv("HAKMEM_TINY_USE_SUPERSLAB");
if (superslab_env && atoi(superslab_env) != 0) {
    g_use_superslab = 1;
}

3. SuperSlab allocation helper

// hakmem_tiny.c:879-949

// Allocate from freelist
static inline void* superslab_alloc_from_slab(SuperSlab* ss, int slab_idx) {
    TinySlabMeta* meta = &ss->slabs[slab_idx];
    if (!meta->freelist) return NULL;

    void* block = meta->freelist;
    meta->freelist = *(void**)block;  // Pop from freelist
    meta->used++;
    return block;
}

// Refill TLS SuperSlab
static SuperSlab* superslab_refill(int class_idx) {
    // Check if current SuperSlab has unused slabs
    SuperSlab* ss = g_tls_superslab[class_idx];
    if (ss && ss->active_slabs < SLABS_PER_SUPERSLAB) {
        int free_idx = superslab_find_free_slab(ss);
        if (free_idx >= 0) {
            superslab_init_slab(ss, free_idx, ...);
            g_tls_slab_idx[class_idx] = free_idx;
            return ss;
        }
    }

    // Allocate new SuperSlab (2MB aligned)
    ss = superslab_allocate((uint8_t)class_idx);
    if (!ss) return NULL;

    superslab_init_slab(ss, 0, ...);
    g_tls_superslab[class_idx] = ss;
    g_tls_slab_idx[class_idx] = 0;
    return ss;
}

// SuperSlab-based allocation
static inline void* hak_tiny_alloc_superslab(int class_idx) {
    SuperSlab* ss = g_tls_superslab[class_idx];
    int slab_idx = g_tls_slab_idx[class_idx];

    if (ss && slab_idx >= 0 && slab_idx < SLABS_PER_SUPERSLAB) {
        void* block = superslab_alloc_from_slab(ss, slab_idx);
        if (block) return block;  // Fast path
    }

    // Slow path: Refill
    ss = superslab_refill(class_idx);
    if (!ss) return NULL;

    slab_idx = g_tls_slab_idx[class_idx];
    return superslab_alloc_from_slab(ss, slab_idx);
}

4. hak_tiny_alloc() への統合

// hakmem_tiny.c:539-551
// Phase 6.23: SuperSlab fast path
if (g_use_superslab) {
    void* ptr = hak_tiny_alloc_superslab(class_idx);
    if (ptr) {
        // Update statistics (sampled)
        t_tiny_rng ^= ...;
        if ((t_tiny_rng & ...) == 0u) {
            g_tiny_pool.alloc_count[class_idx]++;
        }
        return ptr;
    }
    // Fallback to regular path if failed
}

5. Same-thread fast path (free)

// hakmem_tiny.c:952-972
static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
    int slab_idx = ptr_to_slab_index(ptr);
    TinySlabMeta* meta = &ss->slabs[slab_idx];

    // Same-thread check
    uint32_t my_tid = (uint32_t)(uintptr_t)pthread_self();
    if (meta->owner_tid == my_tid) {
        // Fast path: Direct freelist push
        *(void**)ptr = meta->freelist;
        meta->freelist = ptr;
        meta->used--;
    } else {
        // Slow path: Remote free
        // TODO: Implement per-thread remote free queues
        *(void**)ptr = meta->freelist;
        meta->freelist = ptr;
        meta->used--;
    }
}

🧪 Benchmark 詳細

Test 1: bench_tiny (single-threaded)

// 10M iterations x 100 alloc/free pairs = 2B operations
for (int iter = 0; iter < 10000000; iter++) {
    for (int i = 0; i < 100; i++) {
        ptrs[i] = malloc(16);  // 16B allocation
    }
    for (int i = 0; i < 100; i++) {
        free(ptrs[i]);
    }
}

結果: 76.47 → 76.16 M ops/sec (-0.4%)

Test 2: bench_tiny_mt (multi-threaded, 4 threads)

// 1M iterations/thread x 100 alloc/free pairs x 4 threads = 800M operations
void* worker() {
    for (int iter = 0; iter < 1000000; iter++) {
        for (int i = 0; i < 100; i++) {
            ptrs[i] = malloc(16);
        }
        for (int i = 0; i < 100; i++) {
            free(ptrs[i]);
        }
    }
}

結果: 268.21 → 254.15 M ops/sec (-5.2%)


🚧 Next Steps: Phase 6.24

Priority 1: 性能低下の原因特定

  1. Profiling

    • perf で hotspot を特定
    • SuperSlab refill の頻度を測定
    • freelist 初期化のコストを測定
  2. Magazine 無効化テスト

    • Magazine を無効化して SuperSlab の真の性能を測定
    • HAKMEM_TINY_MAG_CAP=0 で測定
  3. SuperSlab allocation 頻度の確認

    • どれくらいの頻度で新しい SuperSlab を allocate しているか
    • Slab の再利用率

Priority 2: 最適化候補

  1. freelist 初期化の遅延化

    • 最初の allocation 時に freelist を構築
    • または pre-allocated freelist を使用
  2. TLS 変数の統合

    typedef struct {
        SuperSlab* ss;
        int slab_idx;
        TinySlabMeta* meta;  // direct cache
    } TinyTLSSlab;
    static __thread TinyTLSSlab g_tls_slabs[TINY_NUM_CLASSES];
    
  3. SuperSlab の再利用

    • Global pool から empty SuperSlab を取得
    • munmap を遅延化
  4. Remote free の最適化

    • Per-thread remote queue の実装
    • Lock-free queue

📊 Performance Comparison

Phase 6.20 → 6.23 推移 (Multi-threaded 4T)

Phase Throughput vs 6.20
6.20 (baseline) ? M ops/sec -
6.22-A ? M ops/sec -
6.22-B 268.21 M ops/sec -
6.23 (SuperSlab ON) 254.15 M ops/sec -5.2%

Note: Phase 6.20-6.22 の multi-threaded benchmark データは未測定。


🎓 Lessons Learned

1. MagazineTLS cacheの重要性

既存の Magazine が非常に高速で、SuperSlab の恩恵が少ない。mimalloc は Magazine を持たず、SuperSlab からの直接 allocation が主流。hakmem は Magazine + SuperSlab の hybrid になっている。

2. 2MB mmap のコスト

2MBまたは 4MBの mmap は思ったより重い可能性。Global pool から再利用する仕組みが必要。

3. freelist 初期化のコスト

64KB slab を 16B blocks に分割すると 4096 blocks。これを freelist として初期化するコストが無視できない。

4. 段階的な最適化の重要性

一度に全機能を実装するのではなく、段階的に最適化して性能を確認すべきだった。


📂 File Changes

新規ファイル

ファイル 内容
bench_tiny.c Single-threaded Tiny benchmark
bench_tiny_mt.c Multi-threaded Tiny benchmark
bench_allocators.c Restored from old commit
test_hakmem.c Restored from old commit

変更ファイル

ファイル 変更内容
hakmem_tiny.c SuperSlab allocation path 追加 (lines 72-75, 500-504, 539-551, 874-972)

合計

  • 新規: 4 ファイル
  • 変更: 1 ファイル, ~150 行追加

🎯 Conclusion

Phase 6.23 では、SuperSlab allocation path の実装に成功しましたが、性能は -5.2% 低下 という予想外の結果となりました。

主な原因(仮説)

  1. 2MB mmap のオーバーヘッド
  2. freelist 初期化のコスト
  3. TLS lookup のオーバーヘッド
  4. Magazine との競合

次のアクション

Phase 6.24 で原因を特定し、最適化を実施します。特に:

  • Profiling による hotspot 特定
  • freelist 初期化の遅延化
  • SuperSlab の再利用改善

作成日: 2025-10-24 12:20 JST ステータス: ⚠️ 性能低下、要改善 次のフェーズ: Phase 6.24 (SuperSlab 最適化)