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

18 KiB
Raw Blame 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)

切り替えの課題

# 現状: 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. ユーザー要望を完全に満たす: 「簡単に切り替えて比較」が環境変数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分で完全解決

にゃ! 🐱🚀