diff --git a/core/box/smallobject_policy_v7_box.h b/core/box/smallobject_policy_v7_box.h index c07ef645..c8c4ca16 100644 --- a/core/box/smallobject_policy_v7_box.h +++ b/core/box/smallobject_policy_v7_box.h @@ -1,9 +1,10 @@ -// smallobject_policy_v7_box.h - SmallObject Policy v7 (Phase v7-4) +// smallobject_policy_v7_box.h - SmallObject Policy v7 (Phase v7-7: Learner integration) // // Purpose: // - Centralized routing policy for ULTRA / v7 / MID_v3 / LEGACY // - Single source of truth for class → route_kind mapping // - ENV configuration managed in one place (L3 Policy layer) +// - Learner: dynamic route switching based on workload stats (v7-7) #ifndef HAKMEM_SMALLOBJECT_POLICY_V7_BOX_H #define HAKMEM_SMALLOBJECT_POLICY_V7_BOX_H @@ -46,4 +47,58 @@ void small_policy_v7_init_from_env(SmallPolicyV7* policy); /// Get route kind name for debugging const char* small_route_kind_name(SmallRouteKind kind); +// ============================================================================ +// Learner Stats (Phase v7-7) +// ============================================================================ + +/// Per-class stats for Learner +typedef struct SmallLearnerClassStatsV7 { + uint64_t total_allocs; // Total allocs for this class (all routes) + uint64_t v7_allocs; // Allocs via v7 route + uint64_t v7_retires; // Page retires from v7 + uint32_t sample_count; // Number of samples +} SmallLearnerClassStatsV7; + +/// Aggregate Learner stats +typedef struct SmallLearnerStatsV7 { + SmallLearnerClassStatsV7 per_class[8]; // C0-C7 + uint64_t total_retires; // Total page retires + uint64_t eval_count; // Number of evaluations +} SmallLearnerStatsV7; + +// ============================================================================ +// Learner API (Phase v7-7) +// ============================================================================ + +/// Record a page retire for Learner stats (called from ColdIface) +/// @param class_idx: Size class of the retired page +/// @param capacity: Capacity of the page (used as traffic proxy) +void small_learner_v7_record_retire(uint32_t class_idx, uint64_t capacity); + +/// Record a page refill for Learner stats (called from ColdIface) +/// @param class_idx: Size class of the refilled page +/// @param capacity: Capacity of the page (used as traffic proxy) +void small_learner_v7_record_refill(uint32_t class_idx, uint64_t capacity); + +/// Update policy from Learner stats (called periodically) +/// Decision rule: if C5 alloc ratio > 30%, route C5 to v7, else MID_v3 +/// @param stats: Learner stats to evaluate +/// @param policy_out: Policy to update +void small_policy_v7_update_from_learner( + const SmallLearnerStatsV7* stats, + SmallPolicyV7* policy_out +); + +/// Get current Learner stats snapshot (for debugging/OBSERVE) +const SmallLearnerStatsV7* small_learner_v7_stats_snapshot(void); + +/// Force re-evaluation of policy from current stats +void small_learner_v7_evaluate(void); + +/// Learner threshold for C5 route decision (default: 30%) +#define SMALL_LEARNER_C5_THRESHOLD_PCT 30 + +/// Evaluation interval (every N refills) +#define SMALL_LEARNER_EVAL_INTERVAL 100 + #endif // HAKMEM_SMALLOBJECT_POLICY_V7_BOX_H diff --git a/core/smallobject_cold_iface_v7.c b/core/smallobject_cold_iface_v7.c index 950000e0..52e10bbc 100644 --- a/core/smallobject_cold_iface_v7.c +++ b/core/smallobject_cold_iface_v7.c @@ -20,6 +20,7 @@ #include "box/smallobject_cold_iface_v7_box.h" #include "box/smallsegment_v7_box.h" #include "box/region_id_v6_box.h" +#include "box/smallobject_policy_v7_box.h" // v7-7: Learner integration #include "tiny_region_id.h" // v7-5a: For HEADER_MAGIC, HEADER_CLASS_MASK #ifndef likely @@ -183,6 +184,9 @@ SmallPageMeta_v7* small_cold_v7_refill_page(SmallHeapCtx_v7* ctx, uint32_t class SmallClassHeap_v7* heap = &ctx->cls[class_idx]; heap->current = page; + // v7-7: Feed refill to Learner for workload detection + small_learner_v7_record_refill(class_idx, capacity); + return page; } @@ -231,11 +235,13 @@ void small_cold_v7_retire_page(SmallHeapCtx_v7* ctx, SmallPageMeta_v7* page) { } // ============================================================================ -// Stats Publishing (stub for now) +// Stats Publishing (v7-7: Learner integration) // ============================================================================ void small_cold_v7_publish_stats(const SmallPageStatsV7* stats) { - // TODO: Future integration with Learner/PolicyBox - // For now, just a no-op - (void)stats; + if (!stats) return; + + // v7-7: Feed stats to Learner for dynamic route switching + // Note: v7-5a removed alloc_count from hot path, use capacity as traffic proxy + small_learner_v7_record_retire(stats->class_idx, stats->capacity); } diff --git a/core/smallobject_policy_v7.c b/core/smallobject_policy_v7.c index 1181bc26..2a912486 100644 --- a/core/smallobject_policy_v7.c +++ b/core/smallobject_policy_v7.c @@ -1,4 +1,4 @@ -// smallobject_policy_v7.c - Policy Box implementation +// smallobject_policy_v7.c - Policy Box implementation (Phase v7-7: Learner integration) #include #include @@ -11,16 +11,41 @@ #endif // ============================================================================ -// TLS Policy Snapshot +// Learner Stats (Global, v7-7) // ============================================================================ +static SmallLearnerStatsV7 g_small_learner_stats_v7; +static int g_learner_v7_enabled = -1; // -1: uninit, 0: disabled, 1: enabled + +static inline int learner_v7_enabled(void) { + if (unlikely(g_learner_v7_enabled < 0)) { + // Enable Learner only when v7 is enabled + const char* e = getenv("HAKMEM_SMALL_HEAP_V7_ENABLED"); + g_learner_v7_enabled = (e && *e && *e != '0') ? 1 : 0; + } + return g_learner_v7_enabled; +} + +// ============================================================================ +// TLS Policy Snapshot (v7-7: version-based invalidation) +// ============================================================================ + +static uint32_t g_policy_v7_version = 0; // Global version, incremented by Learner + static __thread SmallPolicyV7 g_small_policy_v7; -static __thread int g_small_policy_v7_init = 0; +static __thread uint32_t g_small_policy_v7_version = 0; // TLS cached version const SmallPolicyV7* small_policy_v7_snapshot(void) { - if (unlikely(!g_small_policy_v7_init)) { + // Check if TLS cache is stale (version mismatch or uninitialized) + if (unlikely(g_small_policy_v7_version != g_policy_v7_version || g_policy_v7_version == 0)) { small_policy_v7_init_from_env(&g_small_policy_v7); - g_small_policy_v7_init = 1; + + // v7-7: Apply Learner-driven route updates + if (learner_v7_enabled() && g_small_learner_stats_v7.total_retires > 0) { + small_policy_v7_update_from_learner(&g_small_learner_stats_v7, &g_small_policy_v7); + } + + g_small_policy_v7_version = g_policy_v7_version ? g_policy_v7_version : 1; } return &g_small_policy_v7; } @@ -116,3 +141,101 @@ const char* small_route_kind_name(SmallRouteKind kind) { default: return "UNKNOWN"; } } + +// ============================================================================ +// Learner Implementation (Phase v7-7) +// ============================================================================ + +// Total refills (for evaluation interval) +static uint64_t g_small_learner_total_refills = 0; + +void small_learner_v7_record_refill(uint32_t class_idx, uint64_t capacity) { + if (!learner_v7_enabled()) return; + if (class_idx >= 8) return; + + // Record stats: refill indicates page was needed (current page exhausted) + SmallLearnerClassStatsV7* cls = &g_small_learner_stats_v7.per_class[class_idx]; + cls->v7_allocs += capacity; // Use capacity as proxy for traffic volume + cls->sample_count++; + + g_small_learner_total_refills++; + + // Periodic evaluation (on refills) + if (g_small_learner_total_refills % SMALL_LEARNER_EVAL_INTERVAL == 0) { + small_learner_v7_evaluate(); + } +} + +void small_learner_v7_record_retire(uint32_t class_idx, uint64_t capacity) { + if (!learner_v7_enabled()) return; + if (class_idx >= 8) return; + + // Record stats (atomic would be better for multi-thread, but keep simple for now) + // Note: v7-5a removed per-page alloc_count from hot path, so we use capacity instead + // capacity represents "slots available on page" which approximates traffic volume + SmallLearnerClassStatsV7* cls = &g_small_learner_stats_v7.per_class[class_idx]; + cls->v7_retires++; + + g_small_learner_stats_v7.total_retires++; + + (void)capacity; // Not used for now (kept for API compatibility) +} + +void small_policy_v7_update_from_learner( + const SmallLearnerStatsV7* stats, + SmallPolicyV7* policy_out +) { + if (!stats || !policy_out) return; + + // Calculate total allocs across all classes + uint64_t total_allocs = 0; + for (int i = 0; i < 8; i++) { + total_allocs += stats->per_class[i].v7_allocs; + } + + if (total_allocs == 0) return; // No data yet + + // C5 decision: if C5 ratio > threshold, route to v7, else MID_v3 + uint64_t c5_allocs = stats->per_class[5].v7_allocs; + uint64_t c5_ratio_pct = (c5_allocs * 100) / total_allocs; + + SmallRouteKind old_c5_route = policy_out->route_kind[5]; + SmallRouteKind new_c5_route; + + if (c5_ratio_pct >= SMALL_LEARNER_C5_THRESHOLD_PCT) { + // C5-heavy workload → keep C5 on v7 + new_c5_route = SMALL_ROUTE_V7; + } else { + // Mixed workload → move C5 to MID_v3 + new_c5_route = SMALL_ROUTE_MID_V3; + } + + // Only log and update if route changed + if (old_c5_route != new_c5_route) { + // Log only the first switch (to avoid spam) + static int g_learner_v7_switch_logged = 0; + if (!g_learner_v7_switch_logged) { + g_learner_v7_switch_logged = 1; + fprintf(stderr, "[LEARNER_V7] C5 route switch: %s → %s (C5 ratio=%lu%%, threshold=%d%%)\n", + small_route_kind_name(old_c5_route), + small_route_kind_name(new_c5_route), + (unsigned long)c5_ratio_pct, + SMALL_LEARNER_C5_THRESHOLD_PCT); + } + policy_out->route_kind[5] = new_c5_route; + } +} + +const SmallLearnerStatsV7* small_learner_v7_stats_snapshot(void) { + return &g_small_learner_stats_v7; +} + +void small_learner_v7_evaluate(void) { + if (!learner_v7_enabled()) return; + + // Increment global version to invalidate all TLS caches + // Next call to small_policy_v7_snapshot() will re-apply Learner updates + __sync_fetch_and_add(&g_policy_v7_version, 1); + + g_small_learner_stats_v7.eval_count++; +}