Files
hakmem/docs/design/REGISTRY_TOGGLE_DESIGN.md

647 lines
18 KiB
Markdown
Raw Normal View History

# Slab Registry ON/OFF 切り替え設計 - 完全分析レポート
**調査日**: 2025-10-22
**目的**: Registry の有無で性能が大きく変わるため、簡単に切り替えてベンチマーク比較できる仕組みを導入
---
## 📊 **現状の問題**
### パフォーマンス差Phase 6.12.1
| Pattern | 1-thread | 4-thread |
|---------|----------|----------|
| **Registry ON** | +0.8% ✅ | **-93.8%** ❌ (Race Condition) |
| **Registry OFF** | -2.9% | **-22.4%** (Cache line ping-pong) |
### 切り替えの課題
```bash
# 現状: git revert が必要
git log --oneline -5
git revert abc123
make clean && make
./bench_allocators_hakmem
# これを環境変数で切り替えたい!
HAKMEM_USE_REGISTRY=0 ./bench_allocators_hakmem
HAKMEM_USE_REGISTRY=1 ./bench_allocators_hakmem
```
---
## 🔍 **影響範囲の分析**
### Registry 実装の箇所hakmem_tiny.c
| 行番号 | 関数/変数 | 種別 | 影響 |
|--------|----------|------|------|
| 16 | `g_slab_registry[1024]` | グローバル変数 | 8KB メモリ |
| 25-92 | `registry_hash/register/unregister/lookup` | 内部関数 | 67行 |
| 128-134 | `allocate_new_slab()` | Registry登録 | 6行 |
| 147 | `release_slab()` | Registry解除 | 1行 |
| 158-166 | `hak_tiny_owner_slab()` | Registry検索 | 8行 |
| 223 | `hak_tiny_init()` | Registry初期化 | 1行 |
**合計**: ~83行のコード、6箇所の呼び出し
---
## ⚖️ **3つのパターン比較**
### Pattern 1: コンパイル時フラグ (`#ifdef`)
#### 実装案
```c
// hakmem_tiny.h
#define HAKMEM_USE_SLAB_REGISTRY 1 // 0 = OFF, 1 = ON
#if HAKMEM_USE_SLAB_REGISTRY
extern SlabRegistryEntry g_slab_registry[SLAB_REGISTRY_SIZE];
#endif
// hakmem_tiny.c
#if HAKMEM_USE_SLAB_REGISTRY
SlabRegistryEntry g_slab_registry[SLAB_REGISTRY_SIZE];
static TinySlab* registry_lookup(uintptr_t slab_base) {
// ... O(1) lookup ...
}
#endif
TinySlab* hak_tiny_owner_slab(void* ptr) {
uintptr_t slab_base = (uintptr_t)ptr & ~(TINY_SLAB_SIZE - 1);
#if HAKMEM_USE_SLAB_REGISTRY
return registry_lookup(slab_base); // O(1)
#else
// O(N) list traversal
for (int class_idx = 0; class_idx < TINY_NUM_CLASSES; class_idx++) {
for (TinySlab* slab = g_tiny_pool.free_slabs[class_idx]; slab; slab = slab->next) {
if ((uintptr_t)slab->base == slab_base) return slab;
}
for (TinySlab* slab = g_tiny_pool.full_slabs[class_idx]; slab; slab = slab->next) {
if ((uintptr_t)slab->base == slab_base) return slab;
}
}
return NULL;
#endif
}
```
#### メリット/デメリット
| 項目 | 評価 | 詳細 |
|------|------|------|
| **オーバーヘッド** | ⭐⭐⭐⭐⭐ | **0 cycles** - コンパイル時に完全に最適化 |
| **コードサイズ** | ⭐⭐⭐⭐⭐ | 最小(未使用コードは削除) |
| **ベンチマーク比較** | ⭐⭐ | **2回ビルドが必要** |
| **実装の容易さ** | ⭐⭐⭐⭐ | 6箇所に `#if` 追加 |
| **メンテナンス性** | ⭐⭐⭐ | 2つの経路を維持ビルド時検証必要 |
#### 切り替え方法
```bash
# Registry ON
make clean
sed -i 's/HAKMEM_USE_SLAB_REGISTRY 0/HAKMEM_USE_SLAB_REGISTRY 1/' hakmem_tiny.h
make bench
# Registry OFF
make clean
sed -i 's/HAKMEM_USE_SLAB_REGISTRY 1/HAKMEM_USE_SLAB_REGISTRY 0/' hakmem_tiny.h
make bench
```
---
### Pattern 2: ランタイム環境変数 (`HAKMEM_USE_REGISTRY`)
#### 実装案
```c
// hakmem_tiny.c
static int g_use_registry = 1; // Default ON (0 = OFF, 1 = ON)
void hak_tiny_init(void) {
if (g_tiny_initialized) return;
// Read environment variable
char* env = getenv("HAKMEM_USE_REGISTRY");
if (env) {
g_use_registry = atoi(env);
}
g_tiny_initialized = 1;
if (g_use_registry) {
memset(g_slab_registry, 0, sizeof(g_slab_registry));
}
// Lite P1 pre-allocation (省略)
}
TinySlab* hak_tiny_owner_slab(void* ptr) {
if (!ptr || !g_tiny_initialized) return NULL;
uintptr_t slab_base = (uintptr_t)ptr & ~(TINY_SLAB_SIZE - 1);
if (g_use_registry) {
return registry_lookup(slab_base); // O(1)
} else {
// O(N) list traversal
for (int class_idx = 0; class_idx < TINY_NUM_CLASSES; class_idx++) {
for (TinySlab* slab = g_tiny_pool.free_slabs[class_idx]; slab; slab = slab->next) {
if ((uintptr_t)slab->base == slab_base) return slab;
}
for (TinySlab* slab = g_tiny_pool.full_slabs[class_idx]; slab; slab = slab->next) {
if ((uintptr_t)slab->base == slab_base) return slab;
}
}
return NULL;
}
}
static TinySlab* allocate_new_slab(int class_idx) {
// ... (省略) ...
// Registry登録条件付き
if (g_use_registry) {
uintptr_t slab_base = (uintptr_t)aligned_mem;
if (!registry_register(slab_base, slab)) {
// Registry full - cleanup and fail
free(slab->bitmap);
free(slab->base);
free(slab);
return NULL;
}
}
return slab;
}
static void release_slab(TinySlab* slab) {
if (!slab) return;
if (g_use_registry) {
uintptr_t slab_base = (uintptr_t)slab->base;
registry_unregister(slab_base);
}
// ... (省略) ...
}
```
#### メリット/デメリット
| 項目 | 評価 | 詳細 |
|------|------|------|
| **オーバーヘッド** | ⭐⭐⭐⭐ | **~2-5 cycles** - 単純な if分岐予測可能 |
| **コードサイズ** | ⭐⭐⭐ | 両方の経路を含む(+30行程度 |
| **ベンチマーク比較** | ⭐⭐⭐⭐⭐ | **1回ビルド、環境変数で即切り替え** |
| **実装の容易さ** | ⭐⭐⭐⭐⭐ | 3箇所に `if (g_use_registry)` 追加 |
| **メンテナンス性** | ⭐⭐⭐⭐ | 両方の経路が常に実行可能CI/テストで検証容易) |
#### 切り替え方法
```bash
# 1回ビルドするだけ
make bench
# Registry ON
HAKMEM_USE_REGISTRY=1 ./bench_allocators_hakmem
# Registry OFF
HAKMEM_USE_REGISTRY=0 ./bench_allocators_hakmem
# 自動比較スクリプト
bash bench_registry_comparison.sh
```
---
### Pattern 3: 関数ポインタ切り替え
#### 実装案
```c
// hakmem_tiny.c
static TinySlab* (*g_owner_slab_impl)(void* ptr) = NULL;
static TinySlab* owner_slab_registry(void* ptr) {
uintptr_t slab_base = (uintptr_t)ptr & ~(TINY_SLAB_SIZE - 1);
return registry_lookup(slab_base); // O(1)
}
static TinySlab* owner_slab_list(void* ptr) {
uintptr_t slab_base = (uintptr_t)ptr & ~(TINY_SLAB_SIZE - 1);
// O(N) list traversal
for (int class_idx = 0; class_idx < TINY_NUM_CLASSES; class_idx++) {
for (TinySlab* slab = g_tiny_pool.free_slabs[class_idx]; slab; slab = slab->next) {
if ((uintptr_t)slab->base == slab_base) return slab;
}
for (TinySlab* slab = g_tiny_pool.full_slabs[class_idx]; slab; slab = slab->next) {
if ((uintptr_t)slab->base == slab_base) return slab;
}
}
return NULL;
}
void hak_tiny_init(void) {
if (g_tiny_initialized) return;
// Read environment variable
char* env = getenv("HAKMEM_USE_REGISTRY");
int use_registry = env ? atoi(env) : 1;
// Set function pointer (1回だけ分岐)
g_owner_slab_impl = use_registry ? owner_slab_registry : owner_slab_list;
g_tiny_initialized = 1;
if (use_registry) {
memset(g_slab_registry, 0, sizeof(g_slab_registry));
}
// ... (省略) ...
}
TinySlab* hak_tiny_owner_slab(void* ptr) {
if (!ptr || !g_tiny_initialized) return NULL;
return g_owner_slab_impl(ptr); // 間接呼び出し
}
```
#### メリット/デメリット
| 項目 | 評価 | 詳細 |
|------|------|------|
| **オーバーヘッド** | ⭐⭐⭐ | **~5-10 cycles** - 間接呼び出し(分岐予測器を使えない) |
| **コードサイズ** | ⭐⭐⭐⭐ | 両方の経路を別関数に分離(読みやすい) |
| **ベンチマーク比較** | ⭐⭐⭐⭐⭐ | **1回ビルド、環境変数で即切り替え** |
| **実装の容易さ** | ⭐⭐⭐ | 関数分離 + ポインタ初期化(やや複雑) |
| **メンテナンス性** | ⭐⭐⭐⭐⭐ | 2つの実装が完全に分離独立テスト可能 |
---
## 🎯 **推奨パターン: Pattern 2 (ランタイム環境変数)**
### 選択理由
#### 1⃣ **ベンチマーク比較の容易さ(最重要!)**
```bash
# 1回ビルド → 環境変数で即切り替え
make bench
# 自動比較スクリプトで一発測定
bash bench_registry_comparison.sh
```
ユーザーの要望「簡単に切り替えて比較」を完璧に満たす。
#### 2⃣ **オーバーヘッドが実質ゼロ**
- **if分岐のコスト**: ~2-5 cycles
- **Registry自体のコスト**: ~50-100 cycles (hash + probing)
- **O(N)探索のコスト**: ~200-500 cycles (8 classes × 2 lists)
**オーバーヘッド比**: 2-5 / 50-100 = **2-10%**(誤差範囲)
#### 3⃣ **実装が最も簡単**
```diff
+ static int g_use_registry = 1; // 1行追加
void hak_tiny_init(void) {
+ char* env = getenv("HAKMEM_USE_REGISTRY");
+ if (env) g_use_registry = atoi(env); // 2行追加
- memset(g_slab_registry, 0, sizeof(g_slab_registry));
+ if (g_use_registry) { // 1行追加
+ memset(g_slab_registry, 0, sizeof(g_slab_registry));
+ }
}
TinySlab* hak_tiny_owner_slab(void* ptr) {
- return registry_lookup(slab_base);
+ if (g_use_registry) { // 1行追加
+ return registry_lookup(slab_base);
+ } else {
+ // O(N) fallback (10行追加)
+ }
}
```
**合計**: ~15行の追加のみ
#### 4⃣ **メンテナンス性が高い**
- 両方の経路が常にコンパイルされる → ビルドエラーが即座に発覚
- CI/テストで両方をテスト可能
- デバッグも容易(環境変数を変えるだけ)
---
## 📝 **具体的な実装コードPattern 2**
### Step 1: グローバル変数追加hakmem_tiny.c
```c
// ============================================================================
// Global State
// ============================================================================
static TinyPool g_tiny_pool;
int g_tiny_initialized = 0;
// +++ 追加 +++
static int g_use_registry = 1; // Default ON (0 = OFF, 1 = ON)
// +++ ここまで +++
SlabRegistryEntry g_slab_registry[SLAB_REGISTRY_SIZE];
```
### Step 2: 初期化関数修正hak_tiny_init
```c
void hak_tiny_init(void) {
if (g_tiny_initialized) return;
// +++ 追加 +++
// Read environment variable
char* env = getenv("HAKMEM_USE_REGISTRY");
if (env) {
g_use_registry = atoi(env);
}
// +++ ここまで +++
g_tiny_initialized = 1;
// +++ 修正 +++
if (g_use_registry) {
memset(g_slab_registry, 0, sizeof(g_slab_registry));
}
// +++ ここまで +++
// Lite P1 pre-allocation (省略)
}
```
### Step 3: owner_slab 修正
```c
TinySlab* hak_tiny_owner_slab(void* ptr) {
if (!ptr || !g_tiny_initialized) return NULL;
uintptr_t slab_base = (uintptr_t)ptr & ~(TINY_SLAB_SIZE - 1);
// +++ 追加 +++
if (g_use_registry) {
return registry_lookup(slab_base); // O(1)
} else {
// O(N) list traversal fallback
for (int class_idx = 0; class_idx < TINY_NUM_CLASSES; class_idx++) {
// Check free list
for (TinySlab* slab = g_tiny_pool.free_slabs[class_idx]; slab; slab = slab->next) {
if ((uintptr_t)slab->base == slab_base) {
return slab;
}
}
// Check full list
for (TinySlab* slab = g_tiny_pool.full_slabs[class_idx]; slab; slab = slab->next) {
if ((uintptr_t)slab->base == slab_base) {
return slab;
}
}
}
return NULL; // Not found
}
// +++ ここまで +++
}
```
### Step 4: allocate_new_slab 修正
```c
static TinySlab* allocate_new_slab(int class_idx) {
// ... (省略) ...
slab->next = NULL;
// +++ 修正 +++
if (g_use_registry) {
uintptr_t slab_base = (uintptr_t)aligned_mem;
if (!registry_register(slab_base, slab)) {
// Registry full - cleanup and fail
free(slab->bitmap);
free(slab->base);
free(slab);
return NULL;
}
}
// +++ ここまで +++
g_tiny_pool.slab_count[class_idx]++;
return slab;
}
```
### Step 5: release_slab 修正
```c
static void release_slab(TinySlab* slab) {
if (!slab) return;
// +++ 修正 +++
if (g_use_registry) {
uintptr_t slab_base = (uintptr_t)slab->base;
registry_unregister(slab_base);
}
// +++ ここまで +++
free(slab->base);
free(slab->bitmap);
g_tiny_pool.slab_count[slab->class_idx]--;
free(slab);
}
```
---
## 🚀 **ベンチマーク自動化スクリプト**
### bench_registry_comparison.sh
```bash
#!/bin/bash
# Registry ON/OFF 性能比較スクリプト
set -e
# Build benchmark
echo "Building benchmark..."
make bench
# Output file
RESULT_FILE="registry_comparison_$(date +%Y%m%d_%H%M%S).txt"
echo "========================================" | tee -a "$RESULT_FILE"
echo "Slab Registry ON/OFF Comparison" | tee -a "$RESULT_FILE"
echo "Date: $(date)" | tee -a "$RESULT_FILE"
echo "========================================" | tee -a "$RESULT_FILE"
# Registry ON
echo "" | tee -a "$RESULT_FILE"
echo "【Registry ON】" | tee -a "$RESULT_FILE"
HAKMEM_USE_REGISTRY=1 ./bench_allocators_hakmem 2>&1 | tee -a "$RESULT_FILE"
# Registry OFF
echo "" | tee -a "$RESULT_FILE"
echo "【Registry OFF】" | tee -a "$RESULT_FILE"
HAKMEM_USE_REGISTRY=0 ./bench_allocators_hakmem 2>&1 | tee -a "$RESULT_FILE"
echo "" | tee -a "$RESULT_FILE"
echo "========================================" | tee -a "$RESULT_FILE"
echo "Results saved to: $RESULT_FILE" | tee -a "$RESULT_FILE"
echo "========================================" | tee -a "$RESULT_FILE"
```
### 実行方法
```bash
# ビルド + 自動比較
bash bench_registry_comparison.sh
# 出力例:
# ========================================
# Slab Registry ON/OFF Comparison
# ========================================
#
# 【Registry ON】
# string-builder: 10,471 ns (vs mimalloc 18ns = 582倍)
# token-stream: 98 ns (vs mimalloc 9ns = 11倍)
# small-objects: 5 ns (vs mimalloc 3ns = 1.7倍)
#
# 【Registry OFF】
# string-builder: 7,355 ns (vs mimalloc 18ns = 409倍)
# token-stream: 97 ns (vs mimalloc 9ns = 11倍)
# small-objects: 5 ns (vs mimalloc 3ns = 1.7倍)
#
# Diff: -29.7% (Registry OFF の方が速い!)
```
---
## 📊 **期待される比較結果フォーマット**
### 理想的な出力形式
```
========================================
Slab Registry Performance Comparison
========================================
Date: 2025-10-22 14:30:00
Configuration:
- Compiler: gcc 11.4.0
- Flags: -O2 -Wall -Wextra
- Threads: 1 / 4
Scenario: string-builder (8-64B small allocations)
----------------------------------------
Registry ON: 10,471 ns/op | 95,503 ops/sec
Registry OFF: 7,355 ns/op | 136,010 ops/sec
Diff: -29.7% | +42.4% ✅ (Registry OFF が速い)
Scenario: token-stream (16-128B mixed allocations)
----------------------------------------
Registry ON: 98 ns/op | 10,204,082 ops/sec
Registry OFF: 97 ns/op | 10,309,278 ops/sec
Diff: -1.0% | +1.0% (ほぼ同等)
Scenario: small-objects (32-256B allocations)
----------------------------------------
Registry ON: 5 ns/op | 200,000,000 ops/sec
Registry OFF: 5 ns/op | 200,000,000 ops/sec
Diff: 0.0% | 0.0% (同等)
========================================
Summary:
- Registry ON: 有利なシナリオなし
- Registry OFF: string-builder で 42.4% 高速化
- 推奨: Registry OFF + Atomic修正で Race Condition解消
========================================
```
---
## 🔬 **追加調査: Hybrid Approach**
### Option A: デフォルト ON + デバッグビルド切り替え
```c
// hakmem_tiny.h
#ifndef HAKMEM_DEBUG_REGISTRY
#define HAKMEM_DEFAULT_REGISTRY 1 // Release: Registry ON
#else
#define HAKMEM_DEFAULT_REGISTRY 0 // Debug: Registry OFF
#endif
// hakmem_tiny.c
static int g_use_registry = HAKMEM_DEFAULT_REGISTRY;
```
```bash
# Release ビルドRegistry ON デフォルト)
make bench
# Debug ビルドRegistry OFF デフォルト、環境変数で ON 可能)
make bench CFLAGS="-O2 -DHAKMEM_DEBUG_REGISTRY"
```
**メリット**: 本番はデフォルト ON、デバッグは OFF で安全性重視
---
## 🏆 **最終推奨**
### **Pattern 2 (ランタイム環境変数) を採用すべき理由**
1. **ユーザー要望を完全に満たす**: 「簡単に切り替えて比較」が環境変数1つで実現
2. **オーバーヘッドが実質ゼロ**: if分岐 2-5 cycles は Registry/O(N) のコスト50-500 cyclesに対して 1-10% の誤差範囲
3. **実装が最も簡単**: ~15行の追加のみ、既存コードへの影響最小
4. **メンテナンス性が高い**: 両方の経路が常にビルド → CI/テストで検証容易
5. **将来の拡張に対応**: Atomic Registry, Mutex追加などの試行錯誤が容易
### **実装スケジュール**
| Step | 作業 | 時間 |
|------|------|------|
| 1 | グローバル変数追加 | 1分 |
| 2 | `hak_tiny_init()` 修正 | 3分 |
| 3 | `hak_tiny_owner_slab()` 修正O(N) fallback追加 | 10分 |
| 4 | `allocate_new_slab()` 修正 | 3分 |
| 5 | `release_slab()` 修正 | 2分 |
| 6 | ビルド + テスト | 5分 |
| 7 | ベンチマーク自動化スクリプト作成 | 10分 |
**合計**: **34分で完了可能**
---
## 🎯 **次のアクション**
1. **Pattern 2 実装** (34分)
2. **bench_registry_comparison.sh 作成** (10分)
3. **1-thread / 4-thread 両方で比較測定** (30分)
4. **結果に基づいて Registry ON/OFF を決定**:
- Registry OFF が全体的に速い → OFF をデフォルトに
- Registry ON が特定シナリオで速い → Atomic修正 + Race Condition解消
**Total**: **1時間15分で完全解決**
にゃ! 🐱🚀