2025-11-05 12:31:14 +09:00
|
|
|
// 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"
|
2025-11-28 16:03:20 +09:00
|
|
|
#include "hakmem_debug_master.h" // Phase 4b: Master debug control
|
2025-11-05 12:31:14 +09:00
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <math.h>
|
|
|
|
|
#include <time.h>
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
2025-11-28 16:03:20 +09:00
|
|
|
// 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()
|
2025-11-28 15:23:48 +09:00
|
|
|
|
2025-11-05 12:31:14 +09:00
|
|
|
// 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;
|
|
|
|
|
|
2025-11-11 01:47:06 +09:00
|
|
|
#if !HAKMEM_BUILD_RELEASE
|
|
|
|
|
fprintf(stderr, "[ELO] Initialized %d strategies (thresholds: 512KB-32MB)\n", g_num_strategies);
|
|
|
|
|
#endif
|
2025-11-05 12:31:14 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
// For now, use simple heuristics
|
|
|
|
|
const double MAX_CPU_NS = 100000.0; // 100 microseconds
|
|
|
|
|
const double MAX_PAGE_FAULTS = 1000.0;
|
|
|
|
|
const double MAX_BYTES_LIVE = 100000000.0; // 100MB
|
|
|
|
|
|
|
|
|
|
double cpu_score = 1.0 - fmin(stats->cpu_ns / MAX_CPU_NS, 1.0);
|
|
|
|
|
double pf_score = 1.0 - fmin(stats->page_faults / MAX_PAGE_FAULTS, 1.0);
|
|
|
|
|
double mem_score = 1.0 - fmin(stats->bytes_live / 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;
|
|
|
|
|
|
2025-11-11 01:47:06 +09:00
|
|
|
#if !HAKMEM_BUILD_RELEASE
|
|
|
|
|
fprintf(stderr, "[ELO] Triggering evolution (pairwise comparison)...\n");
|
|
|
|
|
#endif
|
2025-11-05 12:31:14 +09:00
|
|
|
|
|
|
|
|
// 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) {
|
2025-11-28 15:23:48 +09:00
|
|
|
if (!is_quiet()) fprintf(stderr, "[ELO] Not enough eligible strategies (need 2, have %d)\n", eligible_count);
|
2025-11-05 12:31:14 +09:00
|
|
|
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++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-28 15:23:48 +09:00
|
|
|
if (!is_quiet()) fprintf(stderr, "[ELO] Completed %d pairwise comparisons\n", comparisons);
|
2025-11-05 12:31:14 +09:00
|
|
|
|
|
|
|
|
// 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;
|
2025-11-28 15:23:48 +09:00
|
|
|
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);
|
2025-11-05 12:31:14 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hak_elo_print_leaderboard();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Print statistics
|
|
|
|
|
void hak_elo_print_stats(void) {
|
|
|
|
|
if (!g_initialized) return;
|
|
|
|
|
|
2025-11-28 15:23:48 +09:00
|
|
|
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: ");
|
2025-11-05 12:31:14 +09:00
|
|
|
int active_count = 0;
|
|
|
|
|
for (int i = 0; i < g_num_strategies; i++) {
|
|
|
|
|
if (g_strategies[i].active) active_count++;
|
|
|
|
|
}
|
2025-11-28 15:23:48 +09:00
|
|
|
if (!is_quiet()) fprintf(stderr, "%d/%d\n", active_count, g_num_strategies);
|
2025-11-05 12:31:14 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Print leaderboard
|
|
|
|
|
void hak_elo_print_leaderboard(void) {
|
|
|
|
|
if (!g_initialized) return;
|
|
|
|
|
|
2025-11-28 15:23:48 +09:00
|
|
|
if (!is_quiet()) {
|
2025-11-05 12:31:14 +09:00
|
|
|
fprintf(stderr, "\n[ELO] Leaderboard:\n");
|
|
|
|
|
fprintf(stderr, " Rank | ID | Threshold | ELO Rating | W/L/D | Samples | Status\n");
|
|
|
|
|
fprintf(stderr, " -----|----|-----------+------------+-------+---------+--------\n");
|
2025-11-28 15:23:48 +09:00
|
|
|
}
|
2025-11-05 12:31:14 +09:00
|
|
|
|
|
|
|
|
// 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];
|
2025-11-28 15:23:48 +09:00
|
|
|
if (!is_quiet()) fprintf(stderr, " %4d | %2d | %7.0fKB | %10.1f | %lu/%lu/%lu | %7lu | %s\n",
|
2025-11-05 12:31:14 +09:00
|
|
|
i + 1, s->strategy_id, s->threshold_bytes / 1024.0, s->elo_rating,
|
|
|
|
|
s->wins, s->losses, s->draws, s->samples,
|
2025-11-28 15:23:48 +09:00
|
|
|
s->active ? "ACTIVE" : "eliminated");
|
2025-11-05 12:31:14 +09:00
|
|
|
}
|
|
|
|
|
}
|
2025-11-11 01:47:06 +09:00
|
|
|
// Release-silent logging
|
|
|
|
|
#include "hakmem_internal.h"
|