303 lines
11 KiB
C
303 lines
11 KiB
C
|
|
// ss_tier_box.h - P-Tier: Utilization-Aware SuperSlab Tiering Box
|
||
|
|
// Purpose: Manage SuperSlab tier transitions based on utilization
|
||
|
|
// License: MIT
|
||
|
|
// Date: 2025-12-04
|
||
|
|
|
||
|
|
#ifndef HAK_SS_TIER_BOX_H
|
||
|
|
#define HAK_SS_TIER_BOX_H
|
||
|
|
|
||
|
|
#include <stdatomic.h>
|
||
|
|
#include <stdbool.h>
|
||
|
|
#include <stdlib.h> // for getenv()
|
||
|
|
#include "../superslab/superslab_types.h"
|
||
|
|
|
||
|
|
// ============================================================================
|
||
|
|
// P-Tier: Utilization-Aware SuperSlab Tiering Box
|
||
|
|
// ============================================================================
|
||
|
|
//
|
||
|
|
// Goal: Reduce registry pressure by consolidating allocations to HOT SuperSlabs
|
||
|
|
// and efficiently draining DRAINING SuperSlabs.
|
||
|
|
//
|
||
|
|
// Tier Definitions:
|
||
|
|
// - HOT (>25% utilization): Accept new allocations, actively used
|
||
|
|
// - DRAINING (<=25% utilization): Drain only, no new allocations
|
||
|
|
// - FREE (0% utilization): Ready for LRU cache or munmap
|
||
|
|
//
|
||
|
|
// Strategy:
|
||
|
|
// - Allocations target HOT tier SuperSlabs only
|
||
|
|
// - DRAINING tier SuperSlabs accept no new allocations
|
||
|
|
// - Automatic transitions based on utilization thresholds
|
||
|
|
// - Hysteresis prevents thrashing between HOT and DRAINING
|
||
|
|
//
|
||
|
|
// Expected Benefits:
|
||
|
|
// - Reduced registry size (fewer partially-used SuperSlabs)
|
||
|
|
// - Improved cache locality (concentrated allocations)
|
||
|
|
// - Faster allocation (skip DRAINING SuperSlabs)
|
||
|
|
// - Efficient memory reclamation (clear path to FREE tier)
|
||
|
|
//
|
||
|
|
// Box Contract:
|
||
|
|
// - ss_tier_calc_utilization(): Calculate current utilization [0.0, 1.0]
|
||
|
|
// - ss_tier_check_transition(): Check and perform tier transitions
|
||
|
|
// - ss_tier_get(): Get current tier
|
||
|
|
// - ss_tier_is_hot(): Quick check if SuperSlab accepts allocations
|
||
|
|
// - ss_tier_set(): Force tier change (testing/debug)
|
||
|
|
//
|
||
|
|
// ============================================================================
|
||
|
|
|
||
|
|
// Default thresholds (can be overridden by environment variables)
|
||
|
|
#define SS_TIER_DOWN_THRESHOLD_DEFAULT 0.25f // HOT → DRAINING (25% utilization)
|
||
|
|
#define SS_TIER_UP_THRESHOLD_DEFAULT 0.50f // DRAINING → HOT (50% utilization, hysteresis)
|
||
|
|
|
||
|
|
// Environment variable support for runtime configuration
|
||
|
|
// ENV: HAKMEM_SS_TIER_DOWN_THRESHOLD (default: 0.25)
|
||
|
|
// ENV: HAKMEM_SS_TIER_UP_THRESHOLD (default: 0.50)
|
||
|
|
static inline float ss_tier_get_down_threshold(void) {
|
||
|
|
static float cached = -1.0f;
|
||
|
|
if (__builtin_expect(cached < 0.0f, 0)) {
|
||
|
|
const char* e = getenv("HAKMEM_SS_TIER_DOWN_THRESHOLD");
|
||
|
|
if (e && *e) {
|
||
|
|
float v = (float)atof(e);
|
||
|
|
cached = (v > 0.0f && v <= 1.0f) ? v : SS_TIER_DOWN_THRESHOLD_DEFAULT;
|
||
|
|
} else {
|
||
|
|
cached = SS_TIER_DOWN_THRESHOLD_DEFAULT;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return cached;
|
||
|
|
}
|
||
|
|
|
||
|
|
static inline float ss_tier_get_up_threshold(void) {
|
||
|
|
static float cached = -1.0f;
|
||
|
|
if (__builtin_expect(cached < 0.0f, 0)) {
|
||
|
|
const char* e = getenv("HAKMEM_SS_TIER_UP_THRESHOLD");
|
||
|
|
if (e && *e) {
|
||
|
|
float v = (float)atof(e);
|
||
|
|
cached = (v > 0.0f && v <= 1.0f) ? v : SS_TIER_UP_THRESHOLD_DEFAULT;
|
||
|
|
} else {
|
||
|
|
cached = SS_TIER_UP_THRESHOLD_DEFAULT;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return cached;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================================================
|
||
|
|
// 1. Utilization Calculation
|
||
|
|
// ============================================================================
|
||
|
|
//
|
||
|
|
// Calculates current utilization as: total_active_blocks / total_capacity
|
||
|
|
//
|
||
|
|
// Uses:
|
||
|
|
// - ss->total_active_blocks: Atomic counter of all active blocks across slabs
|
||
|
|
// - ss->active_slabs: Number of carved slabs
|
||
|
|
// - ss->slabs[].capacity: Per-slab capacity
|
||
|
|
//
|
||
|
|
// Returns: Utilization ratio [0.0, 1.0]
|
||
|
|
// 0.0 = completely empty (FREE tier candidate)
|
||
|
|
// 1.0 = fully utilized (strong HOT tier)
|
||
|
|
//
|
||
|
|
// Note: Uses relaxed memory order as this is a heuristic for tier classification,
|
||
|
|
// not a safety-critical invariant.
|
||
|
|
// ============================================================================
|
||
|
|
|
||
|
|
static inline float ss_tier_calc_utilization(SuperSlab* ss) {
|
||
|
|
if (!ss) return 0.0f;
|
||
|
|
|
||
|
|
// Get current active blocks (atomic load)
|
||
|
|
uint32_t active = atomic_load_explicit(&ss->total_active_blocks, memory_order_relaxed);
|
||
|
|
|
||
|
|
// Calculate total capacity across all active slabs
|
||
|
|
// Note: We sum capacity from active_slabs to handle per-slab class assignment (Phase 12)
|
||
|
|
uint32_t total_capacity = 0;
|
||
|
|
uint32_t max_slabs = (1u << ss->lg_size) / SLAB_SIZE;
|
||
|
|
if (max_slabs > SLABS_PER_SUPERSLAB_MAX) {
|
||
|
|
max_slabs = SLABS_PER_SUPERSLAB_MAX;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Iterate through active slabs and sum capacity
|
||
|
|
for (uint32_t i = 0; i < max_slabs && i < ss->active_slabs; i++) {
|
||
|
|
TinySlabMeta* meta = &ss->slabs[i];
|
||
|
|
if (meta->capacity > 0) {
|
||
|
|
total_capacity += meta->capacity;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Handle edge case: no capacity yet (fresh SuperSlab)
|
||
|
|
if (total_capacity == 0) {
|
||
|
|
return 0.0f;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Return utilization ratio
|
||
|
|
return (float)active / (float)total_capacity;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================================================
|
||
|
|
// 2. Tier Transition Check
|
||
|
|
// ============================================================================
|
||
|
|
//
|
||
|
|
// Checks current utilization and performs tier transitions if needed.
|
||
|
|
//
|
||
|
|
// Transition Rules:
|
||
|
|
// - HOT → DRAINING: utilization <= down_threshold (default: 25%)
|
||
|
|
// - DRAINING → HOT: utilization >= up_threshold (default: 50%, hysteresis)
|
||
|
|
// - DRAINING → FREE: utilization == 0% (all blocks freed)
|
||
|
|
// - FREE → HOT: First allocation (handled by allocation path, not here)
|
||
|
|
//
|
||
|
|
// Hysteresis Rationale:
|
||
|
|
// - Down threshold (25%) < Up threshold (50%) prevents oscillation
|
||
|
|
// - SuperSlab must demonstrate sustained activity to return to HOT
|
||
|
|
//
|
||
|
|
// Returns: true if tier transition occurred, false otherwise
|
||
|
|
//
|
||
|
|
// Thread Safety: Uses atomic compare_exchange for safe concurrent transitions
|
||
|
|
// ============================================================================
|
||
|
|
|
||
|
|
static inline bool ss_tier_check_transition(SuperSlab* ss) {
|
||
|
|
if (!ss) return false;
|
||
|
|
|
||
|
|
// Calculate current utilization
|
||
|
|
float util = ss_tier_calc_utilization(ss);
|
||
|
|
|
||
|
|
// Get current tier (atomic load)
|
||
|
|
uint8_t current_tier = atomic_load_explicit(&ss->tier, memory_order_acquire);
|
||
|
|
|
||
|
|
// Get thresholds (cached after first call)
|
||
|
|
float down_thresh = ss_tier_get_down_threshold();
|
||
|
|
float up_thresh = ss_tier_get_up_threshold();
|
||
|
|
|
||
|
|
// Determine target tier based on utilization and current state
|
||
|
|
uint8_t target_tier = current_tier;
|
||
|
|
|
||
|
|
switch (current_tier) {
|
||
|
|
case SS_TIER_HOT:
|
||
|
|
// HOT → DRAINING: Drop below down threshold
|
||
|
|
if (util <= down_thresh) {
|
||
|
|
target_tier = SS_TIER_DRAINING;
|
||
|
|
}
|
||
|
|
// HOT → FREE: Complete deallocation (rare, usually via DRAINING)
|
||
|
|
if (util == 0.0f) {
|
||
|
|
target_tier = SS_TIER_FREE;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case SS_TIER_DRAINING:
|
||
|
|
// DRAINING → HOT: Rise above up threshold (hysteresis)
|
||
|
|
if (util >= up_thresh) {
|
||
|
|
target_tier = SS_TIER_HOT;
|
||
|
|
}
|
||
|
|
// DRAINING → FREE: Complete deallocation
|
||
|
|
if (util == 0.0f) {
|
||
|
|
target_tier = SS_TIER_FREE;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case SS_TIER_FREE:
|
||
|
|
// FREE → HOT: First allocation (util > 0)
|
||
|
|
// Note: Typically handled by allocation path setting tier directly
|
||
|
|
if (util > 0.0f) {
|
||
|
|
target_tier = SS_TIER_HOT;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
default:
|
||
|
|
// Invalid tier, reset to HOT (defensive)
|
||
|
|
target_tier = SS_TIER_HOT;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
// If no transition needed, return early
|
||
|
|
if (target_tier == current_tier) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Attempt atomic transition (CAS loop for concurrent safety)
|
||
|
|
// Note: We use weak CAS in a loop for efficiency on weak-memory architectures
|
||
|
|
uint8_t expected = current_tier;
|
||
|
|
while (!atomic_compare_exchange_weak_explicit(
|
||
|
|
&ss->tier,
|
||
|
|
&expected,
|
||
|
|
target_tier,
|
||
|
|
memory_order_release, // Success: publish tier change
|
||
|
|
memory_order_relaxed // Failure: retry with updated expected
|
||
|
|
)) {
|
||
|
|
// Concurrent modification detected, re-evaluate
|
||
|
|
// If tier already changed to target, we're done
|
||
|
|
if (expected == target_tier) {
|
||
|
|
return false; // Another thread completed the transition
|
||
|
|
}
|
||
|
|
// Otherwise, retry with new expected value
|
||
|
|
}
|
||
|
|
|
||
|
|
// Transition successful
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================================================
|
||
|
|
// 3. Tier State Query
|
||
|
|
// ============================================================================
|
||
|
|
//
|
||
|
|
// Returns the current tier of the SuperSlab.
|
||
|
|
//
|
||
|
|
// Returns: SS_TIER_HOT, SS_TIER_DRAINING, or SS_TIER_FREE
|
||
|
|
//
|
||
|
|
// Memory Order: Acquire ensures we see all updates made before tier was set
|
||
|
|
// ============================================================================
|
||
|
|
|
||
|
|
static inline SSTier ss_tier_get(SuperSlab* ss) {
|
||
|
|
if (!ss) return SS_TIER_FREE; // Defensive: NULL = not usable
|
||
|
|
|
||
|
|
uint8_t tier = atomic_load_explicit(&ss->tier, memory_order_acquire);
|
||
|
|
return (SSTier)tier;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================================================
|
||
|
|
// 4. Hot Tier Check (Allocation Eligibility)
|
||
|
|
// ============================================================================
|
||
|
|
//
|
||
|
|
// Fast path check: Can this SuperSlab accept new allocations?
|
||
|
|
//
|
||
|
|
// Returns: true if SuperSlab is in HOT tier (accepts allocations)
|
||
|
|
// false otherwise (DRAINING or FREE, skip for allocation)
|
||
|
|
//
|
||
|
|
// Usage: Called by allocation path to filter candidate SuperSlabs
|
||
|
|
//
|
||
|
|
// Memory Order: Relaxed is sufficient as this is a filtering heuristic,
|
||
|
|
// not a safety invariant. Worst case: we occasionally skip
|
||
|
|
// a freshly-promoted HOT SuperSlab (benign race).
|
||
|
|
// ============================================================================
|
||
|
|
|
||
|
|
static inline bool ss_tier_is_hot(SuperSlab* ss) {
|
||
|
|
if (!ss) return false;
|
||
|
|
|
||
|
|
uint8_t tier = atomic_load_explicit(&ss->tier, memory_order_relaxed);
|
||
|
|
return (tier == SS_TIER_HOT);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================================================
|
||
|
|
// 5. Tier Force-Set (Testing/Debug Only)
|
||
|
|
// ============================================================================
|
||
|
|
//
|
||
|
|
// Directly sets the tier without utilization checks.
|
||
|
|
//
|
||
|
|
// WARNING: This bypasses all transition logic and should ONLY be used for:
|
||
|
|
// - Unit tests
|
||
|
|
// - Debug/instrumentation
|
||
|
|
// - Initialization (setting fresh SuperSlab to HOT)
|
||
|
|
//
|
||
|
|
// Do NOT use in production hot paths.
|
||
|
|
//
|
||
|
|
// Memory Order: Release ensures any prior modifications are visible after
|
||
|
|
// the tier change is observed by other threads.
|
||
|
|
// ============================================================================
|
||
|
|
|
||
|
|
static inline void ss_tier_set(SuperSlab* ss, SSTier tier) {
|
||
|
|
if (!ss) return;
|
||
|
|
|
||
|
|
// Validate tier value (defensive)
|
||
|
|
if (tier != SS_TIER_HOT && tier != SS_TIER_DRAINING && tier != SS_TIER_FREE) {
|
||
|
|
return; // Invalid tier, refuse to set
|
||
|
|
}
|
||
|
|
|
||
|
|
atomic_store_explicit(&ss->tier, (uint8_t)tier, memory_order_release);
|
||
|
|
}
|
||
|
|
|
||
|
|
#endif // HAK_SS_TIER_BOX_H
|