# Larson Benchmark Performance Analysis - 2025-11-05 ## 🎯 Executive Summary **HAKMEM は system malloc の 25% (threads=4) / 10.7% (threads=1) しか出ていない** - **Root Cause**: Fast Path 自体が複雑(シングルスレッドで既に 10倍遅い) - **Bottleneck**: malloc() エントリーポイントの 8+ 分岐チェック - **Impact**: Larson benchmark で致命的な性能低下 --- ## 📊 測定結果 ### 性能比較 (Larson benchmark, size=8-128B) | 測定条件 | HAKMEM | system malloc | HAKMEM/system | |----------|--------|---------------|---------------| | **Single-thread (threads=1)** | **0.46M ops/s** | **4.29M ops/s** | **10.7%** 💀 | | Multi-thread (threads=4) | 1.81M ops/s | 7.23M ops/s | 25.0% | | **Performance Gap** | - | - | **-75% @ MT, -89% @ ST** | ### A/B テスト結果 (threads=4) | Profile | Throughput | vs system | 設定の違い | |---------|-----------|-----------|-----------| | tinyhot_tput | 1.81M ops/s | 25.0% | Fast Cap 64, Adopt ON | | tinyhot_best | 1.76M ops/s | 24.4% | Fast Cap 16, TLS List OFF | | tinyhot_noadopt | 1.73M ops/s | 23.9% | Adopt OFF | | tinyhot_sll256 | 1.38M ops/s | 19.1% | SLL Cap 256 | | tinyhot_optimized | 1.23M ops/s | 17.0% | Fast Cap 16, Magazine OFF | **結論**: プロファイル調整では改善せず(-3.9% ~ +0.6% の微差) --- ## 🔬 Root Cause Analysis ### 問題1: malloc() エントリーポイントが複雑 (Primary Bottleneck) **Location**: `core/hakmem.c:1250-1316` **System tcache との比較:** | System tcache | HAKMEM malloc() | |---------------|----------------| | 0 branches | **8+ branches** (毎回実行) | | 3-4 instructions | 50+ instructions | | 直接 tcache pop | 多段階チェック → Fast Path | **Overhead 分析:** ```c void* malloc(size_t size) { // Branch 1: Recursion guard if (g_hakmem_lock_depth > 0) { return __libc_malloc(size); } // Branch 2: Initialization guard if (g_initializing != 0) { return __libc_malloc(size); } // Branch 3: Force libc check if (hak_force_libc_alloc()) { return __libc_malloc(size); } // Branch 4: LD_PRELOAD mode check (getenv呼び出しの可能性) int ld_mode = hak_ld_env_mode(); // Branch 5-8: jemalloc, initialization, LD_SAFE, size check... // ↓ ようやく Fast Path #ifdef HAKMEM_TINY_FAST_PATH void* ptr = tiny_fast_alloc(size); #endif } ``` **推定コスト**: 8 branches × 5 cycles/branch = **40 cycles overhead** (system tcache は 0) --- ### 問題2: Fast Path の階層が深い **HAKMEM 呼び出し経路:** ``` malloc() [8+ branches] ↓ tiny_fast_alloc() [class mapping] ↓ g_tiny_fast_cache[class] pop [3-4 instructions] ↓ (cache miss) tiny_fast_refill() [function call overhead] ↓ for (i=0; i<16; i++) [loop] hak_tiny_alloc() [複雑な内部処理] ``` **System tcache 呼び出し経路:** ``` malloc() ↓ tcache[class] pop [3-4 instructions] ↓ (cache miss) _int_malloc() [chunk from bin] ``` **差分**: HAKMEM は 4-5 階層、system は 2 階層 --- ### 問題3: Refill コストが高い **Location**: `core/tiny_fastcache.c:58-78` **現在の実装:** ```c // Batch refill: 16個を個別に取得 for (int i = 0; i < TINY_FAST_REFILL_BATCH; i++) { void* ptr = hak_tiny_alloc(size); // 関数呼び出し × 16 *(void**)ptr = g_tiny_fast_cache[class_idx]; g_tiny_fast_cache[class_idx] = ptr; } ``` **問題点:** - `hak_tiny_alloc()` を 16 回呼ぶ(関数呼び出しオーバーヘッド) - 各呼び出しで内部の Magazine/SuperSlab を経由 - Larson は malloc/free が頻繁 → refill も頻繁 → コスト増大 **推定コスト**: 16 calls × 100 cycles/call = **1,600 cycles** (system tcache は ~200 cycles) --- ## 💡 改善案 ### Option A: malloc() ガードチェック最適化 ⭐⭐⭐⭐ **Goal**: 分岐数を 8+ → 2-3 に削減 **Implementation:** ```c void* malloc(size_t size) { // Fast path: 初期化済み & Tiny サイズ if (__builtin_expect(g_initialized && size <= 128, 1)) { // Direct inline TLS cache access (0 extra branches!) int cls = size_to_class_inline(size); void* head = g_tls_cache[cls]; if (head) { g_tls_cache[cls] = *(void**)head; return head; // 🚀 3-4 instructions total } // Cache miss → refill return tiny_fast_refill(cls); } // Slow path: 既存のチェック群 (初回のみ or 非 Tiny サイズ) if (g_hakmem_lock_depth > 0) { return __libc_malloc(size); } // ... 他のチェック } ``` **Expected Improvement**: +200-400% (0.46M → 1.4-2.3M ops/s @ threads=1) **Risk**: Low (分岐を並び替えるだけ) **Effort**: 3-5 days --- ### Option B: Refill 効率化 ⭐⭐⭐ **Goal**: Refill コストを 1,600 cycles → 200 cycles に削減 **Implementation:** ```c void* tiny_fast_refill(int class_idx) { // Before: hak_tiny_alloc() を 16 回呼ぶ // After: SuperSlab から直接 batch 取得 void* batch[64]; int count = superslab_batch_alloc(class_idx, batch, 64); // Push to cache in one pass for (int i = 0; i < count; i++) { *(void**)batch[i] = g_tls_cache[class_idx]; g_tls_cache[class_idx] = batch[i]; } // Pop one for caller void* result = g_tls_cache[class_idx]; g_tls_cache[class_idx] = *(void**)result; return result; } ``` **Expected Improvement**: +30-50% (追加効果) **Risk**: Medium (SuperSlab への batch API 追加が必要) **Effort**: 5-7 days --- ### Option C: Fast Path 完全単純化 (Ultimate) ⭐⭐⭐⭐⭐ **Goal**: System tcache と同等の設計 (3-4 instructions) **Implementation:** ```c // 1. malloc() を完全に書き直し void* malloc(size_t size) { // Ultra-fast path: 条件チェック最小化 if (__builtin_expect(size <= 128, 1)) { return tiny_ultra_fast_alloc(size); } // Slow path (非 Tiny) return hak_alloc_at(size, HAK_CALLSITE()); } // 2. Ultra-fast allocator (inline) static inline void* tiny_ultra_fast_alloc(size_t size) { int cls = size_to_class_inline(size); void* head = g_tls_cache[cls]; if (__builtin_expect(head != NULL, 1)) { g_tls_cache[cls] = *(void**)head; return head; // HIT: 3-4 instructions } // MISS: refill return tiny_ultra_fast_refill(cls); } ``` **Expected Improvement**: +400-800% (0.46M → 2.3-4.1M ops/s @ threads=1) **Risk**: Medium-High (malloc() 全体の再設計) **Effort**: 1-2 weeks --- ## 🎯 推奨アクション ### Phase 1 (1週間): Option A (ガードチェック最適化) **Priority**: High **Impact**: High (+200-400%) **Risk**: Low **Steps:** 1. `g_initialized` をキャッシュ化(TLS 変数) 2. Fast path を最優先に移動 3. 分岐予測ヒントを追加 (`__builtin_expect`) **Success Criteria**: 0.46M → 1.4M ops/s @ threads=1 (+200%) --- ### Phase 2 (3-5日): Option B (Refill 効率化) **Priority**: Medium **Impact**: Medium (+30-50%) **Risk**: Medium **Steps:** 1. `superslab_batch_alloc()` API を実装 2. `tiny_fast_refill()` を書き直し 3. A/B テストで効果確認 **Success Criteria**: 追加 +30% (1.4M → 1.8M ops/s @ threads=1) --- ### Phase 3 (1-2週間): Option C (Fast Path 完全単純化) **Priority**: High (Long-term) **Impact**: Very High (+400-800%) **Risk**: Medium-High **Steps:** 1. `malloc()` を完全に書き直し 2. System tcache と同等の設計 3. 段階的リリース(feature flag で切り替え) **Success Criteria**: 2.3-4.1M ops/s @ threads=1 (system の 54-95%) --- ## 📚 参考資料 ### 既存の最適化 (CLAUDE.md より) **Phase 6-1.7 (Box Refactor):** - 達成: 1.68M → 2.75M ops/s (+64%) - 手法: TLS freelist 直接 pop、Batch Refill - **しかし**: これでも system の 25% しか出ていない **Phase 6-2.1 (P0 Optimization):** - 達成: superslab_refill の O(n) → O(1) 化 - 効果: 内部 -12% だが全体効果は限定的 - **教訓**: Bottleneck は malloc() エントリーポイント ### System tcache 仕様 **GNU libc tcache (per-thread cache):** - 64 bins (16B - 1024B) - 7 blocks per bin (default) - **Fast path**: 3-4 instructions (no lock, no branch) - **Refill**: _int_malloc() から chunk を取得 **mimalloc:** - Free list per size class - Thread-local pages - **Fast path**: 4-5 instructions - **Refill**: Page から batch 取得 --- ## 🔍 関連ファイル - `core/hakmem.c:1250-1316` - malloc() エントリーポイント - `core/tiny_fastcache.c:41-88` - Fast Path refill - `core/tiny_alloc_fast.inc.h` - Box 5 Fast Path 実装 - `scripts/profiles/tinyhot_*.env` - A/B テスト用プロファイル --- ## 📝 結論 **HAKMEM の Larson 性能低下(-75%)は、Fast Path の構造的な問題が原因。** 1. ✅ **Root Cause 特定**: シングルスレッドで 10.7% しか出ていない 2. ✅ **Bottleneck 特定**: malloc() エントリーポイントの 8+ 分岐 3. ✅ **解決策提案**: Option A (分岐削減) で +200-400% 改善可能 **次のステップ**: Option A の実装を開始 → Phase 1 で 0.46M → 1.4M ops/s を達成 --- **Date**: 2025-11-05 **Author**: Claude (Ultrathink Analysis Mode) **Status**: Analysis Complete ✅