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>
8.8 KiB
8.8 KiB
Phase 7.6: 何を「動的」にするのか?
日付: 2025-10-26 質問: "となると どこを動的にするんですかにゃ?"
🎯 結論:SuperSlabの「生と死」を動的に
Phase 7.6で動的にするもの
1つだけです: SuperSlabのライフサイクル
❌ 現状(半分だけ動的):
割当: 動的(初回アクセス時に確保)✅
解放: 固定(一度確保したら永遠に保持)❌
✅ Phase 7.6(完全動的):
割当: 動的(初回アクセス時に確保)✅
解放: 動的(空になったらOSに返却)✅ ← これを実装!
📊 現状分析:何が固定で何が動的?
現在の実装(Phase 6.24)
データ構造:
// TLS cache(スレッドごと)
static __thread TinyTLSSlab g_tls_slabs[TINY_NUM_CLASSES];
typedef struct {
SuperSlab* ss; // SuperSlab pointer
TinySlabMeta* meta; // Slab metadata cache
uint8_t slab_idx; // Current slab index
} TinyTLSSlab;
割当フロー:
malloc(16)
↓
hak_tiny_alloc_superslab(class_idx=1) // 16B = class 1
↓
TinyTLSSlab* tls = &g_tls_slabs[1];
↓
if (tls->ss == NULL || slab満杯) {
superslab_refill(1); // ← 初回アクセス時に確保(動的!)
↓
SuperSlab* ss = superslab_allocate(1); // mmap(2MB)
tls->ss = ss; // TLS cacheに保存
}
↓
return block;
解放フロー:
free(ptr)
↓
hak_tiny_free(ptr)
↓
Magazine に push // ← ここで止まる
↓
(SuperSlabへの通知なし)
↓
(OSへの返却なし)← ずっと保持!
🔍 何が「固定」で何が「動的」?
固定のもの(Phase 7.6でも変更なし)
| 項目 | 値 | 理由 |
|---|---|---|
| SuperSlabサイズ | 2MB | mimalloc準拠、最適 |
| Slabサイズ | 64KB | 2MB / 32 = 64KB |
| Slabs per SuperSlab | 32個 | 固定(bitmap効率) |
| サイズクラス | 8種(8-64B) | Tiny Pool範囲固定 |
| Magazine CAP | クラスごと固定 | 現状で十分高性能 |
これらは変更しません!
動的のもの(すでに実装済み)
| 項目 | 現状 | 実装 |
|---|---|---|
| SuperSlab割当 | ✅ 動的 | superslab_refill() で初回アクセス時 |
| Slab初期化 | ✅ 動的 | 必要になったslabのみ初期化 |
| TLS cache | ✅ 動的 | スレッドごとに自動管理 |
これらはすでに動的です!
Phase 7.6で追加する動的要素
| 項目 | 現状 | Phase 7.6後 |
|---|---|---|
| SuperSlab解放 | ❌ 固定(永遠保持) | ✅ 動的(空なら返却) |
これだけです! シンプルですね 🎯
🎨 図解:何が動的になるのか
Before Phase 7.6(現状)
時間軸 →
起動時:
g_tls_slabs[0..7] = {NULL, NULL, ...} // 初期化のみ
SuperSlabs: 0個
初回 malloc(16):
superslab_refill(1) → mmap(2MB) → SuperSlab確保 ✅
g_tls_slabs[1].ss = SuperSlab#1
SuperSlabs: 1個
100K malloc(16):
必要に応じてSuperSlab確保 ✅
SuperSlabs: 3個
100K free(ptr):
Magazineにpush → SuperSlabは保持 ❌
SuperSlabs: 3個(そのまま!)
500K malloc(16):
さらにSuperSlab確保 ✅
SuperSlabs: 7個
500K free(ptr):
Magazineにpush → SuperSlabは保持 ❌
SuperSlabs: 7個(そのまま!)
1M malloc(16):
さらにSuperSlab確保 ✅
SuperSlabs: 13個
1M free(ptr):
Magazineにpush → SuperSlabは保持 ❌
SuperSlabs: 13個(永遠に保持!)← 問題!
結果:
- 割当は動的 ✅(必要な時だけ確保)
- 解放は固定 ❌(一度確保したら永遠保持)
- メモリが無駄! 26MB常駐
After Phase 7.6(目標)
時間軸 →
起動時:
g_tls_slabs[0..7] = {NULL, NULL, ...}
SuperSlabs: 0個 ✅
初回 malloc(16):
superslab_refill(1) → mmap(2MB) ✅
SuperSlabs: 1個
100K malloc(16):
SuperSlabs: 3個 ✅
100K free(ptr):
Magazine + SuperSlab追跡 ✅
total_active_blocks -= 100K
空SuperSlab検出 → munmap(2MB) × 3 ✅
SuperSlabs: 0個(解放!)← 新機能!
500K malloc(16):
SuperSlabs: 7個 ✅
500K free(ptr):
空SuperSlab検出 → munmap × 7 ✅
SuperSlabs: 0個(解放!)
1M malloc(16):
SuperSlabs: 13個 ✅
1M free(ptr):
空SuperSlab検出 → munmap × 13 ✅
SuperSlabs: 0個(解放!)
終了時:
SuperSlabs: 0個 ✅
メモリ: ~2-3 MB(Magazineのみ)
結果:
- 割当は動的 ✅(必要な時だけ確保)
- 解放も動的 ✅(空になったら返却)← NEW!
- メモリ効率MAX! 使用中のみ保持
🎯 具体的に「動的」とは?
SuperSlabのライフサイクル
Before(半分動的):
Birth(誕生): 動的 ✅
↓ malloc時、必要になったら
superslab_allocate() → mmap(2MB)
↓
Life(生存): 使用中
↓ malloc/freeを繰り返す
blocks割当・解放
↓
Death(死): なし ❌
↓ プロセス終了まで
(永遠に保持)
After Phase 7.6(完全動的):
Birth(誕生): 動的 ✅
↓ malloc時、必要になったら
superslab_allocate() → mmap(2MB)
↓
Life(生存): 使用中
↓ malloc/freeを繰り返す
total_active_blocks の増減を追跡 ← NEW!
↓
Death(死): 動的 ✅ ← NEW!
↓ total_active_blocks == 0 になったら
superslab_free() → munmap(2MB) ← NEW!
↓
(OSへ返却、メモリ解放)
📋 Phase 7.6の実装:具体的に何をする?
Step 1-2: Magazine統合(freeの追跡)
目的: SuperSlabが「いつ空になったか」を検出
実装:
// hakmem_tiny.c:908-912(Magazine push)
if (mag->top < cap) {
mag->items[mag->top].ptr = ptr;
mag->top++;
// Phase 7.6: 追跡追加 ← NEW!
SuperSlab* ss = ptr_to_superslab(ptr);
if (ss && ss->magic == SUPERSLAB_MAGIC) {
ss->total_active_blocks--; // カウンタ減算
}
return;
}
これで:
- ✅ freeを追跡できる
- ✅
total_active_blocksが正確になる - ✅ 空検出が可能に
Step 3: 空SuperSlab解放(Deathの実装)
目的: 空になったらOSに返却
実装:
if (ss->total_active_blocks == 0) {
// 完全に空!
superslab_free(ss); // munmap(2MB) ← NEW!
g_tls_slabs[class_idx].ss = NULL; // TLS cacheクリア
}
これで:
- ✅ 空SuperSlabを解放
- ✅ OSにメモリ返却
- ✅ メモリ効率MAX
Step 4: 遅延割当(すでに実装済み)
現状:
// superslab_refill() (line 1027)
// 初回アクセス時のみ確保 ← すでに動的!
if (tls->ss == NULL) {
tls->ss = superslab_allocate(class_idx);
}
Phase 7.6で追加:
- 特になし!
- すでに遅延割当されている
でも明示的に:
// グローバル配列も管理(解放追跡用)
static SuperSlab* g_active_superslabs[TINY_NUM_CLASSES] = {NULL};
🎓 他の「動的」との違い
ACEの「動的」(Mid/Large Pool)
何が動的?
- CAP(在庫量): ヒット率で調整
- W_MAX(丸め度): UCB1学習
- しきい値: Canaryテスト
手法: 学習ベース
Phase 7.6の「動的」(Tiny Pool)
何が動的?
- SuperSlabの解放: 空検出で返却
手法: 設計ベース(Bitmapの柔軟性)
共通点:
- どちらも「必要な時だけ確保、不要になったら解放」
- でも実装手法が異なる
🚀 まとめ
Phase 7.6で動的にするもの
たった1つ:
SuperSlabの解放
具体的には:
// Before
SuperSlab確保 → 永遠保持 ❌
// After
SuperSlab確保 → 使用 → 空検出 → OS返却 ✅
これだけです!
なぜ「全部動的」と言ったのか?
「全部動的」の意味:
割当も動的 ✅(すでに実装済み)
解放も動的 ✅(Phase 7.6で追加)
= SuperSlabのライフサイクル全体が動的
= 「全部動的」
vs 中途半端(悪い例):
割当は動的 ✅
解放は固定 ❌
= 半分だけ動的
= 中途半端 ❌
他に動的にしないもの
これらは固定のまま:
- ❌ SuperSlabサイズ(2MB固定)
- ❌ サイズクラス(8種固定)
- ❌ Magazine CAP(クラスごと固定)
理由:
- すでに最適
- 動的化の価値なし
- ACEと同じ「動的1個問題」を避ける
🎯 次のステップ
やること:
- Magazine統合(Step 1-2)
- 空SuperSlab解放(Step 3)
やらないこと:
- SuperSlabサイズの動的化(不要)
- Magazine CAPの学習(不要)
- ACE統合(独立が美しい)
にゃーん!わかりましたか? 🐱
次: Step 1実装を始めましょう!