# 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 では Magazine(TLS 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 を構築するコストが高い **コード**: ```c // 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]` の二重アクセス **コード**: ```c SuperSlab* ss = g_tls_superslab[class_idx]; int slab_idx = g_tls_slab_idx[class_idx]; ``` **対策**: - TLS 変数を struct にまとめる(cache locality 向上) - slab pointer を直接キャッシュ ### 仮説 4: Magazine との競合 **問題**: 既存の Magazine(TLS cache)が十分高速で、SuperSlab の恩恵が無い **確認方法**: - Magazine を無効化して測定 - SuperSlab のみで測定 --- ## 💻 実装詳細 ### 1. TLS 変数追加 ```c // 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. 環境変数読み込み ```c // 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 ```c // 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() への統合 ```c // 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) ```c // 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) ```c // 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) ```c // 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 変数の統合** ```c 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. Magazine(TLS 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 最適化)