use std::collections::HashMap;
use std::sync::{Arc, OnceLock, RwLock};
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct PerformanceMeasurement {
pub operation_type: String,
pub operation_size: usize,
pub duration: Duration,
pub timestamp: Instant,
}
#[derive(Debug, Clone)]
pub struct AdaptiveThresholds {
pub simd_threshold: usize,
pub parallel_threshold: usize,
pub confidence: f64,
pub sample_count: usize,
}
impl Default for AdaptiveThresholds {
fn default() -> Self {
Self {
simd_threshold: 50, parallel_threshold: 1000, confidence: 0.0, sample_count: 0,
}
}
}
pub struct RuntimeProfiler {
measurements: Arc<RwLock<Vec<PerformanceMeasurement>>>,
thresholds: Arc<RwLock<AdaptiveThresholds>>,
max_measurements: usize,
min_samples_for_adaptation: usize,
}
impl Default for RuntimeProfiler {
fn default() -> Self {
Self::new()
}
}
impl RuntimeProfiler {
pub fn new() -> Self {
Self {
measurements: Arc::new(RwLock::new(Vec::new())),
thresholds: Arc::new(RwLock::new(AdaptiveThresholds::default())),
max_measurements: 10000, min_samples_for_adaptation: 50, }
}
pub fn record_measurement(&self, measurement: PerformanceMeasurement) {
if let Ok(mut measurements) = self.measurements.write() {
measurements.push(measurement);
if measurements.len() > self.max_measurements {
let len = measurements.len();
measurements.drain(0..len - self.max_measurements);
}
if measurements.len() >= self.min_samples_for_adaptation {
self.adapt_thresholds(&measurements);
}
}
}
pub fn get_thresholds(&self) -> AdaptiveThresholds {
self.thresholds
.read()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.clone()
}
fn adapt_thresholds(&self, measurements: &[PerformanceMeasurement]) {
let simd_threshold = self.find_optimal_simd_threshold(measurements);
let parallel_threshold = self.find_optimal_parallel_threshold(measurements);
if let Ok(mut thresholds) = self.thresholds.write() {
let old_simd = thresholds.simd_threshold;
let old_parallel = thresholds.parallel_threshold;
let alpha = 0.1; thresholds.simd_threshold =
((1.0 - alpha) * old_simd as f64 + alpha * simd_threshold as f64) as usize;
thresholds.parallel_threshold =
((1.0 - alpha) * old_parallel as f64 + alpha * parallel_threshold as f64) as usize;
thresholds.sample_count = measurements.len();
thresholds.confidence = self.calculate_confidence(measurements);
if old_simd != thresholds.simd_threshold
|| old_parallel != thresholds.parallel_threshold
{
println!("Adaptive thresholds updated: SIMD {} -> {}, Parallel {} -> {} (confidence: {:.2})",
old_simd, thresholds.simd_threshold,
old_parallel, thresholds.parallel_threshold,
thresholds.confidence
);
}
}
}
fn find_optimal_simd_threshold(&self, measurements: &[PerformanceMeasurement]) -> usize {
let mut simd_measurements: Vec<_> = measurements
.iter()
.filter(|m| m.operation_type.contains("simd"))
.collect();
let mut sequential_measurements: Vec<_> = measurements
.iter()
.filter(|m| m.operation_type.contains("sequential"))
.collect();
if simd_measurements.is_empty() || sequential_measurements.is_empty() {
return 50; }
simd_measurements.sort_by_key(|m| m.operation_size);
sequential_measurements.sort_by_key(|m| m.operation_size);
for size in (10..=1000).step_by(10) {
let simd_perf = self.estimate_performance_at_size(&simd_measurements, size);
let seq_perf = self.estimate_performance_at_size(&sequential_measurements, size);
if let (Some(simd_time), Some(seq_time)) = (simd_perf, seq_perf) {
if simd_time < seq_time {
return size;
}
}
}
50 }
fn find_optimal_parallel_threshold(&self, measurements: &[PerformanceMeasurement]) -> usize {
let mut parallel_measurements: Vec<_> = measurements
.iter()
.filter(|m| m.operation_type.contains("parallel"))
.collect();
let mut sequential_measurements: Vec<_> = measurements
.iter()
.filter(|m| m.operation_type.contains("sequential"))
.collect();
if parallel_measurements.is_empty() || sequential_measurements.is_empty() {
return 1000; }
parallel_measurements.sort_by_key(|m| m.operation_size);
sequential_measurements.sort_by_key(|m| m.operation_size);
for size in (100..=5000).step_by(100) {
let parallel_perf = self.estimate_performance_at_size(¶llel_measurements, size);
let seq_perf = self.estimate_performance_at_size(&sequential_measurements, size);
if let (Some(parallel_time), Some(seq_time)) = (parallel_perf, seq_perf) {
if parallel_time < seq_time {
return size;
}
}
}
1000 }
fn estimate_performance_at_size(
&self,
measurements: &[&PerformanceMeasurement],
target_size: usize,
) -> Option<Duration> {
if measurements.is_empty() {
return None;
}
let mut closest_smaller = None;
let mut closest_larger = None;
for measurement in measurements {
if measurement.operation_size <= target_size {
closest_smaller = Some(measurement);
} else if closest_larger.is_none() {
closest_larger = Some(measurement);
break;
}
}
match (closest_smaller, closest_larger) {
(Some(smaller), Some(larger)) => {
let size_diff = larger.operation_size - smaller.operation_size;
let time_diff =
larger.duration.as_nanos() as f64 - smaller.duration.as_nanos() as f64;
let target_offset = target_size - smaller.operation_size;
let interpolated_nanos = smaller.duration.as_nanos() as f64
+ (time_diff * target_offset as f64) / size_diff as f64;
Some(Duration::from_nanos(interpolated_nanos as u64))
}
(Some(measurement), None) | (None, Some(measurement)) => {
Some(measurement.duration)
}
(None, None) => None,
}
}
fn calculate_confidence(&self, measurements: &[PerformanceMeasurement]) -> f64 {
if measurements.len() < 10 {
return 0.0;
}
let recent_measurements: Vec<_> = measurements
.iter()
.rev()
.take(100) .collect();
if recent_measurements.is_empty() {
return 0.0;
}
let mut type_groups: HashMap<String, Vec<Duration>> = HashMap::new();
for measurement in recent_measurements {
type_groups
.entry(measurement.operation_type.clone())
.or_default()
.push(measurement.duration);
}
let mut total_consistency = 0.0;
let mut group_count = 0;
for (_, durations) in type_groups {
if durations.len() < 3 {
continue;
}
let mean_duration =
durations.iter().sum::<Duration>().as_nanos() as f64 / durations.len() as f64;
let variance = durations
.iter()
.map(|d| {
let diff = d.as_nanos() as f64 - mean_duration;
diff * diff
})
.sum::<f64>()
/ durations.len() as f64;
let coefficient_of_variation = if mean_duration > 0.0 {
variance.sqrt() / mean_duration
} else {
1.0
};
let consistency = 1.0 / (1.0 + coefficient_of_variation);
total_consistency += consistency;
group_count += 1;
}
if group_count > 0 {
(total_consistency / group_count as f64).min(1.0)
} else {
0.0
}
}
pub fn get_statistics(&self) -> ProfilerStatistics {
let measurements = self
.measurements
.read()
.expect("BUG: Profiler measurements lock poisoned - indicates panic during profiler read in another thread");
let thresholds = self.get_thresholds();
let total_measurements = measurements.len();
let recent_measurements = measurements.iter().rev().take(100).count();
let mut type_stats: HashMap<String, (Duration, usize)> = HashMap::new();
for measurement in measurements.iter().rev().take(1000) {
let (total_duration, count) = type_stats
.entry(measurement.operation_type.clone())
.or_insert((Duration::ZERO, 0));
*total_duration += measurement.duration;
*count += 1;
}
let average_performance: HashMap<String, Duration> = type_stats
.into_iter()
.map(|(op_type, (total_duration, count))| {
let avg_duration = if count > 0 {
Duration::from_nanos((total_duration.as_nanos() / count as u128) as u64)
} else {
Duration::ZERO
};
(op_type, avg_duration)
})
.collect();
ProfilerStatistics {
total_measurements,
recent_measurements,
current_thresholds: thresholds,
average_performance,
}
}
}
#[derive(Debug, Clone)]
pub struct ProfilerStatistics {
pub total_measurements: usize,
pub recent_measurements: usize,
pub current_thresholds: AdaptiveThresholds,
pub average_performance: HashMap<String, Duration>,
}
static GLOBAL_PROFILER: OnceLock<RuntimeProfiler> = OnceLock::new();
pub fn get_global_profiler() -> &'static RuntimeProfiler {
GLOBAL_PROFILER.get_or_init(RuntimeProfiler::new)
}
pub fn record_performance(operation_type: &str, operation_size: usize, duration: Duration) {
let measurement = PerformanceMeasurement {
operation_type: operation_type.to_owned(),
operation_size,
duration,
timestamp: Instant::now(),
};
get_global_profiler().record_measurement(measurement);
}
pub fn get_adaptive_thresholds() -> AdaptiveThresholds {
get_global_profiler().get_thresholds()
}
pub fn get_profiler_statistics() -> ProfilerStatistics {
get_global_profiler().get_statistics()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_runtime_profiler_creation() {
let profiler = RuntimeProfiler::new();
let thresholds = profiler.get_thresholds();
assert_eq!(thresholds.simd_threshold, 50);
assert_eq!(thresholds.parallel_threshold, 1000);
assert_eq!(thresholds.confidence, 0.0);
assert_eq!(thresholds.sample_count, 0);
}
#[test]
fn test_measurement_recording() {
let profiler = RuntimeProfiler::new();
let measurement = PerformanceMeasurement {
operation_type: "test_operation".to_string(),
operation_size: 100,
duration: Duration::from_millis(10),
timestamp: Instant::now(),
};
profiler.record_measurement(measurement);
let thresholds = profiler.get_thresholds();
assert_eq!(thresholds.sample_count, 0); }
#[test]
fn test_global_profiler() {
record_performance("test_simd", 100, Duration::from_micros(50));
record_performance("test_sequential", 100, Duration::from_micros(100));
let stats = get_profiler_statistics();
assert!(stats.total_measurements >= 2);
let thresholds = get_adaptive_thresholds();
assert!(thresholds.simd_threshold > 0);
assert!(thresholds.parallel_threshold > 0);
}
}