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>
10 KiB
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% の性能低下 となりました。
実装内容
- ✅ SuperSlab allocation helper 関数実装
- ✅
hak_tiny_alloc()への SuperSlab fast path 統合 - ✅ Same-thread fast path 実装(free)
- ✅ 環境変数
HAKMEM_TINY_USE_SUPERSLAB=1で切り替え可能 - ✅ ビルド成功、動作確認完了
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 を構築するコストが高い
コード:
// 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 との競合
問題: 既存の Magazine(TLS 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: 性能低下の原因特定
-
Profiling
- perf で hotspot を特定
- SuperSlab refill の頻度を測定
- freelist 初期化のコストを測定
-
Magazine 無効化テスト
- Magazine を無効化して SuperSlab の真の性能を測定
HAKMEM_TINY_MAG_CAP=0で測定
-
SuperSlab allocation 頻度の確認
- どれくらいの頻度で新しい SuperSlab を allocate しているか
- Slab の再利用率
Priority 2: 最適化候補
-
freelist 初期化の遅延化
- 最初の allocation 時に freelist を構築
- または pre-allocated freelist を使用
-
TLS 変数の統合
typedef struct { SuperSlab* ss; int slab_idx; TinySlabMeta* meta; // direct cache } TinyTLSSlab; static __thread TinyTLSSlab g_tls_slabs[TINY_NUM_CLASSES]; -
SuperSlab の再利用
- Global pool から empty SuperSlab を取得
- munmap を遅延化
-
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% 低下 という予想外の結果となりました。
主な原因(仮説)
- 2MB mmap のオーバーヘッド
- freelist 初期化のコスト
- TLS lookup のオーバーヘッド
- Magazine との競合
次のアクション
Phase 6.24 で原因を特定し、最適化を実施します。特に:
- Profiling による hotspot 特定
- freelist 初期化の遅延化
- SuperSlab の再利用改善
作成日: 2025-10-24 12:20 JST ステータス: ⚠️ 性能低下、要改善 次のフェーズ: Phase 6.24 (SuperSlab 最適化)