// hakmem_elo.c - ELO Rating Strategy Selection Implementation // // Multi-objective optimization using ELO rating system // Composite score: 40% CPU + 30% PageFaults + 30% Memory #include "hakmem_elo.h" #include "hakmem_debug_master.h" // Phase 4b: Master debug control #include #include #include #include #include // Global state static EloStrategyCandidate g_strategies[ELO_MAX_STRATEGIES]; static int g_num_strategies = 0; static int g_initialized = 0; static int g_current_strategy = 0; static uint64_t g_total_selections = 0; // ENV Cleanup Phase 4b: Use centralized quiet mode from hakmem_debug_master.h // This replaces the previous local is_quiet() implementation #define is_quiet() hak_is_quiet() // Score normalization constants (used in hak_elo_compute_score) // These define the "maximum" values for metric normalization (0-1 range) static const double ELO_MAX_CPU_NS = 100000.0; // 100 microseconds static const double ELO_MAX_PAGE_FAULTS = 1000.0; // 1000 page faults static const double ELO_MAX_BYTES_LIVE = 100000000.0; // 100 MB // Strategy threshold presets (geometric progression from 512KB to 8MB) static const size_t STRATEGY_THRESHOLDS[] = { 524288, // 512KB 786432, // 768KB 1048576, // 1MB 1572864, // 1.5MB 2097152, // 2MB 3145728, // 3MB 4194304, // 4MB 6291456, // 6MB 8388608, // 8MB 12582912, // 12MB 16777216, // 16MB 33554432 // 32MB }; // Fast random (for epsilon-greedy) static uint64_t g_rng_state = 123456789; static uint64_t fast_random(void) { g_rng_state ^= g_rng_state << 13; g_rng_state ^= g_rng_state >> 7; g_rng_state ^= g_rng_state << 17; return g_rng_state; } // Initialize ELO system void hak_elo_init(void) { if (g_initialized) return; // Initialize random seed g_rng_state = (uint64_t)time(NULL); // Create strategy candidates g_num_strategies = sizeof(STRATEGY_THRESHOLDS) / sizeof(STRATEGY_THRESHOLDS[0]); if (g_num_strategies > ELO_MAX_STRATEGIES) { g_num_strategies = ELO_MAX_STRATEGIES; } for (int i = 0; i < g_num_strategies; i++) { g_strategies[i].strategy_id = i; g_strategies[i].elo_rating = ELO_INITIAL_RATING; g_strategies[i].wins = 0; g_strategies[i].losses = 0; g_strategies[i].draws = 0; g_strategies[i].samples = 0; g_strategies[i].threshold_bytes = STRATEGY_THRESHOLDS[i]; g_strategies[i].active = 1; } // Select initial strategy (middle of the pack) g_current_strategy = g_num_strategies / 2; g_initialized = 1; #if !HAKMEM_BUILD_RELEASE fprintf(stderr, "[ELO] Initialized %d strategies (thresholds: 512KB-32MB)\n", g_num_strategies); #endif } // Shutdown ELO system void hak_elo_shutdown(void) { if (!g_initialized) return; hak_elo_print_leaderboard(); g_initialized = 0; } // Epsilon-greedy strategy selection int hak_elo_select_strategy(void) { if (!g_initialized) hak_elo_init(); g_total_selections++; // Epsilon-greedy: 10% exploration, 90% exploitation double rand_val = (double)(fast_random() % 1000) / 1000.0; if (rand_val < ELO_EPSILON) { // Exploration: random active strategy int active_count = 0; int active_indices[ELO_MAX_STRATEGIES]; for (int i = 0; i < g_num_strategies; i++) { if (g_strategies[i].active) { active_indices[active_count++] = i; } } if (active_count > 0) { int idx = fast_random() % active_count; g_current_strategy = active_indices[idx]; } } else { // Exploitation: highest ELO rating double best_rating = -1e9; int best_idx = 0; for (int i = 0; i < g_num_strategies; i++) { if (g_strategies[i].active && g_strategies[i].elo_rating > best_rating) { best_rating = g_strategies[i].elo_rating; best_idx = i; } } g_current_strategy = best_idx; } return g_current_strategy; } // Get threshold for strategy size_t hak_elo_get_threshold(int strategy_id) { if (!g_initialized) hak_elo_init(); if (strategy_id < 0 || strategy_id >= g_num_strategies) { return STRATEGY_THRESHOLDS[g_num_strategies / 2]; } return g_strategies[strategy_id].threshold_bytes; } // Record allocation result void hak_elo_record_alloc(int strategy_id, size_t size, uint64_t duration_ns) { if (!g_initialized) return; if (strategy_id < 0 || strategy_id >= g_num_strategies) return; EloStrategyCandidate* strategy = &g_strategies[strategy_id]; strategy->samples++; // Simple tracking (real implementation would track full stats) (void)size; (void)duration_ns; } // Compute composite score (normalized 0-1) double hak_elo_compute_score(const EloAllocStats* stats) { // Normalize each metric (lower is better, so invert) // Using constants defined at file scope for maintainability double cpu_score = 1.0 - fmin(stats->cpu_ns / ELO_MAX_CPU_NS, 1.0); double pf_score = 1.0 - fmin(stats->page_faults / ELO_MAX_PAGE_FAULTS, 1.0); double mem_score = 1.0 - fmin(stats->bytes_live / ELO_MAX_BYTES_LIVE, 1.0); // Weighted combination: 40% CPU, 30% PageFaults, 30% Memory return 0.4 * cpu_score + 0.3 * pf_score + 0.3 * mem_score; } // Update ELO ratings (standard ELO formula) void hak_elo_update_ratings(EloStrategyCandidate* a, EloStrategyCandidate* b, double score_diff) { // Expected probability of A winning double expected_a = 1.0 / (1.0 + pow(10.0, (b->elo_rating - a->elo_rating) / 400.0)); // Actual result: 1.0 = A wins, 0.0 = B wins, 0.5 = draw double actual_a; if (score_diff > 0.01) { actual_a = 1.0; // A wins a->wins++; b->losses++; } else if (score_diff < -0.01) { actual_a = 0.0; // B wins a->losses++; b->wins++; } else { actual_a = 0.5; // Draw a->draws++; b->draws++; } // Update ratings a->elo_rating += ELO_K_FACTOR * (actual_a - expected_a); b->elo_rating += ELO_K_FACTOR * ((1.0 - actual_a) - (1.0 - expected_a)); } // Trigger ELO evolution (pairwise comparison) void hak_elo_trigger_evolution(void) { if (!g_initialized) return; #if !HAKMEM_BUILD_RELEASE fprintf(stderr, "[ELO] Triggering evolution (pairwise comparison)...\n"); #endif // Count active strategies with enough samples int eligible[ELO_MAX_STRATEGIES]; int eligible_count = 0; for (int i = 0; i < g_num_strategies; i++) { if (g_strategies[i].active && g_strategies[i].samples >= ELO_MIN_SAMPLES) { eligible[eligible_count++] = i; } } if (eligible_count < 2) { if (!is_quiet()) fprintf(stderr, "[ELO] Not enough eligible strategies (need 2, have %d)\n", eligible_count); return; } // Perform pairwise comparisons (all pairs) int comparisons = 0; for (int i = 0; i < eligible_count; i++) { for (int j = i + 1; j < eligible_count; j++) { EloStrategyCandidate* a = &g_strategies[eligible[i]]; EloStrategyCandidate* b = &g_strategies[eligible[j]]; // Mock comparison (in real implementation, would run N samples) // For now, simulate based on threshold proximity to 2MB (optimal) size_t optimal = 2097152; // 2MB double score_a = 1.0 / (1.0 + fabs((double)a->threshold_bytes - (double)optimal) / (double)optimal); double score_b = 1.0 / (1.0 + fabs((double)b->threshold_bytes - (double)optimal) / (double)optimal); double score_diff = score_a - score_b; hak_elo_update_ratings(a, b, score_diff); comparisons++; } } if (!is_quiet()) fprintf(stderr, "[ELO] Completed %d pairwise comparisons\n", comparisons); // Survival: keep top-M strategies if (eligible_count > ELO_SURVIVAL_COUNT) { // Sort by ELO rating int sorted_indices[ELO_MAX_STRATEGIES]; for (int i = 0; i < eligible_count; i++) { sorted_indices[i] = eligible[i]; } // Simple bubble sort (good enough for 12 strategies) for (int i = 0; i < eligible_count - 1; i++) { for (int j = 0; j < eligible_count - i - 1; j++) { if (g_strategies[sorted_indices[j]].elo_rating < g_strategies[sorted_indices[j + 1]].elo_rating) { int temp = sorted_indices[j]; sorted_indices[j] = sorted_indices[j + 1]; sorted_indices[j + 1] = temp; } } } // Deactivate bottom strategies for (int i = ELO_SURVIVAL_COUNT; i < eligible_count; i++) { int idx = sorted_indices[i]; g_strategies[idx].active = 0; if (!is_quiet()) fprintf(stderr, "[ELO] Strategy %d (%.0fKB) eliminated (rating: %.1f)\n", idx, g_strategies[idx].threshold_bytes / 1024.0, g_strategies[idx].elo_rating); } } hak_elo_print_leaderboard(); } // Print statistics void hak_elo_print_stats(void) { if (!g_initialized) return; if (!is_quiet()) fprintf(stderr, "\n[ELO] Statistics:\n"); if (!is_quiet()) fprintf(stderr, " Total selections: %lu\n", g_total_selections); if (!is_quiet()) fprintf(stderr, " Active strategies: "); int active_count = 0; for (int i = 0; i < g_num_strategies; i++) { if (g_strategies[i].active) active_count++; } if (!is_quiet()) fprintf(stderr, "%d/%d\n", active_count, g_num_strategies); } // Print leaderboard void hak_elo_print_leaderboard(void) { if (!g_initialized) return; if (!is_quiet()) { fprintf(stderr, "\n[ELO] Leaderboard:\n"); fprintf(stderr, " Rank | ID | Threshold | ELO Rating | W/L/D | Samples | Status\n"); fprintf(stderr, " -----|----|-----------+------------+-------+---------+--------\n"); } // Sort by ELO rating int sorted_indices[ELO_MAX_STRATEGIES]; for (int i = 0; i < g_num_strategies; i++) { sorted_indices[i] = i; } for (int i = 0; i < g_num_strategies - 1; i++) { for (int j = 0; j < g_num_strategies - i - 1; j++) { if (g_strategies[sorted_indices[j]].elo_rating < g_strategies[sorted_indices[j + 1]].elo_rating) { int temp = sorted_indices[j]; sorted_indices[j] = sorted_indices[j + 1]; sorted_indices[j + 1] = temp; } } } for (int i = 0; i < g_num_strategies; i++) { int idx = sorted_indices[i]; EloStrategyCandidate* s = &g_strategies[idx]; if (!is_quiet()) fprintf(stderr, " %4d | %2d | %7.0fKB | %10.1f | %lu/%lu/%lu | %7lu | %s\n", i + 1, s->strategy_id, s->threshold_bytes / 1024.0, s->elo_rating, s->wins, s->losses, s->draws, s->samples, s->active ? "ACTIVE" : "eliminated"); } } // Release-silent logging #include "hakmem_internal.h"