Phase 17-2: Small-Mid Dedicated SuperSlab Backend (実験結果: 70% page fault, 性能改善なし)
Summary: ======== Phase 17-2 implements dedicated SuperSlab backend for Small-Mid allocator (256B-1KB). Result: No performance improvement (-0.9%), worse than Phase 17-1 (+0.3%). Root cause: 70% page fault (ChatGPT + perf profiling). Conclusion: Small-Mid専用層戦略は失敗。Tiny SuperSlab最適化が必要。 Implementation: =============== 1. Dedicated Small-Mid SuperSlab pool (1MB, 16 slabs/SS) - Separate from Tiny SuperSlab (no competition) - Batch refill (8-16 blocks per TLS refill) - Direct 0xb0 header writes (no Tiny delegation) 2. Backend architecture - SmallMidSuperSlab: 1MB aligned region, fast ptr→SS lookup - SmallMidSlabMeta: per-slab metadata (capacity/used/carved/freelist) - SmallMidSSHead: per-class pool with LRU tracking 3. Batch refill implementation - smallmid_refill_batch(): 8-16 blocks/call (vs 1 in Phase 17-1) - Freelist priority → bump allocation fallback - Auto SuperSlab expansion when exhausted Files Added: ============ - core/hakmem_smallmid_superslab.h: SuperSlab metadata structures - core/hakmem_smallmid_superslab.c: Backend implementation (~450 lines) Files Modified: =============== - core/hakmem_smallmid.c: Removed Tiny delegation, added batch refill - Makefile: Added hakmem_smallmid_superslab.o to build - CURRENT_TASK.md: Phase 17 完了記録 + Phase 18 計画 A/B Benchmark Results: ====================== | Size | Phase 17-1 (ON) | Phase 17-2 (ON) | Delta | vs Baseline | |--------|-----------------|-----------------|----------|-------------| | 256B | 6.06M ops/s | 5.84M ops/s | -3.6% | -4.1% | | 512B | 5.91M ops/s | 5.86M ops/s | -0.8% | +1.2% | | 1024B | 5.54M ops/s | 5.44M ops/s | -1.8% | +0.4% | | Avg | 5.84M ops/s | 5.71M ops/s | -2.2% | -0.9% | Performance Analysis (ChatGPT + perf): ====================================== ✅ Frontend (TLS/batch refill): OK - Only 30% CPU time - Batch refill logic is efficient - Direct 0xb0 header writes work correctly ❌ Backend (SuperSlab allocation): BOTTLENECK - 70% CPU time in asm_exc_page_fault - mmap(1MB) → kernel page allocation → very slow - New SuperSlab allocation per benchmark run - No warm SuperSlab reuse (used counter never decrements) Root Cause: =========== Small-Mid allocates new SuperSlabs frequently: alloc → TLS miss → refill → new SuperSlab → mmap(1MB) → page fault (70%) Tiny reuses warm SuperSlabs: alloc → TLS miss → refill → existing warm SuperSlab → no page fault Key Finding: "70% page fault" reveals SuperSlab layer needs optimization, NOT frontend layer (TLS/batch refill design is correct). Lessons Learned: ================ 1. ❌ Small-Mid専用層戦略は失敗 (Phase 17-1: +0.3%, Phase 17-2: -0.9%) 2. ✅ Frontend実装は成功 (30% CPU, batch refill works) 3. 🔥 70% page fault = SuperSlab allocation bottleneck 4. ✅ Tiny (6.08M ops/s) is already well-optimized, hard to beat 5. ✅ Layer separation doesn't improve performance - backend optimization needed Next Steps (Phase 18): ====================== ChatGPT recommendation: Optimize Tiny SuperSlab (NOT Small-Mid specific layer) Box SS-Reuse (Priority 1): - Implement meta->freelist reuse (currently bump-only) - Detect slab empty → return to shared_pool - Reuse same SuperSlab for longer (reduce page faults) - Target: 70% page fault → 5-10%, 2-4x improvement Box SS-Prewarm (Priority 2): - Pre-allocate SuperSlabs per class (Phase 11: +6.4%) - Concentrate page faults at benchmark start - Benchmark-only optimization Small-Mid Implementation Status: ================================= - ENV=0 by default (zero overhead, branch predictor learns) - Complete separation from Tiny (no interference) - Valuable as experimental record ("why dedicated layer failed") - Can be removed later if needed (not blocking Tiny optimization) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -21,8 +21,8 @@
|
||||
|
||||
#include "hakmem_smallmid.h"
|
||||
#include "hakmem_build_flags.h"
|
||||
#include "hakmem_tiny.h" // For backend: hak_tiny_alloc / hak_tiny_free
|
||||
#include "tiny_region_id.h" // For header writing
|
||||
#include "hakmem_smallmid_superslab.h" // Phase 17-2: Dedicated backend
|
||||
#include "tiny_region_id.h" // For header writing
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
|
||||
@ -170,85 +170,58 @@ static inline bool smallmid_tls_push(int class_idx, void* ptr) {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Backend Delegation (Phase 17-1: Reuse Tiny infrastructure)
|
||||
// TLS Refill (Phase 17-2: Batch refill from dedicated SuperSlab)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* smallmid_backend_alloc - Allocate from Tiny backend and convert header
|
||||
* smallmid_tls_refill - Refill TLS freelist from SuperSlab
|
||||
*
|
||||
* @param size Allocation size (256-1024)
|
||||
* @return User pointer with Small-Mid header (0xb0), or NULL on failure
|
||||
* @param class_idx Size class index
|
||||
* @return true on success, false on failure
|
||||
*
|
||||
* Strategy:
|
||||
* - Call Tiny allocator (handles C5/C6/C7 = 256B/512B/1KB)
|
||||
* - Tiny writes header: 0xa5/0xa6/0xa7
|
||||
* - Overwrite with Small-Mid header: 0xb0/0xb1/0xb2
|
||||
* Strategy (Phase 17-2):
|
||||
* - Batch refill 8-16 blocks from dedicated SmallMid SuperSlab
|
||||
* - No Tiny delegation (completely separate backend)
|
||||
* - Amortizes SuperSlab lookup cost across multiple blocks
|
||||
* - Expected cost: ~1-2 instructions per block (amortized)
|
||||
*/
|
||||
static void* smallmid_backend_alloc(size_t size) {
|
||||
static bool smallmid_tls_refill(int class_idx) {
|
||||
// Determine batch size based on size class
|
||||
const int batch_sizes[SMALLMID_NUM_CLASSES] = {
|
||||
SMALLMID_REFILL_BATCH_256B, // 16 blocks
|
||||
SMALLMID_REFILL_BATCH_512B, // 12 blocks
|
||||
SMALLMID_REFILL_BATCH_1KB // 8 blocks
|
||||
};
|
||||
|
||||
int batch_max = batch_sizes[class_idx];
|
||||
void* batch[16]; // Max batch size
|
||||
|
||||
// Call SuperSlab batch refill
|
||||
int refilled = smallmid_refill_batch(class_idx, batch, batch_max);
|
||||
if (refilled == 0) {
|
||||
SMALLMID_LOG("smallmid_tls_refill: SuperSlab refill failed (class=%d)", class_idx);
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef HAKMEM_SMALLMID_STATS
|
||||
__atomic_fetch_add(&g_smallmid_stats.tls_misses, 1, __ATOMIC_RELAXED);
|
||||
__atomic_fetch_add(&g_smallmid_stats.superslab_refills, 1, __ATOMIC_RELAXED);
|
||||
#endif
|
||||
|
||||
// Call Tiny allocator
|
||||
void* ptr = hak_tiny_alloc(size);
|
||||
if (!ptr) {
|
||||
SMALLMID_LOG("smallmid_backend_alloc(%zu): Tiny allocation failed", size);
|
||||
return NULL;
|
||||
// Push blocks to TLS freelist (in reverse order for LIFO)
|
||||
for (int i = refilled - 1; i >= 0; i--) {
|
||||
void* user_ptr = batch[i];
|
||||
void* base = (uint8_t*)user_ptr - 1;
|
||||
|
||||
if (!smallmid_tls_push(class_idx, base)) {
|
||||
// TLS full - should not happen with proper batch sizing
|
||||
SMALLMID_LOG("smallmid_tls_refill: TLS push failed (class=%d, i=%d)", class_idx, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Overwrite header: Tiny (0xa0 | tiny_class) → Small-Mid (0xb0 | sm_class)
|
||||
// Tiny class mapping: C5=256B, C6=512B, C7=1KB
|
||||
// Small-Mid class mapping: SM0=256B, SM1=512B, SM2=1KB
|
||||
uint8_t* base = (uint8_t*)ptr - 1;
|
||||
uint8_t tiny_header = *base;
|
||||
uint8_t tiny_class = tiny_header & 0x0f;
|
||||
|
||||
// Convert Tiny class (5/6/7) to Small-Mid class (0/1/2)
|
||||
int sm_class = tiny_class - 5;
|
||||
if (sm_class < 0 || sm_class >= SMALLMID_NUM_CLASSES) {
|
||||
// Should never happen - Tiny allocated wrong class
|
||||
SMALLMID_LOG("smallmid_backend_alloc(%zu): Invalid Tiny class %d", size, tiny_class);
|
||||
// Revert header and free
|
||||
hak_tiny_free(ptr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Write Small-Mid header
|
||||
*base = 0xb0 | sm_class;
|
||||
|
||||
SMALLMID_LOG("smallmid_backend_alloc(%zu) = %p (Tiny C%d → SM C%d)", size, ptr, tiny_class, sm_class);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* smallmid_backend_free - Convert header and delegate to Tiny backend
|
||||
*
|
||||
* @param ptr User pointer (must have Small-Mid header 0xb0)
|
||||
* @param size Allocation size (unused, Tiny reads header)
|
||||
*
|
||||
* Strategy:
|
||||
* - Convert header: Small-Mid (0xb0 | sm_class) → Tiny (0xa0 | tiny_class)
|
||||
* - Call Tiny free to handle deallocation
|
||||
*/
|
||||
static void smallmid_backend_free(void* ptr, size_t size) {
|
||||
(void)size; // Unused - Tiny reads size from header
|
||||
|
||||
// Read Small-Mid header
|
||||
uint8_t* base = (uint8_t*)ptr - 1;
|
||||
uint8_t sm_header = *base;
|
||||
uint8_t sm_class = sm_header & 0x0f;
|
||||
|
||||
// Convert Small-Mid class (0/1/2) to Tiny class (5/6/7)
|
||||
uint8_t tiny_class = sm_class + 5;
|
||||
|
||||
// Write Tiny header
|
||||
*base = 0xa0 | tiny_class;
|
||||
|
||||
SMALLMID_LOG("smallmid_backend_free(%p): SM C%d → Tiny C%d", ptr, sm_class, tiny_class);
|
||||
|
||||
// Call Tiny free
|
||||
hak_tiny_free(ptr);
|
||||
SMALLMID_LOG("smallmid_tls_refill: Refilled %d blocks (class=%d)", refilled, class_idx);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@ -264,6 +237,7 @@ void* smallmid_alloc(size_t size) {
|
||||
// Initialize if needed
|
||||
if (__builtin_expect(!g_smallmid_initialized, 0)) {
|
||||
smallmid_init();
|
||||
smallmid_superslab_init(); // Phase 17-2: Initialize SuperSlab backend
|
||||
}
|
||||
|
||||
// Validate size range
|
||||
@ -291,16 +265,21 @@ void* smallmid_alloc(size_t size) {
|
||||
return (uint8_t*)ptr + 1; // Return user pointer (skip header)
|
||||
}
|
||||
|
||||
// TLS miss: Allocate from Tiny backend
|
||||
// Phase 17-1: Reuse Tiny infrastructure (C5/C6/C7) instead of dedicated SuperSlab
|
||||
ptr = smallmid_backend_alloc(size);
|
||||
if (!ptr) {
|
||||
SMALLMID_LOG("smallmid_alloc(%zu) = NULL (backend failed)", size);
|
||||
// TLS miss: Refill from SuperSlab (Phase 17-2: Batch refill)
|
||||
if (!smallmid_tls_refill(class_idx)) {
|
||||
SMALLMID_LOG("smallmid_alloc(%zu) = NULL (refill failed)", size);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SMALLMID_LOG("smallmid_alloc(%zu) = %p (backend alloc, class=%d)", size, ptr, class_idx);
|
||||
return ptr;
|
||||
// Retry TLS pop after refill
|
||||
ptr = smallmid_tls_pop(class_idx);
|
||||
if (!ptr) {
|
||||
SMALLMID_LOG("smallmid_alloc(%zu) = NULL (TLS pop failed after refill)", size);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SMALLMID_LOG("smallmid_alloc(%zu) = %p (TLS refill, class=%d)", size, ptr, class_idx);
|
||||
return (uint8_t*)ptr + 1; // Return user pointer (skip header)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@ -319,32 +298,33 @@ void smallmid_free(void* ptr) {
|
||||
__atomic_fetch_add(&g_smallmid_stats.total_frees, 1, __ATOMIC_RELAXED);
|
||||
#endif
|
||||
|
||||
// Phase 17-1: Read header to identify if this is a Small-Mid TLS allocation
|
||||
// or a backend (Tiny) allocation
|
||||
// Phase 17-2: Read header to identify size class
|
||||
uint8_t* base = (uint8_t*)ptr - 1;
|
||||
uint8_t header = *base;
|
||||
|
||||
// Small-Mid TLS allocations have magic 0xb0
|
||||
// Tiny allocations have magic 0xa0
|
||||
// Small-Mid allocations have magic 0xb0
|
||||
uint8_t magic = header & 0xf0;
|
||||
int class_idx = header & 0x0f;
|
||||
|
||||
if (magic == 0xb0 && class_idx >= 0 && class_idx < SMALLMID_NUM_CLASSES) {
|
||||
// This is a Small-Mid TLS allocation, push to TLS freelist
|
||||
if (smallmid_tls_push(class_idx, base)) {
|
||||
SMALLMID_LOG("smallmid_free(%p): pushed to TLS (class=%d)", ptr, class_idx);
|
||||
return;
|
||||
}
|
||||
|
||||
// TLS full: Delegate to Tiny backend
|
||||
SMALLMID_LOG("smallmid_free(%p): TLS full, delegating to backend", ptr);
|
||||
// Fall through to backend free
|
||||
if (magic != 0xb0 || class_idx < 0 || class_idx >= SMALLMID_NUM_CLASSES) {
|
||||
// Invalid header - should not happen
|
||||
SMALLMID_LOG("smallmid_free(%p): Invalid header 0x%02x", ptr, header);
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a backend (Tiny) allocation, or TLS full - delegate to Tiny
|
||||
// Tiny will handle the free based on its own header (0xa0)
|
||||
size_t size = 0; // Tiny free doesn't need size, it reads header
|
||||
smallmid_backend_free(ptr, size);
|
||||
// Fast path: Push to TLS freelist
|
||||
if (smallmid_tls_push(class_idx, base)) {
|
||||
SMALLMID_LOG("smallmid_free(%p): pushed to TLS (class=%d)", ptr, class_idx);
|
||||
return;
|
||||
}
|
||||
|
||||
// TLS full: Push to SuperSlab freelist (slow path)
|
||||
// TODO Phase 17-2.1: Implement SuperSlab freelist push
|
||||
// For now, just log and leak (will be fixed in next commit)
|
||||
SMALLMID_LOG("smallmid_free(%p): TLS full, SuperSlab freelist not yet implemented", ptr);
|
||||
|
||||
// Placeholder: Write next pointer to freelist (unsafe without SuperSlab lookup)
|
||||
// This will be properly implemented with smallmid_superslab_lookup() in Phase 17-2.1
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user