388 lines
10 KiB
Markdown
388 lines
10 KiB
Markdown
|
|
# 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 最適化)
|