290 lines
7.2 KiB
Markdown
290 lines
7.2 KiB
Markdown
|
|
# Phase 6.12: Tiny Pool - 超小サイズ専用アロケータ
|
|||
|
|
|
|||
|
|
**Date**: 2025-10-21
|
|||
|
|
**Status**: 実装中
|
|||
|
|
**優先度**: P1(最初に実装)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 目標
|
|||
|
|
|
|||
|
|
**≤1KB allocations 専用の固定サイズスラブアロケータ**
|
|||
|
|
|
|||
|
|
**期待効果**:
|
|||
|
|
- mimalloc比 -10-20% (tiny allocations)
|
|||
|
|
- L1キャッシュヒット率向上
|
|||
|
|
- メタデータオーバーヘッド削減
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📊 サイズクラス設計
|
|||
|
|
|
|||
|
|
### 8つの固定サイズクラス
|
|||
|
|
|
|||
|
|
| Class | Size | Blocks/Slab (64KB) | Bitmap (uint64_t) |
|
|||
|
|
|-------|------|-------------------|-------------------|
|
|||
|
|
| 0 | 8B | 8192 | 128 |
|
|||
|
|
| 1 | 16B | 4096 | 64 |
|
|||
|
|
| 2 | 32B | 2048 | 32 |
|
|||
|
|
| 3 | 64B | 1024 | 16 |
|
|||
|
|
| 4 | 128B | 512 | 8 |
|
|||
|
|
| 5 | 256B | 256 | 4 |
|
|||
|
|
| 6 | 512B | 128 | 2 |
|
|||
|
|
| 7 | 1KB | 64 | 1 |
|
|||
|
|
|
|||
|
|
**Bitmap計算**: blocks/slab ÷ 64 = bitmap配列サイズ
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🏗️ データ構造
|
|||
|
|
|
|||
|
|
### TinySlab (スラブ単位)
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
typedef struct TinySlab {
|
|||
|
|
void* base; // スラブのベースアドレス (64KB aligned)
|
|||
|
|
uint64_t* bitmap; // 空きブロックのビットマップ(動的サイズ)
|
|||
|
|
uint16_t free_count; // 空きブロック数
|
|||
|
|
uint16_t total_count; // 総ブロック数
|
|||
|
|
uint8_t class_idx; // サイズクラスインデックス (0-7)
|
|||
|
|
uint8_t _padding[5];
|
|||
|
|
struct TinySlab* next; // 次のスラブ
|
|||
|
|
} TinySlab;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### TinyPool (グローバル状態)
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
typedef struct {
|
|||
|
|
TinySlab* slabs[8]; // 各クラスのスラブリスト(空きがあるスラブのみ)
|
|||
|
|
TinySlab* full_slabs[8]; // 満杯スラブリスト(将来のfree用)
|
|||
|
|
uint64_t alloc_count[8]; // 各クラスのallocation統計
|
|||
|
|
uint64_t free_count[8]; // 各クラスのfree統計
|
|||
|
|
uint64_t slab_count[8]; // 各クラスのスラブ数
|
|||
|
|
} TinyPool;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ⚡ アルゴリズム
|
|||
|
|
|
|||
|
|
### Allocation (O(1) 高速パス)
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
void* hak_tiny_alloc(size_t size) {
|
|||
|
|
// 1. サイズ → クラスインデックス(branchless LUT)
|
|||
|
|
int class_idx = tiny_size_to_class(size);
|
|||
|
|
if (class_idx < 0) return NULL; // >1KB
|
|||
|
|
|
|||
|
|
// 2. 空きスラブを取得
|
|||
|
|
TinySlab* slab = g_tiny_pool.slabs[class_idx];
|
|||
|
|
if (!slab) {
|
|||
|
|
// 新しいスラブを確保
|
|||
|
|
slab = allocate_new_slab(class_idx);
|
|||
|
|
if (!slab) return NULL;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. ビットマップから空きブロックを検索 (__builtin_ctzll)
|
|||
|
|
int block_idx = find_free_block(slab);
|
|||
|
|
if (block_idx < 0) {
|
|||
|
|
// スラブが満杯 → 次のスラブへ
|
|||
|
|
move_to_full_list(class_idx, slab);
|
|||
|
|
return hak_tiny_alloc(size); // リトライ
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 4. ビットマップをセット、ポインタを返す
|
|||
|
|
set_block_used(slab, block_idx);
|
|||
|
|
slab->free_count--;
|
|||
|
|
|
|||
|
|
size_t block_size = g_tiny_class_sizes[class_idx];
|
|||
|
|
void* ptr = (char*)slab->base + (block_idx * block_size);
|
|||
|
|
|
|||
|
|
g_tiny_pool.alloc_count[class_idx]++;
|
|||
|
|
return ptr;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Free (O(1) 高速パス)
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
void hak_tiny_free(void* ptr) {
|
|||
|
|
// 1. ポインタ → スラブ(64KB境界でアライン)
|
|||
|
|
uintptr_t addr = (uintptr_t)ptr;
|
|||
|
|
uintptr_t slab_base = addr & ~(SLAB_SIZE - 1); // 64KB境界
|
|||
|
|
TinySlab* slab = find_slab_by_base(slab_base);
|
|||
|
|
|
|||
|
|
if (!slab) return; // Tiny Pool外
|
|||
|
|
|
|||
|
|
// 2. ブロックインデックスを計算
|
|||
|
|
size_t block_size = g_tiny_class_sizes[slab->class_idx];
|
|||
|
|
int block_idx = (addr - slab_base) / block_size;
|
|||
|
|
|
|||
|
|
// 3. ビットマップをクリア
|
|||
|
|
clear_block_used(slab, block_idx);
|
|||
|
|
slab->free_count++;
|
|||
|
|
|
|||
|
|
// 4. スラブが満杯→空きリストへ移動
|
|||
|
|
if (slab->free_count == 1 && slab->total_count > 1) {
|
|||
|
|
move_to_free_list(slab->class_idx, slab);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 5. スラブが完全に空 → 解放(またはキャッシュ)
|
|||
|
|
if (slab->free_count == slab->total_count) {
|
|||
|
|
release_slab(slab);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
g_tiny_pool.free_count[slab->class_idx]++;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔍 ビットマップ操作(高速化の肝)
|
|||
|
|
|
|||
|
|
### 空きブロック検索
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
static inline int find_free_block(TinySlab* slab) {
|
|||
|
|
int bitmap_size = (slab->total_count + 63) / 64;
|
|||
|
|
|
|||
|
|
for (int i = 0; i < bitmap_size; i++) {
|
|||
|
|
uint64_t bits = ~slab->bitmap[i]; // 0=空き, 1=使用中
|
|||
|
|
if (bits != 0) {
|
|||
|
|
// 最下位の0ビット(空きブロック)を検索
|
|||
|
|
int bit_idx = __builtin_ctzll(bits);
|
|||
|
|
return i * 64 + bit_idx;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return -1; // 空きなし
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### ビット設定/クリア
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
static inline void set_block_used(TinySlab* slab, int block_idx) {
|
|||
|
|
int word_idx = block_idx / 64;
|
|||
|
|
int bit_idx = block_idx % 64;
|
|||
|
|
slab->bitmap[word_idx] |= (1ULL << bit_idx);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static inline void clear_block_used(TinySlab* slab, int block_idx) {
|
|||
|
|
int word_idx = block_idx / 64;
|
|||
|
|
int bit_idx = block_idx % 64;
|
|||
|
|
slab->bitmap[word_idx] &= ~(1ULL << bit_idx);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🚀 統合方法
|
|||
|
|
|
|||
|
|
### hakmem.c への統合
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
void* hak_alloc_at(size_t size, uintptr_t site) {
|
|||
|
|
// Phase 6.12: Tiny Pool 優先パス
|
|||
|
|
if (size <= 1024) {
|
|||
|
|
void* ptr = hak_tiny_alloc(size);
|
|||
|
|
if (ptr) return ptr;
|
|||
|
|
// fallback to L2 Pool
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 既存のL2 Pool / BigCache / malloc/mmap パス
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void hak_free(void* ptr) {
|
|||
|
|
if (!ptr) return;
|
|||
|
|
|
|||
|
|
// Phase 6.12: Tiny Pool チェック
|
|||
|
|
if (hak_tiny_is_managed(ptr)) {
|
|||
|
|
hak_tiny_free(ptr);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 既存のL2 Pool / BigCache / malloc/mmap パス
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📈 期待される効果
|
|||
|
|
|
|||
|
|
### ベンチマーク見込み
|
|||
|
|
|
|||
|
|
| Scenario | Before | After | Improvement |
|
|||
|
|
|----------|--------|-------|-------------|
|
|||
|
|
| **tiny** (8-64B) | +30% vs mimalloc | **-5%** | -35% |
|
|||
|
|
| **small** (128-512B) | +15% vs mimalloc | **-10%** | -25% |
|
|||
|
|
| **json** (64KB) | +0.3% vs mimalloc | **-2%** | -2.3% |
|
|||
|
|
|
|||
|
|
**総合効果**: mimalloc比 -10-20% (tiny allocations中心のワークロード)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔧 実装ファイル
|
|||
|
|
|
|||
|
|
### 新規作成
|
|||
|
|
|
|||
|
|
- `hakmem_tiny.h` (120 lines) - API定義
|
|||
|
|
- `hakmem_tiny.c` (300 lines) - スラブアロケータ実装
|
|||
|
|
|
|||
|
|
### 修正
|
|||
|
|
|
|||
|
|
- `hakmem.c` - Tiny Pool統合(hak_alloc_at, hak_free)
|
|||
|
|
- `hakmem.h` - Tiny Pool API export
|
|||
|
|
- `test_hakmem.c` - Tiny Pool テスト追加
|
|||
|
|
- `Makefile` - hakmem_tiny.o 追加
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧪 テスト計画
|
|||
|
|
|
|||
|
|
### 単体テスト
|
|||
|
|
|
|||
|
|
1. **基本動作**: 8クラス × alloc/free
|
|||
|
|
2. **スラブ遷移**: 空き → 満杯 → 空き
|
|||
|
|
3. **ビットマップ正確性**: 全ブロック exhaustive test
|
|||
|
|
4. **境界条件**: 0B, 1B, 1024B, 1025B
|
|||
|
|
|
|||
|
|
### 統合テスト
|
|||
|
|
|
|||
|
|
1. **ベンチマーク**: tiny/small/json シナリオ
|
|||
|
|
2. **ストレステスト**: 100万回 alloc/free
|
|||
|
|
3. **メモリリーク**: valgrind / AddressSanitizer
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 実装スケジュール
|
|||
|
|
|
|||
|
|
### Step 1: ヘッダー・基本実装 (30分)
|
|||
|
|
- `hakmem_tiny.h` 作成
|
|||
|
|
- `hakmem_tiny.c` 骨格作成
|
|||
|
|
- サイズクラス定義
|
|||
|
|
|
|||
|
|
### Step 2: alloc/free実装 (45分)
|
|||
|
|
- `hak_tiny_alloc()` 実装
|
|||
|
|
- `hak_tiny_free()` 実装
|
|||
|
|
- ビットマップ操作実装
|
|||
|
|
|
|||
|
|
### Step 3: 統合 (30分)
|
|||
|
|
- `hakmem.c` に統合
|
|||
|
|
- Makefile 更新
|
|||
|
|
- ビルド確認
|
|||
|
|
|
|||
|
|
### Step 4: テスト (30分)
|
|||
|
|
- 基本動作テスト作成・実行
|
|||
|
|
- ベンチマーク測定
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**Total**: 約2時間(実装 + テスト)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**Generated**: 2025-10-21
|
|||
|
|
**実装者**: Claude (ultrathink mode)
|