use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct AdaptivePrefetchPolicy {
pub prefetch_depth: usize,
pub aggressiveness: f64,
pub min_depth: usize,
pub max_depth: usize,
pub target_hit_rate: f64,
pub strategy: AdaptationStrategy,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AdaptationStrategy {
Conservative,
Balanced,
Aggressive,
Custom,
}
#[derive(Debug, Clone)]
pub struct PrefetchMetrics {
pub hit_rate: f64,
pub miss_rate: f64,
pub waste_rate: f64,
pub cache_hit_latency_us: f64,
pub cache_miss_latency_us: f64,
pub memory_pressure: f64,
pub predictability: f64,
}
impl Default for PrefetchMetrics {
fn default() -> Self {
Self {
hit_rate: 0.0,
miss_rate: 0.0,
waste_rate: 0.0,
cache_hit_latency_us: 0.0,
cache_miss_latency_us: 0.0,
memory_pressure: 0.0,
predictability: 0.0,
}
}
}
#[derive(Debug, Clone)]
pub struct TuningDecision {
pub new_depth: usize,
pub new_aggressiveness: f64,
pub reason: String,
pub confidence: f64,
}
pub struct AdaptivePrefetchTuner {
policy: Arc<Mutex<AdaptivePrefetchPolicy>>,
metrics_history: Arc<Mutex<VecDeque<(Instant, PrefetchMetrics)>>>,
max_history_size: usize,
tuning_interval: Duration,
last_tuning: Arc<Mutex<Instant>>,
}
impl AdaptivePrefetchPolicy {
pub fn new() -> Self {
Self {
prefetch_depth: 4,
aggressiveness: 0.5,
min_depth: 1,
max_depth: 32,
target_hit_rate: 0.8,
strategy: AdaptationStrategy::Balanced,
}
}
pub fn conservative() -> Self {
Self {
prefetch_depth: 2,
aggressiveness: 0.3,
min_depth: 1,
max_depth: 8,
target_hit_rate: 0.9,
strategy: AdaptationStrategy::Conservative,
}
}
pub fn aggressive() -> Self {
Self {
prefetch_depth: 8,
aggressiveness: 0.8,
min_depth: 2,
max_depth: 64,
target_hit_rate: 0.7,
strategy: AdaptationStrategy::Aggressive,
}
}
pub fn custom(min_depth: usize, max_depth: usize, target_hit_rate: f64) -> Self {
Self {
prefetch_depth: (min_depth + max_depth) / 2,
aggressiveness: 0.5,
min_depth,
max_depth,
target_hit_rate: target_hit_rate.clamp(0.0, 1.0),
strategy: AdaptationStrategy::Custom,
}
}
}
impl Default for AdaptivePrefetchPolicy {
fn default() -> Self {
Self::new()
}
}
impl AdaptivePrefetchTuner {
pub fn new(policy: AdaptivePrefetchPolicy) -> Self {
Self {
policy: Arc::new(Mutex::new(policy)),
metrics_history: Arc::new(Mutex::new(VecDeque::new())),
max_history_size: 100,
tuning_interval: Duration::from_secs(5),
last_tuning: Arc::new(Mutex::new(Instant::now())),
}
}
pub fn with_interval(mut self, interval: Duration) -> Self {
self.tuning_interval = interval;
self
}
pub fn with_history_size(mut self, size: usize) -> Self {
self.max_history_size = size;
self
}
pub fn update_metrics(&self, metrics: PrefetchMetrics) -> Option<TuningDecision> {
let mut history = self
.metrics_history
.lock()
.expect("lock should not be poisoned");
history.push_back((Instant::now(), metrics.clone()));
while history.len() > self.max_history_size {
history.pop_front();
}
drop(history);
let mut last_tuning = self
.last_tuning
.lock()
.expect("lock should not be poisoned");
if last_tuning.elapsed() < self.tuning_interval {
return None;
}
*last_tuning = Instant::now();
drop(last_tuning);
let decision = self.make_tuning_decision(&metrics)?;
let mut policy = self.policy.lock().expect("lock should not be poisoned");
policy.prefetch_depth = decision.new_depth;
policy.aggressiveness = decision.new_aggressiveness;
drop(policy);
Some(decision)
}
fn make_tuning_decision(&self, metrics: &PrefetchMetrics) -> Option<TuningDecision> {
let policy = self.policy.lock().expect("lock should not be poisoned");
let current_depth = policy.prefetch_depth;
let current_aggressiveness = policy.aggressiveness;
let strategy = policy.strategy;
let target_hit_rate = policy.target_hit_rate;
let min_depth = policy.min_depth;
let max_depth = policy.max_depth;
drop(policy);
let hit_rate_delta = metrics.hit_rate - target_hit_rate;
let (depth_adjustment, aggressiveness_adjustment, reason, confidence) = match strategy {
AdaptationStrategy::Conservative => self.conservative_decision(
metrics,
hit_rate_delta,
current_depth,
min_depth,
max_depth,
),
AdaptationStrategy::Balanced => {
self.balanced_decision(metrics, hit_rate_delta, current_depth, min_depth, max_depth)
}
AdaptationStrategy::Aggressive => self.aggressive_decision(
metrics,
hit_rate_delta,
current_depth,
min_depth,
max_depth,
),
AdaptationStrategy::Custom => {
self.balanced_decision(metrics, hit_rate_delta, current_depth, min_depth, max_depth)
}
};
let new_depth = (current_depth as i32 + depth_adjustment)
.max(min_depth as i32)
.min(max_depth as i32) as usize;
let new_aggressiveness =
(current_aggressiveness + aggressiveness_adjustment).clamp(0.0, 1.0);
if new_depth == current_depth && (new_aggressiveness - current_aggressiveness).abs() < 0.05
{
return None;
}
Some(TuningDecision {
new_depth,
new_aggressiveness,
reason,
confidence,
})
}
fn conservative_decision(
&self,
metrics: &PrefetchMetrics,
hit_rate_delta: f64,
current_depth: usize,
min_depth: usize,
max_depth: usize,
) -> (i32, f64, String, f64) {
if hit_rate_delta > 0.1 && metrics.predictability > 0.7 {
(
1,
0.05,
"High hit rate with good predictability".to_string(),
0.8,
)
} else if hit_rate_delta < -0.2 {
(
-1,
-0.1,
"Significant hit rate degradation".to_string(),
0.9,
)
} else if metrics.memory_pressure > 0.8 {
(-1, -0.05, "High memory pressure".to_string(), 0.95)
} else {
(0, 0.0, "No change needed".to_string(), 0.6)
}
}
fn balanced_decision(
&self,
metrics: &PrefetchMetrics,
hit_rate_delta: f64,
current_depth: usize,
min_depth: usize,
max_depth: usize,
) -> (i32, f64, String, f64) {
let mut depth_adj = 0i32;
let mut aggr_adj = 0.0;
let mut reasons = Vec::new();
let mut confidence: f64 = 0.5;
if hit_rate_delta > 0.05 {
depth_adj += 1;
aggr_adj += 0.1;
reasons.push("Good hit rate");
confidence += 0.1;
} else if hit_rate_delta < -0.1 {
depth_adj -= 1;
aggr_adj -= 0.1;
reasons.push("Poor hit rate");
confidence += 0.2;
}
if metrics.predictability > 0.6 {
depth_adj += 1;
reasons.push("High predictability");
confidence += 0.15;
} else if metrics.predictability < 0.3 {
depth_adj -= 1;
reasons.push("Low predictability");
confidence += 0.1;
}
if metrics.memory_pressure > 0.7 {
depth_adj -= 2;
aggr_adj -= 0.2;
reasons.push("High memory pressure");
confidence += 0.2;
}
if metrics.waste_rate > 0.3 {
depth_adj -= 1;
aggr_adj -= 0.15;
reasons.push("High waste rate");
confidence += 0.15;
}
let reason = if reasons.is_empty() {
"Maintaining current settings".to_string()
} else {
reasons.join(", ")
};
(depth_adj, aggr_adj, reason, confidence.min(1.0))
}
fn aggressive_decision(
&self,
metrics: &PrefetchMetrics,
hit_rate_delta: f64,
current_depth: usize,
min_depth: usize,
max_depth: usize,
) -> (i32, f64, String, f64) {
let (depth_adj, aggr_adj, reason, confidence) =
if metrics.predictability > 0.5 && hit_rate_delta > -0.1 {
(
2,
0.2,
"Increasing prefetch for better performance".to_string(),
0.7,
)
} else if hit_rate_delta < -0.15 || metrics.memory_pressure > 0.8 {
(
-2,
-0.2,
"Reducing prefetch due to issues".to_string(),
0.85,
)
} else {
(1, 0.1, "Exploring higher prefetch levels".to_string(), 0.6)
};
(depth_adj, aggr_adj, reason, confidence)
}
pub fn get_policy(&self) -> AdaptivePrefetchPolicy {
self.policy
.lock()
.expect("lock should not be poisoned")
.clone()
}
pub fn get_metrics_history(&self) -> Vec<(Instant, PrefetchMetrics)> {
self.metrics_history
.lock()
.expect("metrics_history lock should not be poisoned")
.iter()
.cloned()
.collect()
}
pub fn get_average_metrics(&self, window: Duration) -> Option<PrefetchMetrics> {
let history = self
.metrics_history
.lock()
.expect("lock should not be poisoned");
let cutoff = Instant::now() - window;
let recent: Vec<_> = history
.iter()
.filter(|(time, _)| *time > cutoff)
.map(|(_, metrics)| metrics)
.collect();
if recent.is_empty() {
return None;
}
let count = recent.len() as f64;
Some(PrefetchMetrics {
hit_rate: recent.iter().map(|m| m.hit_rate).sum::<f64>() / count,
miss_rate: recent.iter().map(|m| m.miss_rate).sum::<f64>() / count,
waste_rate: recent.iter().map(|m| m.waste_rate).sum::<f64>() / count,
cache_hit_latency_us: recent.iter().map(|m| m.cache_hit_latency_us).sum::<f64>()
/ count,
cache_miss_latency_us: recent.iter().map(|m| m.cache_miss_latency_us).sum::<f64>()
/ count,
memory_pressure: recent.iter().map(|m| m.memory_pressure).sum::<f64>() / count,
predictability: recent.iter().map(|m| m.predictability).sum::<f64>() / count,
})
}
pub fn reset(&self) {
self.metrics_history
.lock()
.expect("lock should not be poisoned")
.clear();
*self
.last_tuning
.lock()
.expect("lock should not be poisoned") = Instant::now();
}
pub fn generate_report(&self) -> String {
let policy = self.policy.lock().expect("lock should not be poisoned");
let avg_metrics = self
.get_average_metrics(Duration::from_secs(60))
.unwrap_or_default();
let mut report = String::new();
report.push_str("=== Adaptive Prefetch Tuning Report ===\n\n");
report.push_str("## Current Policy\n");
report.push_str(&format!(" Strategy: {:?}\n", policy.strategy));
report.push_str(&format!(" Prefetch Depth: {}\n", policy.prefetch_depth));
report.push_str(&format!(" Aggressiveness: {:.2}\n", policy.aggressiveness));
report.push_str(&format!(
" Depth Range: {} - {}\n",
policy.min_depth, policy.max_depth
));
report.push_str(&format!(
" Target Hit Rate: {:.1}%\n\n",
policy.target_hit_rate * 100.0
));
report.push_str("## Average Metrics (Last 60s)\n");
report.push_str(&format!(
" Hit Rate: {:.1}%\n",
avg_metrics.hit_rate * 100.0
));
report.push_str(&format!(
" Miss Rate: {:.1}%\n",
avg_metrics.miss_rate * 100.0
));
report.push_str(&format!(
" Waste Rate: {:.1}%\n",
avg_metrics.waste_rate * 100.0
));
report.push_str(&format!(
" Predictability: {:.1}%\n",
avg_metrics.predictability * 100.0
));
report.push_str(&format!(
" Memory Pressure: {:.1}%\n",
avg_metrics.memory_pressure * 100.0
));
report.push_str(&format!(
" Cache Hit Latency: {:.2}μs\n",
avg_metrics.cache_hit_latency_us
));
report.push_str(&format!(
" Cache Miss Latency: {:.2}μs\n",
avg_metrics.cache_miss_latency_us
));
report
}
}
#[derive(Debug, Clone)]
pub struct PidAdaptiveController {
pub kp: f64,
pub ki: f64,
pub kd: f64,
pub setpoint_hit_rate: f64,
integral: f64,
prev_error: f64,
pub integral_cap: f64,
pub min_depth: usize,
pub max_depth: usize,
current_depth: usize,
}
impl PidAdaptiveController {
pub fn new(
kp: f64,
ki: f64,
kd: f64,
setpoint_hit_rate: f64,
initial_depth: usize,
min_depth: usize,
max_depth: usize,
) -> Self {
let clamped_depth = initial_depth.clamp(min_depth, max_depth);
Self {
kp,
ki,
kd,
setpoint_hit_rate: setpoint_hit_rate.clamp(0.0, 1.0),
integral: 0.0,
prev_error: 0.0,
integral_cap: 10.0,
min_depth,
max_depth,
current_depth: clamped_depth,
}
}
pub fn tick(&mut self, current_hit_rate: f64) -> usize {
let error = self.setpoint_hit_rate - current_hit_rate.clamp(0.0, 1.0);
self.integral = (self.integral + error).clamp(-self.integral_cap, self.integral_cap);
let derivative = error - self.prev_error;
self.prev_error = error;
let output = self.kp * error + self.ki * self.integral + self.kd * derivative;
let delta = output.round() as i64;
let new_depth = (self.current_depth as i64 + delta)
.clamp(self.min_depth as i64, self.max_depth as i64) as usize;
self.current_depth = new_depth;
new_depth
}
pub fn current_depth(&self) -> usize {
self.current_depth
}
pub fn reset(&mut self) {
self.integral = 0.0;
self.prev_error = 0.0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_policy_creation() {
let policy = AdaptivePrefetchPolicy::new();
assert_eq!(policy.prefetch_depth, 4);
assert_eq!(policy.strategy, AdaptationStrategy::Balanced);
let conservative = AdaptivePrefetchPolicy::conservative();
assert_eq!(conservative.strategy, AdaptationStrategy::Conservative);
assert!(conservative.prefetch_depth < policy.prefetch_depth);
let aggressive = AdaptivePrefetchPolicy::aggressive();
assert_eq!(aggressive.strategy, AdaptationStrategy::Aggressive);
assert!(aggressive.prefetch_depth > policy.prefetch_depth);
}
#[test]
fn test_tuner_creation() {
let policy = AdaptivePrefetchPolicy::new();
let tuner = AdaptivePrefetchTuner::new(policy);
let current_policy = tuner.get_policy();
assert_eq!(current_policy.prefetch_depth, 4);
}
#[test]
fn test_metrics_update() {
let policy = AdaptivePrefetchPolicy::new();
let tuner = AdaptivePrefetchTuner::new(policy).with_interval(Duration::from_millis(100));
let metrics = PrefetchMetrics {
hit_rate: 0.9,
predictability: 0.8,
..Default::default()
};
tuner.update_metrics(metrics.clone());
let decision = tuner.update_metrics(metrics);
assert!(decision.is_none());
}
#[test]
fn test_average_metrics() {
let policy = AdaptivePrefetchPolicy::new();
let tuner = AdaptivePrefetchTuner::new(policy);
for i in 1..=5 {
let metrics = PrefetchMetrics {
hit_rate: i as f64 * 0.1,
..Default::default()
};
tuner.update_metrics(metrics);
}
let avg = tuner
.get_average_metrics(Duration::from_secs(60))
.expect("test: operation should succeed");
assert!((avg.hit_rate - 0.3).abs() < 0.01); }
#[test]
fn test_high_hit_rate_increases_depth() {
let policy = AdaptivePrefetchPolicy::new();
let tuner = AdaptivePrefetchTuner::new(policy).with_interval(Duration::from_millis(1));
std::thread::sleep(Duration::from_millis(2));
let metrics = PrefetchMetrics {
hit_rate: 0.95, predictability: 0.9,
memory_pressure: 0.1,
..Default::default()
};
let decision = tuner.update_metrics(metrics);
if let Some(d) = decision {
assert!(d.new_depth > 4, "Should increase depth on high hit rate");
}
}
#[test]
fn test_high_memory_pressure_reduces_depth() {
let policy = AdaptivePrefetchPolicy::new();
let tuner = AdaptivePrefetchTuner::new(policy).with_interval(Duration::from_millis(1));
std::thread::sleep(Duration::from_millis(2));
let metrics = PrefetchMetrics {
hit_rate: 0.5,
memory_pressure: 0.9, ..Default::default()
};
let decision = tuner.update_metrics(metrics);
if let Some(d) = decision {
assert!(
d.new_depth < 4,
"Should reduce depth on high memory pressure"
);
}
}
#[test]
fn test_reset() {
let policy = AdaptivePrefetchPolicy::new();
let tuner = AdaptivePrefetchTuner::new(policy);
tuner.update_metrics(PrefetchMetrics::default());
assert!(!tuner.get_metrics_history().is_empty());
tuner.reset();
assert!(tuner.get_metrics_history().is_empty());
}
#[test]
fn test_generate_report() {
let policy = AdaptivePrefetchPolicy::new();
let tuner = AdaptivePrefetchTuner::new(policy);
tuner.update_metrics(PrefetchMetrics {
hit_rate: 0.85,
..Default::default()
});
let report = tuner.generate_report();
assert!(report.contains("Adaptive Prefetch Tuning Report"));
assert!(report.contains("Prefetch Depth"));
assert!(report.contains("Hit Rate"));
}
#[test]
fn test_custom_policy() {
let policy = AdaptivePrefetchPolicy::custom(2, 16, 0.75);
assert_eq!(policy.min_depth, 2);
assert_eq!(policy.max_depth, 16);
assert!((policy.target_hit_rate - 0.75).abs() < 0.01);
assert_eq!(policy.strategy, AdaptationStrategy::Custom);
}
#[test]
fn test_pid_controller_step_response() {
let mut ctrl = PidAdaptiveController::new(0.5, 0.05, 0.01, 0.80, 4, 1, 32);
let initial = ctrl.current_depth();
let mut last_depth = initial;
for _ in 0..50 {
last_depth = ctrl.tick(0.50);
}
assert!(
last_depth > initial,
"depth should increase when hit rate is below setpoint (init={}, last={})",
initial,
last_depth
);
}
#[test]
fn test_pid_controller_steady_state() {
let mut ctrl = PidAdaptiveController::new(0.3, 0.0, 0.0, 0.80, 4, 1, 32);
let mut depth = 0;
for _ in 0..100 {
depth = ctrl.tick(0.80);
}
assert!(
(1_usize..=32).contains(&depth),
"depth out of bounds: {}",
depth
);
}
#[test]
fn test_pid_controller_boundaries() {
let mut ctrl = PidAdaptiveController::new(10.0, 1.0, 1.0, 0.80, 4, 1, 8);
for _ in 0..50 {
ctrl.tick(0.0);
}
assert_eq!(ctrl.current_depth(), 8, "depth should be clamped to max=8");
let mut ctrl2 = PidAdaptiveController::new(10.0, 1.0, 1.0, 0.80, 4, 2, 16);
for _ in 0..50 {
ctrl2.tick(1.0);
}
assert_eq!(ctrl2.current_depth(), 2, "depth should be clamped to min=2");
}
}