use crate::eval::{Environment, CachedEnvironment, Value};
use std::rc::Rc;
use std::time::{Duration, Instant};
#[derive(Clone)]
pub struct BenchmarkConfig {
pub chain_depth: usize,
pub variables_per_level: usize,
pub lookup_iterations: usize,
pub cache_size: usize,
}
impl Default for BenchmarkConfig {
fn default() -> Self {
Self {
chain_depth: 50,
variables_per_level: 10,
lookup_iterations: 1000,
cache_size: 256,
}
}
}
#[derive(Debug, Clone)]
pub struct BenchmarkResults {
pub traditional_time: Duration,
pub cached_time: Duration,
pub speedup_ratio: f64,
pub cache_hit_ratio: f64,
pub total_lookups: usize,
pub avg_traditional_ns: f64,
pub avg_cached_ns: f64,
}
impl BenchmarkResults {
pub fn new(
traditional_time: Duration,
cached_time: Duration,
cache_hit_ratio: f64,
total_lookups: usize
) -> Self {
let speedup_ratio = if cached_time.as_nanos() > 0 {
traditional_time.as_nanos() as f64 / cached_time.as_nanos() as f64
} else {
f64::INFINITY
};
Self {
traditional_time,
cached_time,
speedup_ratio,
cache_hit_ratio,
total_lookups,
avg_traditional_ns: traditional_time.as_nanos() as f64 / total_lookups as f64,
avg_cached_ns: cached_time.as_nanos() as f64 / total_lookups as f64,
}
}
pub fn print_results(&self) {
println!("\n=== Environment Optimization Benchmark Results ===");
println!("Total lookups: {}", self.total_lookups);
println!("Traditional Environment: {:?} ({:.2} ns/lookup)",
self.traditional_time, self.avg_traditional_ns);
println!("Cached Environment: {:?} ({:.2} ns/lookup)",
self.cached_time, self.avg_cached_ns);
println!("Speedup: {:.2}x", self.speedup_ratio);
println!("Cache hit ratio: {:.1}%", self.cache_hit_ratio);
if self.speedup_ratio >= 5.0 {
println!("✅ Excellent performance improvement!");
} else if self.speedup_ratio >= 2.0 {
println!("✅ Good performance improvement!");
} else if self.speedup_ratio >= 1.5 {
println!("⚠️ Moderate performance improvement");
} else {
println!("❌ Performance improvement below expectations");
}
}
}
pub fn create_environment_chain(config: &BenchmarkConfig) -> Rc<Environment> {
let mut env = Rc::new(Environment::new(None, 0));
for i in 0..config.variables_per_level {
env.define(format!("global_var{i}"), Value::integer(i as i64));
}
for level in 1..=config.chain_depth {
env = env.extend((level as u32).into());
for i in 0..config.variables_per_level {
env.define(
format!("var{i}_{level}"),
Value::integer((level * 100 + i) as i64)
);
}
}
env
}
pub fn generate_lookup_pattern(config: &BenchmarkConfig) -> Vec<String> {
let mut patterns = Vec::new();
for _ in 0..config.lookup_iterations {
let rand_val = (patterns.len() * 73 + 17) % 100;
let var_name = if rand_val < 70 {
let level = config.chain_depth - (rand_val % (config.chain_depth / 5));
let var_idx = rand_val % config.variables_per_level;
format!("var{var_idx}_{level}")
} else if rand_val < 90 {
let level = config.chain_depth / 2 + (rand_val % (config.chain_depth / 4));
let var_idx = rand_val % config.variables_per_level;
format!("var{var_idx}_{level}")
} else {
let var_idx = rand_val % config.variables_per_level;
format!("global_var{var_idx}")
};
patterns.push(var_name);
}
patterns
}
pub fn benchmark_traditional_environment(
env: &Rc<Environment>,
lookup_patterns: &[String]
) -> Duration {
let start = Instant::now();
for pattern in lookup_patterns {
let _value = env.lookup(pattern);
}
start.elapsed()
}
pub fn benchmark_cached_environment(
env: &Rc<Environment>,
lookup_patterns: &[String],
cache_size: usize
) -> (Duration, f64) {
let cached_env = CachedEnvironment::with_cache_size(env.clone(), cache_size);
let start = Instant::now();
for pattern in lookup_patterns {
let _value = cached_env.lookup(pattern);
}
let elapsed = start.elapsed();
let hit_ratio = cached_env.cache_hit_ratio();
(elapsed, hit_ratio)
}
pub fn run_comprehensive_benchmark(config: BenchmarkConfig) -> BenchmarkResults {
println!("Setting up environment chain (depth: {}, vars/level: {})...",
config.chain_depth, config.variables_per_level);
let env = create_environment_chain(&config);
let lookup_patterns = generate_lookup_pattern(&config);
println!("Running traditional Environment benchmark...");
let traditional_time = benchmark_traditional_environment(&env, &lookup_patterns);
println!("Running CachedEnvironment benchmark...");
let (cached_time, cache_hit_ratio) = benchmark_cached_environment(
&env, &lookup_patterns, config.cache_size
);
BenchmarkResults::new(
traditional_time,
cached_time,
cache_hit_ratio,
config.lookup_iterations
)
}
pub fn verify_correctness(config: &BenchmarkConfig) -> bool {
let env = create_environment_chain(config);
let cached_env = CachedEnvironment::new(env.clone());
let test_cases = vec![
format!("global_var0"),
format!("var0_1"),
format!("var5_{}", config.chain_depth),
format!("var{}_10", config.variables_per_level - 1),
format!("nonexistent_variable"),
];
for test_case in &test_cases {
let traditional_result = env.lookup(test_case);
let cached_result = cached_env.lookup(test_case);
if traditional_result != cached_result {
println!("❌ Correctness error for '{test_case}': traditional={traditional_result:?}, cached={cached_result:?}");
return false;
}
}
let outer = Rc::new(Environment::new(None, 0));
outer.define("x".to_string(), Value::integer(1));
let middle = outer.extend(1);
middle.define("x".to_string(), Value::integer(10));
middle.define("y".to_string(), Value::integer(2));
let inner = middle.extend(2);
inner.define("x".to_string(), Value::integer(100));
let cached_inner = CachedEnvironment::new(inner.clone());
if inner.lookup("x") != cached_inner.lookup("x") ||
inner.lookup("y") != cached_inner.lookup("y") {
println!("❌ Shadowing behavior mismatch");
return false;
}
println!("✅ Mathematical correctness verified");
true
}
pub fn run_performance_tests() {
let configs = [
BenchmarkConfig { chain_depth: 5, variables_per_level: 5, ..Default::default() },
BenchmarkConfig { chain_depth: 25, variables_per_level: 8, ..Default::default() },
BenchmarkConfig { chain_depth: 100, variables_per_level: 10, ..Default::default() },
BenchmarkConfig { chain_depth: 200, variables_per_level: 15, ..Default::default() },
];
for (i, config) in configs.iter().enumerate() {
println!("\n--- Test Case {} ---", i + 1);
if !verify_correctness(config) {
println!("❌ Correctness verification failed for test case {}", i + 1);
continue;
}
let results = run_comprehensive_benchmark(config.clone());
results.print_results();
let target_speedup = match config.chain_depth {
0..=10 => 1.5, 11..=50 => 3.0, 51..=150 => 8.0, _ => 15.0, };
if results.speedup_ratio >= target_speedup {
println!("✅ Performance target achieved ({:.1}x >= {:.1}x)",
results.speedup_ratio, target_speedup);
} else {
println!("⚠️ Performance target not met ({:.1}x < {:.1}x)",
results.speedup_ratio, target_speedup);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_environment_chain_creation() {
let config = BenchmarkConfig {
chain_depth: 10,
variables_per_level: 3,
..Default::default()
};
let env = create_environment_chain(&config);
assert!(env.lookup("global_var0").is_some());
assert!(env.lookup("var0_5").is_some());
assert!(env.lookup("var2_10").is_some());
assert!(env.lookup("nonexistent").is_none());
}
#[test]
fn test_lookup_pattern_generation() {
let config = BenchmarkConfig {
chain_depth: 10,
variables_per_level: 5,
lookup_iterations: 100,
..Default::default()
};
let patterns = generate_lookup_pattern(&config);
assert_eq!(patterns.len(), config.lookup_iterations);
let has_global = patterns.iter().any(|p| p.starts_with("global_"));
let has_numbered = patterns.iter().any(|p| p.contains("_"));
assert!(has_global);
assert!(has_numbered);
}
#[test]
fn test_benchmark_correctness() {
let config = BenchmarkConfig {
chain_depth: 5,
variables_per_level: 3,
lookup_iterations: 10,
..Default::default()
};
assert!(verify_correctness(&config));
}
#[test]
fn test_small_scale_benchmark() {
let config = BenchmarkConfig {
chain_depth: 3,
variables_per_level: 2,
lookup_iterations: 20,
cache_size: 16,
};
let results = run_comprehensive_benchmark(config);
assert!(results.speedup_ratio > 0.0);
assert!(results.cache_hit_ratio >= 0.0 && results.cache_hit_ratio <= 100.0);
assert_eq!(results.total_lookups, 20);
}
}