Files
hakmem/core/hakmem_shared_pool.c
Moe Charm (CI) 40be86425b Phase 12 SP-SLOT + Mid-Large P0 fix: Pool TLS debug logging & analysis
Phase 12 SP-SLOT Box (Complete):
- Per-slot state tracking (UNUSED/ACTIVE/EMPTY) for shared SuperSlabs
- 3-stage allocation: EMPTY reuse → UNUSED reuse → New SS
- Results: 877 → 72 SuperSlabs (-92%), 563K → 1.30M ops/s (+131%)
- Reports: PHASE12_SP_SLOT_BOX_IMPLEMENTATION_REPORT.md, CURRENT_TASK.md

Mid-Large P0 Analysis (2025-11-14):
- Root cause: Pool TLS disabled by default (build.sh:106 → POOL_TLS_PHASE1=0)
- Fix: POOL_TLS_PHASE1=1 build flag → 0.24M → 0.97M ops/s (+304%)
- Identified P0-2: futex bottleneck (67% syscall time) in pool_remote_push mutex
- Added debug logging: pool_tls.c (refill failures), pool_tls_arena.c (mmap/chunk failures)
- Reports: MID_LARGE_P0_FIX_REPORT_20251114.md, BOTTLENECK_ANALYSIS_REPORT_20251114.md

Next: Lock-free remote queue to reduce futex from 67% → <10%

Files modified:
- core/hakmem_shared_pool.c (SP-SLOT implementation)
- core/pool_tls.c (debug logging + stdatomic.h)
- core/pool_tls_arena.c (debug logging + stdio.h/errno.h/stdatomic.h)
- CURRENT_TASK.md (Phase 12 completion status)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 14:18:56 +09:00

