Files
hakmem/docs/status/PHASE_6.23_RESULTS_2025_10_24.md

388 lines
10 KiB
Markdown
Raw Normal View 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 を構築するコストが高い
**コード**:
```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 との競合
**問題**: 既存の MagazineTLS 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. 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 最適化)