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