583 lines
18 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "hakmem_shared_pool.h"
#include "hakmem_tiny_superslab.h"
#include "hakmem_tiny_superslab_constants.h"
#include <stdlib.h>
#include <string.h>
// Phase 12-2: SharedSuperSlabPool skeleton implementation
// Goal:
// - Centralize SuperSlab allocation/registration
// - Provide acquire_slab/release_slab APIs for later refill/free integration
// - Keep logic simple & conservative; correctness and observability first.
//
// Notes:
// - Concurrency: protected by g_shared_pool.alloc_lock for now.
// - class_hints is best-effort: read lock-free, written under lock.
// - LRU hooks left as no-op placeholders.
SharedSuperSlabPool g_shared_pool = {
.slabs = NULL,
.capacity = 0,
.total_count = 0,
.active_count = 0,
.alloc_lock = PTHREAD_MUTEX_INITIALIZER,
.class_hints = { NULL },
.lru_head = NULL,
.lru_tail = NULL,
.lru_count = 0,
// Phase 12: SP-SLOT fields
.free_slots = {{.entries = {{0}}, .count = 0}}, // Zero-init all class free lists
.ss_metadata = NULL,
.ss_meta_capacity = 0,
.ss_meta_count = 0
};
static void
shared_pool_ensure_capacity_unlocked(uint32_t min_capacity)
{
if (g_shared_pool.capacity >= min_capacity) {
return;
}
uint32_t new_cap = g_shared_pool.capacity ? g_shared_pool.capacity : 16;
while (new_cap < min_capacity) {
new_cap *= 2;
}
SuperSlab** new_slabs = (SuperSlab**)realloc(g_shared_pool.slabs,
new_cap * sizeof(SuperSlab*));
if (!new_slabs) {
// Allocation failure: keep old state; caller must handle NULL later.
return;
}
// Zero new entries to keep scanning logic simple.
memset(new_slabs + g_shared_pool.capacity, 0,
(new_cap - g_shared_pool.capacity) * sizeof(SuperSlab*));
g_shared_pool.slabs = new_slabs;
g_shared_pool.capacity = new_cap;
}
void
shared_pool_init(void)
{
// Idempotent init; safe to call from multiple early paths.
// pthread_mutex_t with static initializer is already valid.
pthread_mutex_lock(&g_shared_pool.alloc_lock);
if (g_shared_pool.capacity == 0 && g_shared_pool.slabs == NULL) {
shared_pool_ensure_capacity_unlocked(16);
}
pthread_mutex_unlock(&g_shared_pool.alloc_lock);
}
// ============================================================================
// Phase 12: SP-SLOT Box - Modular Helper Functions
// ============================================================================
// ---------- Layer 1: Slot Operations (Low-level) ----------
// Find first unused slot in SharedSSMeta
// Returns: slot_idx on success, -1 if no unused slots
static int sp_slot_find_unused(SharedSSMeta* meta) {
if (!meta) return -1;
for (int i = 0; i < meta->total_slots; i++) {
if (meta->slots[i].state == SLOT_UNUSED) {
return i;
}
}
return -1;
}
// Mark slot as ACTIVE (UNUSED→ACTIVE or EMPTY→ACTIVE)
// Returns: 0 on success, -1 on error
static int sp_slot_mark_active(SharedSSMeta* meta, int slot_idx, int class_idx) {
if (!meta || slot_idx < 0 || slot_idx >= meta->total_slots) return -1;
if (class_idx < 0 || class_idx >= TINY_NUM_CLASSES_SS) return -1;
SharedSlot* slot = &meta->slots[slot_idx];
// Transition: UNUSED→ACTIVE or EMPTY→ACTIVE
if (slot->state == SLOT_UNUSED || slot->state == SLOT_EMPTY) {
slot->state = SLOT_ACTIVE;
slot->class_idx = (uint8_t)class_idx;
slot->slab_idx = (uint8_t)slot_idx;
meta->active_slots++;
return 0;
}
return -1; // Already ACTIVE or invalid state
}
// Mark slot as EMPTY (ACTIVE→EMPTY)
// Returns: 0 on success, -1 on error
static int sp_slot_mark_empty(SharedSSMeta* meta, int slot_idx) {
if (!meta || slot_idx < 0 || slot_idx >= meta->total_slots) return -1;
SharedSlot* slot = &meta->slots[slot_idx];
if (slot->state == SLOT_ACTIVE) {
slot->state = SLOT_EMPTY;
if (meta->active_slots > 0) {
meta->active_slots--;
}
return 0;
}
return -1; // Not ACTIVE
}
// ---------- Layer 2: Metadata Management (Mid-level) ----------
// Ensure ss_metadata array has capacity for at least min_count entries
// Caller must hold alloc_lock
// Returns: 0 on success, -1 on allocation failure
static int sp_meta_ensure_capacity(uint32_t min_count) {
if (g_shared_pool.ss_meta_capacity >= min_count) {
return 0;
}
uint32_t new_cap = g_shared_pool.ss_meta_capacity ? g_shared_pool.ss_meta_capacity : 16;
while (new_cap < min_count) {
new_cap *= 2;
}
SharedSSMeta* new_meta = (SharedSSMeta*)realloc(
g_shared_pool.ss_metadata,
new_cap * sizeof(SharedSSMeta)
);
if (!new_meta) {
return -1;
}
// Zero new entries
memset(new_meta + g_shared_pool.ss_meta_capacity, 0,
(new_cap - g_shared_pool.ss_meta_capacity) * sizeof(SharedSSMeta));
g_shared_pool.ss_metadata = new_meta;
g_shared_pool.ss_meta_capacity = new_cap;
return 0;
}
// Find SharedSSMeta for given SuperSlab, or create if not exists
// Caller must hold alloc_lock
// Returns: SharedSSMeta* on success, NULL on error
static SharedSSMeta* sp_meta_find_or_create(SuperSlab* ss) {
if (!ss) return NULL;
// Search existing metadata
for (uint32_t i = 0; i < g_shared_pool.ss_meta_count; i++) {
if (g_shared_pool.ss_metadata[i].ss == ss) {
return &g_shared_pool.ss_metadata[i];
}
}
// Create new metadata entry
if (sp_meta_ensure_capacity(g_shared_pool.ss_meta_count + 1) != 0) {
return NULL;
}
SharedSSMeta* meta = &g_shared_pool.ss_metadata[g_shared_pool.ss_meta_count];
meta->ss = ss;
meta->total_slots = (uint8_t)ss_slabs_capacity(ss);
meta->active_slots = 0;
// Initialize all slots as UNUSED
for (int i = 0; i < meta->total_slots; i++) {
meta->slots[i].state = SLOT_UNUSED;
meta->slots[i].class_idx = 0;
meta->slots[i].slab_idx = (uint8_t)i;
}
g_shared_pool.ss_meta_count++;
return meta;
}
// ---------- Layer 3: Free List Management ----------
// Push empty slot to per-class free list
// Caller must hold alloc_lock
// Returns: 0 on success, -1 if list is full
static int sp_freelist_push(int class_idx, SharedSSMeta* meta, int slot_idx) {
if (class_idx < 0 || class_idx >= TINY_NUM_CLASSES_SS) return -1;
if (!meta || slot_idx < 0 || slot_idx >= meta->total_slots) return -1;
FreeSlotList* list = &g_shared_pool.free_slots[class_idx];
if (list->count >= MAX_FREE_SLOTS_PER_CLASS) {
return -1; // List full
}
list->entries[list->count].meta = meta;
list->entries[list->count].slot_idx = (uint8_t)slot_idx;
list->count++;
return 0;
}
// Pop empty slot from per-class free list
// Caller must hold alloc_lock
// Returns: 1 if popped (out params filled), 0 if list empty
static int sp_freelist_pop(int class_idx, SharedSSMeta** out_meta, int* out_slot_idx) {
if (class_idx < 0 || class_idx >= TINY_NUM_CLASSES_SS) return 0;
if (!out_meta || !out_slot_idx) return 0;
FreeSlotList* list = &g_shared_pool.free_slots[class_idx];
if (list->count == 0) {
return 0; // List empty
}
// Pop from end (LIFO for cache locality)
list->count--;
*out_meta = list->entries[list->count].meta;
*out_slot_idx = list->entries[list->count].slot_idx;
return 1;
}
/*
* Internal: allocate and register a new SuperSlab for the shared pool.
*
* Phase 12 NOTE:
* - We MUST use the real superslab_allocate() path so that:
* - backing memory is a full SuperSlab region (12MB),
* - header/layout are initialized correctly,
* - registry integration stays consistent.
* - shared_pool is responsible only for:
* - tracking pointers,
* - marking per-slab class_idx as UNASSIGNED initially.
* It does NOT bypass registry/LRU.
*
* Caller must hold alloc_lock.
*/
static SuperSlab*
shared_pool_allocate_superslab_unlocked(void)
{
// Use size_class 0 as a neutral hint; Phase 12 per-slab class_idx is authoritative.
extern SuperSlab* superslab_allocate(uint8_t size_class);
SuperSlab* ss = superslab_allocate(0);
if (!ss) {
return NULL;
}
// superslab_allocate() already:
// - zeroes slab metadata / remote queues,
// - sets magic/lg_size/etc,
// - registers in global registry.
// For shared-pool semantics we normalize all slab class_idx to UNASSIGNED.
int max_slabs = ss_slabs_capacity(ss);
for (int i = 0; i < max_slabs; i++) {
ss->slabs[i].class_idx = 255; // UNASSIGNED
}
if (g_shared_pool.total_count >= g_shared_pool.capacity) {
shared_pool_ensure_capacity_unlocked(g_shared_pool.total_count + 1);
if (g_shared_pool.total_count >= g_shared_pool.capacity) {
// Pool table expansion failed; leave ss alive (registry-owned),
// but do not treat it as part of shared_pool.
return NULL;
}
}
g_shared_pool.slabs[g_shared_pool.total_count] = ss;
g_shared_pool.total_count++;
// Not counted as active until at least one slab is assigned.
return ss;
}
SuperSlab*
shared_pool_acquire_superslab(void)
{
// Phase 12 debug safety:
// If shared backend is disabled at Box API level, this function SHOULD NOT be called.
// But since bench currently SEGVs here even with legacy forced, treat this as a hard guard:
// we early-return error instead of touching potentially-bad state.
//
// This isolates shared_pool from the current crash so we can validate legacy path first.
// FIXED: Remove the return -1; that was preventing operation
shared_pool_init();
pthread_mutex_lock(&g_shared_pool.alloc_lock);
// For now, always allocate a fresh SuperSlab and register it.
// More advanced reuse/GC comes later.
SuperSlab* ss = shared_pool_allocate_superslab_unlocked();
pthread_mutex_unlock(&g_shared_pool.alloc_lock);
return ss;
}
// ---------- Layer 4: Public API (High-level) ----------
int
shared_pool_acquire_slab(int class_idx, SuperSlab** ss_out, int* slab_idx_out)
{
// Phase 12: SP-SLOT Box - 3-Stage Acquire Logic
//
// Stage 1: Reuse EMPTY slots from per-class free list (EMPTY→ACTIVE)
// Stage 2: Find UNUSED slots in existing SuperSlabs
// Stage 3: Get new SuperSlab (LRU pop or mmap)
//
// Invariants:
// - On success: *ss_out != NULL, 0 <= *slab_idx_out < total_slots
// - The chosen slab has meta->class_idx == class_idx
if (!ss_out || !slab_idx_out) {
return -1;
}
if (class_idx < 0 || class_idx >= TINY_NUM_CLASSES_SS) {
return -1;
}
shared_pool_init();
// Debug logging
static int dbg_acquire = -1;
if (__builtin_expect(dbg_acquire == -1, 0)) {
const char* e = getenv("HAKMEM_SS_ACQUIRE_DEBUG");
dbg_acquire = (e && *e && *e != '0') ? 1 : 0;
}
pthread_mutex_lock(&g_shared_pool.alloc_lock);
// ========== Stage 1: Reuse EMPTY slots from free list ==========
// Best case: Same class freed a slot, reuse immediately (cache-hot)
SharedSSMeta* reuse_meta = NULL;
int reuse_slot_idx = -1;
if (sp_freelist_pop(class_idx, &reuse_meta, &reuse_slot_idx)) {
// Found EMPTY slot for this class - reactivate it
if (sp_slot_mark_active(reuse_meta, reuse_slot_idx, class_idx) == 0) {
SuperSlab* ss = reuse_meta->ss;
if (dbg_acquire == 1) {
fprintf(stderr, "[SP_ACQUIRE_STAGE1] class=%d reusing EMPTY slot (ss=%p slab=%d)\n",
class_idx, (void*)ss, reuse_slot_idx);
}
// Update SuperSlab metadata
ss->slab_bitmap |= (1u << reuse_slot_idx);
ss->slabs[reuse_slot_idx].class_idx = (uint8_t)class_idx;
if (ss->active_slabs == 0) {
// Was empty, now active again
ss->active_slabs = 1;
g_shared_pool.active_count++;
}
// Update hint
g_shared_pool.class_hints[class_idx] = ss;
*ss_out = ss;
*slab_idx_out = reuse_slot_idx;
pthread_mutex_unlock(&g_shared_pool.alloc_lock);
return 0; // ✅ Stage 1 success
}
}
// ========== Stage 2: Find UNUSED slots in existing SuperSlabs ==========
// Scan all SuperSlabs for UNUSED slots
for (uint32_t i = 0; i < g_shared_pool.ss_meta_count; i++) {
SharedSSMeta* meta = &g_shared_pool.ss_metadata[i];
int unused_idx = sp_slot_find_unused(meta);
if (unused_idx >= 0) {
// Found UNUSED slot - activate it
if (sp_slot_mark_active(meta, unused_idx, class_idx) == 0) {
SuperSlab* ss = meta->ss;
if (dbg_acquire == 1) {
fprintf(stderr, "[SP_ACQUIRE_STAGE2] class=%d using UNUSED slot (ss=%p slab=%d)\n",
class_idx, (void*)ss, unused_idx);
}
// Update SuperSlab metadata
ss->slab_bitmap |= (1u << unused_idx);
ss->slabs[unused_idx].class_idx = (uint8_t)class_idx;
if (ss->active_slabs == 0) {
ss->active_slabs = 1;
g_shared_pool.active_count++;
}
// Update hint
g_shared_pool.class_hints[class_idx] = ss;
*ss_out = ss;
*slab_idx_out = unused_idx;
pthread_mutex_unlock(&g_shared_pool.alloc_lock);
return 0; // ✅ Stage 2 success
}
}
}
// ========== Stage 3: Get new SuperSlab ==========
// Try LRU cache first, then mmap
SuperSlab* new_ss = NULL;
// Stage 3a: Try LRU cache
extern SuperSlab* hak_ss_lru_pop(uint8_t size_class);
new_ss = hak_ss_lru_pop((uint8_t)class_idx);
int from_lru = (new_ss != NULL);
// Stage 3b: If LRU miss, allocate new SuperSlab
if (!new_ss) {
new_ss = shared_pool_allocate_superslab_unlocked();
}
if (dbg_acquire == 1 && new_ss) {
fprintf(stderr, "[SP_ACQUIRE_STAGE3] class=%d new SuperSlab (ss=%p from_lru=%d)\n",
class_idx, (void*)new_ss, from_lru);
}
if (!new_ss) {
pthread_mutex_unlock(&g_shared_pool.alloc_lock);
return -1; // ❌ Out of memory
}
// Create metadata for this new SuperSlab
SharedSSMeta* new_meta = sp_meta_find_or_create(new_ss);
if (!new_meta) {
pthread_mutex_unlock(&g_shared_pool.alloc_lock);
return -1; // ❌ Metadata allocation failed
}
// Assign first slot to this class
int first_slot = 0;
if (sp_slot_mark_active(new_meta, first_slot, class_idx) != 0) {
pthread_mutex_unlock(&g_shared_pool.alloc_lock);
return -1; // ❌ Should not happen
}
// Update SuperSlab metadata
new_ss->slab_bitmap |= (1u << first_slot);
new_ss->slabs[first_slot].class_idx = (uint8_t)class_idx;
new_ss->active_slabs = 1;
g_shared_pool.active_count++;
// Update hint
g_shared_pool.class_hints[class_idx] = new_ss;
*ss_out = new_ss;
*slab_idx_out = first_slot;
pthread_mutex_unlock(&g_shared_pool.alloc_lock);
return 0; // ✅ Stage 3 success
}
void
shared_pool_release_slab(SuperSlab* ss, int slab_idx)
{
// Phase 12: SP-SLOT Box - Slot-based Release
//
// Flow:
// 1. Validate inputs and check meta->used == 0
// 2. Find SharedSSMeta for this SuperSlab
// 3. Mark slot ACTIVE → EMPTY
// 4. Push to per-class free list (enables same-class reuse)
// 5. If all slots EMPTY → superslab_free() → LRU cache
if (!ss) {
return;
}
if (slab_idx < 0 || slab_idx >= SLABS_PER_SUPERSLAB_MAX) {
return;
}
// Debug logging
static int dbg = -1;
if (__builtin_expect(dbg == -1, 0)) {
const char* e = getenv("HAKMEM_SS_FREE_DEBUG");
dbg = (e && *e && *e != '0') ? 1 : 0;
}
pthread_mutex_lock(&g_shared_pool.alloc_lock);
TinySlabMeta* slab_meta = &ss->slabs[slab_idx];
if (slab_meta->used != 0) {
// Not actually empty; nothing to do
pthread_mutex_unlock(&g_shared_pool.alloc_lock);
return;
}
uint8_t class_idx = slab_meta->class_idx;
if (dbg == 1) {
fprintf(stderr, "[SP_SLOT_RELEASE] ss=%p slab_idx=%d class=%d used=0 (marking EMPTY)\n",
(void*)ss, slab_idx, class_idx);
}
// Find SharedSSMeta for this SuperSlab
SharedSSMeta* sp_meta = NULL;
for (uint32_t i = 0; i < g_shared_pool.ss_meta_count; i++) {
if (g_shared_pool.ss_metadata[i].ss == ss) {
sp_meta = &g_shared_pool.ss_metadata[i];
break;
}
}
if (!sp_meta) {
// SuperSlab not in SP-SLOT system yet - create metadata
sp_meta = sp_meta_find_or_create(ss);
if (!sp_meta) {
pthread_mutex_unlock(&g_shared_pool.alloc_lock);
return; // Failed to create metadata
}
}
// Mark slot as EMPTY (ACTIVE → EMPTY)
if (sp_slot_mark_empty(sp_meta, slab_idx) != 0) {
pthread_mutex_unlock(&g_shared_pool.alloc_lock);
return; // Slot wasn't ACTIVE
}
// Update SuperSlab metadata
uint32_t bit = (1u << slab_idx);
if (ss->slab_bitmap & bit) {
ss->slab_bitmap &= ~bit;
slab_meta->class_idx = 255; // UNASSIGNED
if (ss->active_slabs > 0) {
ss->active_slabs--;
if (ss->active_slabs == 0 && g_shared_pool.active_count > 0) {
g_shared_pool.active_count--;
}
}
}
// Push to per-class free list (enables reuse by same class)
if (class_idx < TINY_NUM_CLASSES_SS) {
sp_freelist_push(class_idx, sp_meta, slab_idx);
if (dbg == 1) {
fprintf(stderr, "[SP_SLOT_FREELIST] class=%d pushed slot (ss=%p slab=%d) count=%u active_slots=%u/%u\n",
class_idx, (void*)ss, slab_idx, g_shared_pool.free_slots[class_idx].count,
sp_meta->active_slots, sp_meta->total_slots);
}
}
// Check if SuperSlab is now completely empty (all slots EMPTY or UNUSED)
if (sp_meta->active_slots == 0) {
if (dbg == 1) {
fprintf(stderr, "[SP_SLOT_COMPLETELY_EMPTY] ss=%p active_slots=0 (calling superslab_free)\n",
(void*)ss);
}
pthread_mutex_unlock(&g_shared_pool.alloc_lock);
// Free SuperSlab:
// 1. Try LRU cache (hak_ss_lru_push) - lazy deallocation
// 2. Or munmap if LRU is full - eager deallocation
extern void superslab_free(SuperSlab* ss);
superslab_free(ss);
return;
}
pthread_mutex_unlock(&g_shared_pool.alloc_lock);
}