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>
18 KiB
18 KiB
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) |
切り替えの課題
# 現状: 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)
実装案
// 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つの経路を維持(ビルド時検証必要) |
切り替え方法
# 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)
実装案
// 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/テストで検証容易) |
切り替え方法
# 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: 関数ポインタ切り替え
実装案
// 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️⃣ ベンチマーク比較の容易さ(最重要!)
# 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️⃣ 実装が最も簡単
+ 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)
// ============================================================================
// 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)
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 修正
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 修正
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 修正
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
#!/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 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 + デバッグビルド切り替え
// 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;
# Release ビルド(Registry ON デフォルト)
make bench
# Debug ビルド(Registry OFF デフォルト、環境変数で ON 可能)
make bench CFLAGS="-O2 -DHAKMEM_DEBUG_REGISTRY"
メリット: 本番はデフォルト ON、デバッグは OFF で安全性重視
🏆 最終推奨
Pattern 2 (ランタイム環境変数) を採用すべき理由
- ユーザー要望を完全に満たす: 「簡単に切り替えて比較」が環境変数1つで実現
- オーバーヘッドが実質ゼロ: if分岐 2-5 cycles は Registry/O(N) のコスト(50-500 cycles)に対して 1-10% の誤差範囲
- 実装が最も簡単: ~15行の追加のみ、既存コードへの影響最小
- メンテナンス性が高い: 両方の経路が常にビルド → CI/テストで検証容易
- 将来の拡張に対応: 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分で完了可能!
🎯 次のアクション
- Pattern 2 実装 (34分)
- bench_registry_comparison.sh 作成 (10分)
- 1-thread / 4-thread 両方で比較測定 (30分)
- 結果に基づいて Registry ON/OFF を決定:
- Registry OFF が全体的に速い → OFF をデフォルトに
- Registry ON が特定シナリオで速い → Atomic修正 + Race Condition解消
Total: 1時間15分で完全解決! ✅
にゃ! 🐱🚀