use crate::error::Result;
use crate::memory_bandwidth_optimization::OptimizedStateVector;
use scirs2_core::parallel_ops::{IndexedParallelIterator, ParallelIterator};
use std::collections::{BTreeMap, HashMap, VecDeque};
use std::sync::{Arc, Mutex, RwLock};
use std::thread;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PrefetchStrategy {
None,
Sequential,
Stride,
Pattern,
MLGuided,
Adaptive,
NUMAAware,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LocalityStrategy {
Temporal,
Spatial,
Loop,
CacheConscious,
NUMATopology,
Hybrid,
}
#[derive(Debug, Clone)]
pub struct NUMATopology {
pub num_nodes: usize,
pub memory_per_node: Vec<usize>,
pub cores_per_node: Vec<usize>,
pub latency_matrix: Vec<Vec<usize>>,
pub bandwidth_per_node: Vec<f64>,
pub thread_node_mapping: HashMap<usize, usize>,
}
impl Default for NUMATopology {
fn default() -> Self {
Self {
num_nodes: 1,
memory_per_node: vec![64 * 1024 * 1024 * 1024],
cores_per_node: vec![8],
latency_matrix: vec![vec![0]],
bandwidth_per_node: vec![100.0 * 1024.0 * 1024.0 * 1024.0],
thread_node_mapping: HashMap::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct PrefetchConfig {
pub strategy: PrefetchStrategy,
pub distance: usize,
pub degree: usize,
pub hardware_hints: bool,
pub threshold: f64,
pub max_queue_size: usize,
pub cross_page_prefetch: bool,
pub adaptive_adjustment: bool,
}
impl Default for PrefetchConfig {
fn default() -> Self {
Self {
strategy: PrefetchStrategy::Adaptive,
distance: 8,
degree: 4,
hardware_hints: true,
threshold: 0.7,
max_queue_size: 64,
cross_page_prefetch: true,
adaptive_adjustment: true,
}
}
}
#[derive(Debug)]
pub struct AccessPatternPredictor {
access_history: VecDeque<usize>,
stride_patterns: HashMap<isize, u64>,
pattern_confidence: HashMap<String, f64>,
ml_weights: Vec<f64>,
prediction_cache: HashMap<usize, Vec<usize>>,
correct_predictions: u64,
total_predictions: u64,
}
impl Default for AccessPatternPredictor {
fn default() -> Self {
Self {
access_history: VecDeque::with_capacity(1000),
stride_patterns: HashMap::new(),
pattern_confidence: HashMap::new(),
ml_weights: vec![0.5; 16],
prediction_cache: HashMap::new(),
correct_predictions: 0,
total_predictions: 0,
}
}
}
impl AccessPatternPredictor {
pub fn record_access(&mut self, address: usize) {
self.access_history.push_back(address);
if self.access_history.len() > 1000 {
self.access_history.pop_front();
}
if self.access_history.len() >= 2 {
let prev_addr = self.access_history[self.access_history.len() - 2];
let stride = address as isize - prev_addr as isize;
*self.stride_patterns.entry(stride).or_insert(0) += 1;
}
self.update_pattern_confidence();
}
pub fn predict_next_accesses(&mut self, count: usize) -> Vec<usize> {
if self.access_history.is_empty() {
return Vec::new();
}
let current_addr = *self
.access_history
.back()
.expect("access_history is not empty (checked above)");
if let Some(cached) = self.prediction_cache.get(¤t_addr) {
return cached.clone();
}
let predictions = match self.get_dominant_pattern() {
PredictedPattern::Stride(stride) => {
Self::predict_stride_pattern(current_addr, stride, count)
}
PredictedPattern::Sequential => Self::predict_sequential_pattern(current_addr, count),
PredictedPattern::Random => Self::predict_random_pattern(current_addr, count),
PredictedPattern::MLGuided => self.predict_ml_pattern(current_addr, count),
};
self.prediction_cache
.insert(current_addr, predictions.clone());
if self.prediction_cache.len() > 1000 {
self.prediction_cache.clear();
}
self.total_predictions += 1;
predictions
}
fn update_pattern_confidence(&mut self) {
if self.total_predictions > 0 {
let accuracy = self.correct_predictions as f64 / self.total_predictions as f64;
self.pattern_confidence
.insert("stride".to_string(), accuracy);
self.pattern_confidence
.insert("sequential".to_string(), accuracy * 0.9);
self.pattern_confidence
.insert("ml".to_string(), accuracy * 1.1);
}
}
fn get_dominant_pattern(&self) -> PredictedPattern {
let dominant_stride = self
.stride_patterns
.iter()
.max_by_key(|(_, &count)| count)
.map(|(&stride, _)| stride);
match dominant_stride {
Some(stride) if stride == 1 => PredictedPattern::Sequential,
Some(stride) if stride != 0 => PredictedPattern::Stride(stride),
_ => {
let ml_confidence = self.pattern_confidence.get("ml").unwrap_or(&0.0);
if *ml_confidence > 0.8 {
PredictedPattern::MLGuided
} else {
PredictedPattern::Random
}
}
}
}
fn predict_stride_pattern(current_addr: usize, stride: isize, count: usize) -> Vec<usize> {
let mut predictions = Vec::with_capacity(count);
let mut addr = current_addr;
for _ in 0..count {
addr = (addr as isize + stride) as usize;
predictions.push(addr);
}
predictions
}
fn predict_sequential_pattern(current_addr: usize, count: usize) -> Vec<usize> {
(1..=count).map(|i| current_addr + i).collect()
}
fn predict_random_pattern(current_addr: usize, count: usize) -> Vec<usize> {
(1..=count).map(|i| current_addr + i * 64).collect()
}
fn predict_ml_pattern(&self, current_addr: usize, count: usize) -> Vec<usize> {
let mut predictions = Vec::with_capacity(count);
let features = self.extract_features();
for i in 0..count {
let prediction = self.ml_predict(&features, i);
predictions.push((current_addr as f64 + prediction) as usize);
}
predictions
}
fn extract_features(&self) -> Vec<f64> {
let mut features = [0.0; 16];
if self.access_history.len() >= 4 {
let recent: Vec<_> = self.access_history.iter().rev().take(4).collect();
for i in 0..3 {
if i + 1 < recent.len() {
let stride = *recent[i] as f64 - *recent[i + 1] as f64;
features[i] = stride / 1000.0;
}
}
features[3] = (*recent[0] % 1024) as f64 / 1024.0;
features[4] = (*recent[0] / 1024) as f64;
let dominant_stride = self
.stride_patterns
.iter()
.max_by_key(|(_, &count)| count)
.map_or(0, |(&stride, _)| stride);
features[5] = dominant_stride as f64 / 1000.0;
}
features.to_vec()
}
fn ml_predict(&self, features: &[f64], step: usize) -> f64 {
let mut prediction = 0.0;
for (i, &feature) in features.iter().enumerate() {
if i < self.ml_weights.len() {
prediction += feature * self.ml_weights[i];
}
}
prediction * (step + 1) as f64
}
pub fn update_ml_weights(&mut self, predictions: &[usize], actual: &[usize]) {
if predictions.len() != actual.len() || predictions.is_empty() {
return;
}
let learning_rate = 0.01;
for (pred, &act) in predictions.iter().zip(actual.iter()) {
let error = act as f64 - *pred as f64;
for weight in &mut self.ml_weights {
*weight += learning_rate * error * 0.1;
}
}
}
#[must_use]
pub fn get_accuracy(&self) -> f64 {
if self.total_predictions > 0 {
self.correct_predictions as f64 / self.total_predictions as f64
} else {
0.0
}
}
}
#[derive(Debug, Clone)]
enum PredictedPattern {
Stride(isize),
Sequential,
Random,
MLGuided,
}
#[derive(Debug)]
pub struct MemoryPrefetcher {
config: PrefetchConfig,
predictor: Arc<Mutex<AccessPatternPredictor>>,
prefetch_queue: Arc<Mutex<VecDeque<PrefetchRequest>>>,
numa_topology: NUMATopology,
stats: Arc<RwLock<PrefetchStats>>,
prefetch_threads: Vec<thread::JoinHandle<()>>,
}
#[derive(Debug, Clone)]
pub struct PrefetchRequest {
pub address: usize,
pub priority: f64,
pub hint_type: PrefetchHint,
pub timestamp: Instant,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PrefetchHint {
Temporal,
NonTemporal,
L1,
L2,
L3,
Write,
}
#[derive(Debug, Clone, Default)]
pub struct PrefetchStats {
pub total_requests: u64,
pub successful_prefetches: u64,
pub failed_prefetches: u64,
pub average_latency: Duration,
pub bandwidth_utilization: f64,
pub cache_hit_improvement: f64,
}
impl MemoryPrefetcher {
pub fn new(config: PrefetchConfig, numa_topology: NUMATopology) -> Result<Self> {
let prefetcher = Self {
config,
predictor: Arc::new(Mutex::new(AccessPatternPredictor::default())),
prefetch_queue: Arc::new(Mutex::new(VecDeque::new())),
numa_topology,
stats: Arc::new(RwLock::new(PrefetchStats::default())),
prefetch_threads: Vec::new(),
};
Ok(prefetcher)
}
pub fn start_prefetch_threads(&mut self) -> Result<()> {
let num_threads = self.config.degree.min(4);
for thread_id in 0..num_threads {
let queue = Arc::clone(&self.prefetch_queue);
let stats = Arc::clone(&self.stats);
let config = self.config.clone();
let handle = thread::spawn(move || {
Self::prefetch_worker_thread(thread_id, queue, stats, config);
});
self.prefetch_threads.push(handle);
}
Ok(())
}
fn prefetch_worker_thread(
_thread_id: usize,
queue: Arc<Mutex<VecDeque<PrefetchRequest>>>,
stats: Arc<RwLock<PrefetchStats>>,
_config: PrefetchConfig,
) {
loop {
let request = {
let mut q = queue
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
q.pop_front()
};
if let Some(req) = request {
let start_time = Instant::now();
Self::execute_prefetch(&req);
let latency = start_time.elapsed();
if let Ok(mut s) = stats.write() {
s.total_requests += 1;
s.average_latency = if s.total_requests == 1 {
latency
} else {
Duration::from_nanos(u128::midpoint(
s.average_latency.as_nanos(),
latency.as_nanos(),
) as u64)
};
}
} else {
thread::sleep(Duration::from_micros(100));
}
}
}
fn execute_prefetch(request: &PrefetchRequest) {
unsafe {
match request.hint_type {
PrefetchHint::Temporal
| PrefetchHint::L1
| PrefetchHint::L2
| PrefetchHint::L3
| PrefetchHint::NonTemporal
| PrefetchHint::Write => {
let _ = std::ptr::read_volatile(request.address as *const u8);
}
}
}
}
pub fn record_access(&self, address: usize) -> Result<()> {
if let Ok(mut predictor) = self.predictor.lock() {
predictor.record_access(address);
let predictions = predictor.predict_next_accesses(self.config.distance);
if let Ok(mut queue) = self.prefetch_queue.lock() {
for (i, &pred_addr) in predictions.iter().enumerate() {
if queue.len() < self.config.max_queue_size {
let priority = 1.0 - (i as f64 / predictions.len() as f64);
let hint_type = Self::determine_prefetch_hint(pred_addr, i);
queue.push_back(PrefetchRequest {
address: pred_addr,
priority,
hint_type,
timestamp: Instant::now(),
});
}
}
}
}
Ok(())
}
const fn determine_prefetch_hint(_address: usize, distance: usize) -> PrefetchHint {
match distance {
0..=2 => PrefetchHint::L1,
3..=6 => PrefetchHint::L2,
7..=12 => PrefetchHint::L3,
_ => PrefetchHint::NonTemporal,
}
}
#[must_use]
pub fn get_stats(&self) -> PrefetchStats {
self.stats
.read()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.clone()
}
pub fn optimize_strategy(&mut self, performance_feedback: &PerformanceFeedback) -> Result<()> {
if !self.config.adaptive_adjustment {
return Ok(());
}
if performance_feedback.cache_hit_rate < 0.8 {
self.config.distance = (self.config.distance + 2).min(16);
} else if performance_feedback.cache_hit_rate > 0.95 {
self.config.distance = (self.config.distance.saturating_sub(1)).max(2);
}
if performance_feedback.bandwidth_utilization < 0.6 {
self.config.degree = (self.config.degree + 1).min(8);
} else if performance_feedback.bandwidth_utilization > 0.9 {
self.config.degree = (self.config.degree.saturating_sub(1)).max(1);
}
if self.config.strategy == PrefetchStrategy::MLGuided {
if let Ok(mut predictor) = self.predictor.lock() {
let accuracy_improvement = performance_feedback.cache_hit_rate - 0.8;
predictor
.ml_weights
.iter_mut()
.for_each(|w| *w += accuracy_improvement * 0.01);
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct PerformanceFeedback {
pub cache_hit_rate: f64,
pub bandwidth_utilization: f64,
pub memory_latency: Duration,
pub cpu_utilization: f64,
}
#[derive(Debug)]
pub struct DataLocalityOptimizer {
strategy: LocalityStrategy,
numa_topology: NUMATopology,
memory_regions: HashMap<usize, MemoryRegionInfo>,
access_analyzer: AccessPatternAnalyzer,
}
#[derive(Debug, Clone)]
pub struct MemoryRegionInfo {
pub start_address: usize,
pub size: usize,
pub numa_node: usize,
pub access_frequency: u64,
pub last_access: Instant,
pub access_pattern: AccessPatternType,
}
#[derive(Debug)]
pub struct AccessPatternAnalyzer {
temporal_patterns: BTreeMap<Instant, Vec<usize>>,
spatial_patterns: HashMap<usize, Vec<usize>>,
loop_detection: LoopDetectionState,
}
#[derive(Debug)]
pub struct LoopDetectionState {
loop_starts: HashMap<usize, usize>,
current_iteration: Vec<usize>,
detected_loops: Vec<LoopPattern>,
}
#[derive(Debug, Clone)]
pub struct LoopPattern {
pub start_address: usize,
pub stride: isize,
pub iterations: usize,
pub confidence: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccessPatternType {
Sequential,
Random,
Strided,
Loop,
Temporal,
Hybrid,
}
impl DataLocalityOptimizer {
#[must_use]
pub fn new(strategy: LocalityStrategy, numa_topology: NUMATopology) -> Self {
Self {
strategy,
numa_topology,
memory_regions: HashMap::new(),
access_analyzer: AccessPatternAnalyzer {
temporal_patterns: BTreeMap::new(),
spatial_patterns: HashMap::new(),
loop_detection: LoopDetectionState {
loop_starts: HashMap::new(),
current_iteration: Vec::new(),
detected_loops: Vec::new(),
},
},
}
}
pub fn optimize_data_placement(
&mut self,
state_vector: &mut OptimizedStateVector,
access_pattern: &[usize],
) -> Result<LocalityOptimizationResult> {
let start_time = Instant::now();
self.analyze_access_patterns(access_pattern)?;
let optimization_result = match self.strategy {
LocalityStrategy::Temporal => {
Self::optimize_temporal_locality(state_vector, access_pattern)?
}
LocalityStrategy::Spatial => {
Self::optimize_spatial_locality(state_vector, access_pattern)?
}
LocalityStrategy::Loop => self.optimize_loop_locality(state_vector, access_pattern)?,
LocalityStrategy::CacheConscious => {
Self::optimize_cache_conscious(state_vector, access_pattern)?
}
LocalityStrategy::NUMATopology => {
self.optimize_numa_topology(state_vector, access_pattern)?
}
LocalityStrategy::Hybrid => {
self.optimize_hybrid_locality(state_vector, access_pattern)?
}
};
let optimization_time = start_time.elapsed();
Ok(LocalityOptimizationResult {
optimization_time,
locality_improvement: optimization_result.locality_improvement,
memory_movements: optimization_result.memory_movements,
numa_migrations: optimization_result.numa_migrations,
cache_efficiency_gain: optimization_result.cache_efficiency_gain,
strategy_used: self.strategy,
})
}
fn analyze_access_patterns(&mut self, access_pattern: &[usize]) -> Result<()> {
let now = Instant::now();
self.access_analyzer
.temporal_patterns
.insert(now, access_pattern.to_vec());
for &address in access_pattern {
let page = address / 4096;
self.access_analyzer
.spatial_patterns
.entry(page)
.or_default()
.push(address);
}
self.detect_loop_patterns(access_pattern)?;
while self.access_analyzer.temporal_patterns.len() > 1000 {
self.access_analyzer.temporal_patterns.pop_first();
}
Ok(())
}
fn detect_loop_patterns(&mut self, access_pattern: &[usize]) -> Result<()> {
if access_pattern.len() < 3 {
return Ok(());
}
for window in access_pattern.windows(3) {
if let [start, middle, end] = window {
let stride1 = *middle as isize - *start as isize;
let stride2 = *end as isize - *middle as isize;
if stride1 == stride2 && stride1 != 0 {
*self
.access_analyzer
.loop_detection
.loop_starts
.entry(*start)
.or_insert(0) += 1;
if self.access_analyzer.loop_detection.loop_starts[start] >= 3 {
let confidence =
self.access_analyzer.loop_detection.loop_starts[start] as f64 / 10.0;
let confidence = confidence.min(1.0);
self.access_analyzer
.loop_detection
.detected_loops
.push(LoopPattern {
start_address: *start,
stride: stride1,
iterations: self.access_analyzer.loop_detection.loop_starts[start],
confidence,
});
}
}
}
}
Ok(())
}
fn optimize_temporal_locality(
_state_vector: &mut OptimizedStateVector,
access_pattern: &[usize],
) -> Result<OptimizationResult> {
let mut reuse_distances = HashMap::new();
let mut last_access = HashMap::new();
for (i, &address) in access_pattern.iter().enumerate() {
if let Some(&last_pos) = last_access.get(&address) {
let reuse_distance = i - last_pos;
reuse_distances.insert(address, reuse_distance);
}
last_access.insert(address, i);
}
let avg_reuse_distance: f64 = reuse_distances.values().map(|&d| d as f64).sum::<f64>()
/ reuse_distances.len().max(1) as f64;
let locality_improvement = (100.0 / (avg_reuse_distance + 1.0)).min(1.0);
Ok(OptimizationResult {
locality_improvement,
memory_movements: 0,
numa_migrations: 0,
cache_efficiency_gain: locality_improvement * 0.5,
})
}
fn optimize_spatial_locality(
_state_vector: &mut OptimizedStateVector,
access_pattern: &[usize],
) -> Result<OptimizationResult> {
let mut spatial_clusters = HashMap::new();
for &address in access_pattern {
let cache_line = address / 64;
*spatial_clusters.entry(cache_line).or_insert(0) += 1;
}
let total_accesses = access_pattern.len();
let unique_cache_lines = spatial_clusters.len();
let spatial_efficiency = if unique_cache_lines > 0 {
total_accesses as f64 / unique_cache_lines as f64
} else {
1.0
};
let locality_improvement = (spatial_efficiency / 10.0).min(1.0);
Ok(OptimizationResult {
locality_improvement,
memory_movements: spatial_clusters.len(),
numa_migrations: 0,
cache_efficiency_gain: locality_improvement * 0.7,
})
}
fn optimize_loop_locality(
&self,
_state_vector: &mut OptimizedStateVector,
_access_pattern: &[usize],
) -> Result<OptimizationResult> {
let total_loops = self.access_analyzer.loop_detection.detected_loops.len();
let high_confidence_loops = self
.access_analyzer
.loop_detection
.detected_loops
.iter()
.filter(|loop_pattern| loop_pattern.confidence > 0.8)
.count();
let loop_efficiency = if total_loops > 0 {
high_confidence_loops as f64 / total_loops as f64
} else {
0.5
};
Ok(OptimizationResult {
locality_improvement: loop_efficiency,
memory_movements: total_loops,
numa_migrations: 0,
cache_efficiency_gain: loop_efficiency * 0.8,
})
}
fn optimize_cache_conscious(
_state_vector: &mut OptimizedStateVector,
access_pattern: &[usize],
) -> Result<OptimizationResult> {
let cache_size = 256 * 1024;
let cache_line_size = 64;
let cache_lines = cache_size / cache_line_size;
let mut cache_hits = 0;
let mut cache_misses = 0;
let mut cache_state = HashMap::new();
for &address in access_pattern {
let cache_line = address / cache_line_size;
let cache_set = cache_line % cache_lines;
if let std::collections::hash_map::Entry::Vacant(e) = cache_state.entry(cache_set) {
cache_misses += 1;
e.insert(cache_line);
} else {
cache_hits += 1;
}
}
let cache_hit_rate = if cache_hits + cache_misses > 0 {
cache_hits as f64 / (cache_hits + cache_misses) as f64
} else {
0.0
};
Ok(OptimizationResult {
locality_improvement: cache_hit_rate,
memory_movements: cache_misses,
numa_migrations: 0,
cache_efficiency_gain: cache_hit_rate,
})
}
fn optimize_numa_topology(
&self,
_state_vector: &mut OptimizedStateVector,
access_pattern: &[usize],
) -> Result<OptimizationResult> {
let mut numa_accesses = HashMap::new();
for &address in access_pattern {
let numa_node = (address / (1024 * 1024 * 1024)) % self.numa_topology.num_nodes;
*numa_accesses.entry(numa_node).or_insert(0) += 1;
}
let dominant_node = numa_accesses.iter().max_by_key(|(_, &count)| count);
let numa_efficiency = if let Some((_, &dominant_count)) = dominant_node {
f64::from(dominant_count) / access_pattern.len() as f64
} else {
0.0
};
let numa_migrations = numa_accesses.len().saturating_sub(1);
Ok(OptimizationResult {
locality_improvement: numa_efficiency,
memory_movements: 0,
numa_migrations,
cache_efficiency_gain: numa_efficiency * 0.6,
})
}
fn optimize_hybrid_locality(
&self,
state_vector: &mut OptimizedStateVector,
access_pattern: &[usize],
) -> Result<OptimizationResult> {
let temporal = Self::optimize_temporal_locality(state_vector, access_pattern)?;
let spatial = Self::optimize_spatial_locality(state_vector, access_pattern)?;
let numa = self.optimize_numa_topology(state_vector, access_pattern)?;
let locality_improvement = numa.locality_improvement.mul_add(
0.2,
temporal
.locality_improvement
.mul_add(0.4, spatial.locality_improvement * 0.4),
);
Ok(OptimizationResult {
locality_improvement,
memory_movements: temporal.memory_movements + spatial.memory_movements,
numa_migrations: numa.numa_migrations,
cache_efficiency_gain: temporal
.cache_efficiency_gain
.max(spatial.cache_efficiency_gain),
})
}
#[must_use]
pub fn get_detected_loops(&self) -> &[LoopPattern] {
&self.access_analyzer.loop_detection.detected_loops
}
}
#[derive(Debug, Clone)]
pub struct OptimizationResult {
pub locality_improvement: f64,
pub memory_movements: usize,
pub numa_migrations: usize,
pub cache_efficiency_gain: f64,
}
#[derive(Debug, Clone)]
pub struct LocalityOptimizationResult {
pub optimization_time: Duration,
pub locality_improvement: f64,
pub memory_movements: usize,
pub numa_migrations: usize,
pub cache_efficiency_gain: f64,
pub strategy_used: LocalityStrategy,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::memory_bandwidth_optimization::{MemoryOptimizationConfig, OptimizedStateVector};
#[test]
fn test_access_pattern_predictor() {
let mut predictor = AccessPatternPredictor::default();
for i in 0..10 {
predictor.record_access(i * 64);
}
let predictions = predictor.predict_next_accesses(5);
assert_eq!(predictions.len(), 5);
for (i, &pred) in predictions.iter().enumerate() {
assert_eq!(pred, (10 + i) * 64);
}
}
#[test]
fn test_memory_prefetcher_creation() {
let config = PrefetchConfig::default();
let numa = NUMATopology::default();
let prefetcher = MemoryPrefetcher::new(config, numa)
.expect("MemoryPrefetcher creation should succeed with default config");
assert_eq!(prefetcher.config.strategy, PrefetchStrategy::Adaptive);
}
#[test]
fn test_prefetch_request() {
let request = PrefetchRequest {
address: 0x1000,
priority: 0.8,
hint_type: PrefetchHint::L1,
timestamp: Instant::now(),
};
assert_eq!(request.address, 0x1000);
assert_eq!(request.priority, 0.8);
assert_eq!(request.hint_type, PrefetchHint::L1);
}
#[test]
fn test_data_locality_optimizer() {
let numa = NUMATopology::default();
let optimizer = DataLocalityOptimizer::new(LocalityStrategy::Spatial, numa);
assert!(matches!(optimizer.strategy, LocalityStrategy::Spatial));
}
#[test]
fn test_loop_pattern_detection() {
let mut optimizer =
DataLocalityOptimizer::new(LocalityStrategy::Loop, NUMATopology::default());
let access_pattern = vec![100, 200, 300, 400, 500, 600];
optimizer
.detect_loop_patterns(&access_pattern)
.expect("loop pattern detection should succeed");
assert!(!optimizer
.access_analyzer
.loop_detection
.loop_starts
.is_empty());
}
#[test]
fn test_spatial_locality_optimization() {
let numa = NUMATopology::default();
let optimizer = DataLocalityOptimizer::new(LocalityStrategy::Spatial, numa);
let access_pattern = vec![0, 8, 16, 24, 32, 40];
let config = MemoryOptimizationConfig::default();
let mut state_vector = OptimizedStateVector::new(3, config)
.expect("OptimizedStateVector creation should succeed");
let result =
DataLocalityOptimizer::optimize_spatial_locality(&mut state_vector, &access_pattern)
.expect("spatial locality optimization should succeed");
assert!(result.locality_improvement > 0.0);
assert!(result.cache_efficiency_gain >= 0.0);
}
#[test]
fn test_numa_topology_default() {
let numa = NUMATopology::default();
assert_eq!(numa.num_nodes, 1);
assert_eq!(numa.cores_per_node.len(), 1);
assert_eq!(numa.memory_per_node.len(), 1);
}
#[test]
fn test_prefetch_hint_determination() {
let config = PrefetchConfig::default();
let numa = NUMATopology::default();
let _prefetcher = MemoryPrefetcher::new(config, numa)
.expect("MemoryPrefetcher creation should succeed for prefetch hint test");
assert_eq!(
MemoryPrefetcher::determine_prefetch_hint(0x1000, 0),
PrefetchHint::L1
);
assert_eq!(
MemoryPrefetcher::determine_prefetch_hint(0x1000, 5),
PrefetchHint::L2
);
assert_eq!(
MemoryPrefetcher::determine_prefetch_hint(0x1000, 10),
PrefetchHint::L3
);
assert_eq!(
MemoryPrefetcher::determine_prefetch_hint(0x1000, 15),
PrefetchHint::NonTemporal
);
}
#[test]
fn test_ml_prediction() {
let mut predictor = AccessPatternPredictor::default();
for i in 0..20 {
predictor.record_access(i * 8);
}
let features = predictor.extract_features();
assert_eq!(features.len(), 16);
let prediction = predictor.ml_predict(&features, 0);
assert!(prediction.is_finite());
}
#[test]
fn test_performance_feedback() {
let feedback = PerformanceFeedback {
cache_hit_rate: 0.85,
bandwidth_utilization: 0.7,
memory_latency: Duration::from_nanos(100),
cpu_utilization: 0.6,
};
assert_eq!(feedback.cache_hit_rate, 0.85);
assert_eq!(feedback.bandwidth_utilization, 0.7);
}
}