Files
hakmem/docs/design/REGISTRY_TOGGLE_DESIGN.md
Moe Charm (CI) 52386401b3 Debug Counters Implementation - Clean History
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>
2025-11-05 12:31:14 +09:00

647 lines
18 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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分で完全解決**
にゃ! 🐱🚀