Skip to main content

sklears_compose/
benchmarking.rs

1//! Benchmarking utilities for pipeline composition performance analysis
2//!
3//! This module provides comprehensive benchmarking frameworks for measuring
4//! and comparing the performance of different composition strategies.
5
6use scirs2_core::ndarray::{Array2, ArrayView2};
7use scirs2_core::random::{Rng, RngExt};
8use serde::{Deserialize, Serialize};
9use sklears_core::{
10    error::Result as SklResult,
11    traits::{Estimator, Transform},
12};
13use std::collections::{BTreeMap, HashMap};
14use std::fmt::Write as FmtWrite;
15use std::time::{Duration, Instant};
16
17/// Advanced benchmark configuration
18#[derive(Debug, Clone)]
19pub struct BenchmarkConfig {
20    /// Number of iterations for each benchmark
21    pub iterations: usize,
22    /// Warmup iterations before timing
23    pub warmup_iterations: usize,
24    /// Sample sizes to test
25    pub sample_sizes: Vec<usize>,
26    /// Feature counts to test
27    pub feature_counts: Vec<usize>,
28    /// Whether to include memory usage measurements
29    pub measure_memory: bool,
30    /// Whether to include throughput measurements
31    pub measure_throughput: bool,
32    /// Enable CPU profiling
33    pub enable_cpu_profiling: bool,
34    /// Enable cache miss analysis
35    pub enable_cache_analysis: bool,
36    /// Enable parallel execution profiling
37    pub enable_parallel_profiling: bool,
38    /// Maximum benchmark duration per test
39    pub max_duration: Duration,
40    /// Statistical confidence level (0.0 to 1.0)
41    pub confidence_level: f64,
42    /// Enable convergence detection
43    pub enable_convergence_detection: bool,
44    /// Custom benchmark tags for categorization
45    pub tags: HashMap<String, String>,
46    /// Outlier detection threshold (number of standard deviations)
47    pub outlier_threshold: f64,
48}
49
50impl BenchmarkConfig {
51    /// Create a new benchmark configuration
52    #[must_use]
53    pub fn new() -> Self {
54        Self {
55            iterations: 100,
56            warmup_iterations: 10,
57            sample_sizes: vec![100, 1000, 10000],
58            feature_counts: vec![5, 10, 20, 50],
59            measure_memory: true,
60            measure_throughput: true,
61            enable_cpu_profiling: true,
62            enable_cache_analysis: false, // Requires special hardware support
63            enable_parallel_profiling: true,
64            max_duration: Duration::from_secs(300), // 5 minutes max
65            confidence_level: 0.95,
66            enable_convergence_detection: true,
67            tags: HashMap::new(),
68            outlier_threshold: 2.0, // 2 standard deviations
69        }
70    }
71
72    /// Set number of iterations
73    #[must_use]
74    pub fn iterations(mut self, iterations: usize) -> Self {
75        self.iterations = iterations;
76        self
77    }
78
79    /// Set warmup iterations
80    #[must_use]
81    pub fn warmup_iterations(mut self, warmup: usize) -> Self {
82        self.warmup_iterations = warmup;
83        self
84    }
85
86    /// Set sample sizes to test
87    #[must_use]
88    pub fn sample_sizes(mut self, sizes: Vec<usize>) -> Self {
89        self.sample_sizes = sizes;
90        self
91    }
92
93    /// Set feature counts to test
94    #[must_use]
95    pub fn feature_counts(mut self, counts: Vec<usize>) -> Self {
96        self.feature_counts = counts;
97        self
98    }
99
100    /// Enable/disable memory measurements
101    #[must_use]
102    pub fn measure_memory(mut self, enable: bool) -> Self {
103        self.measure_memory = enable;
104        self
105    }
106
107    /// Enable/disable throughput measurements
108    #[must_use]
109    pub fn measure_throughput(mut self, enable: bool) -> Self {
110        self.measure_throughput = enable;
111        self
112    }
113
114    /// Enable/disable CPU profiling
115    #[must_use]
116    pub fn enable_cpu_profiling(mut self, enable: bool) -> Self {
117        self.enable_cpu_profiling = enable;
118        self
119    }
120
121    /// Enable/disable cache analysis
122    #[must_use]
123    pub fn enable_cache_analysis(mut self, enable: bool) -> Self {
124        self.enable_cache_analysis = enable;
125        self
126    }
127
128    /// Enable/disable parallel profiling
129    #[must_use]
130    pub fn enable_parallel_profiling(mut self, enable: bool) -> Self {
131        self.enable_parallel_profiling = enable;
132        self
133    }
134
135    /// Set maximum benchmark duration
136    #[must_use]
137    pub fn max_duration(mut self, duration: Duration) -> Self {
138        self.max_duration = duration;
139        self
140    }
141
142    /// Set statistical confidence level
143    #[must_use]
144    pub fn confidence_level(mut self, level: f64) -> Self {
145        self.confidence_level = level.clamp(0.0, 1.0);
146        self
147    }
148
149    /// Enable/disable convergence detection
150    #[must_use]
151    pub fn enable_convergence_detection(mut self, enable: bool) -> Self {
152        self.enable_convergence_detection = enable;
153        self
154    }
155
156    /// Add custom tags
157    #[must_use]
158    pub fn add_tag(mut self, key: String, value: String) -> Self {
159        self.tags.insert(key, value);
160        self
161    }
162
163    /// Set outlier detection threshold
164    #[must_use]
165    pub fn outlier_threshold(mut self, threshold: f64) -> Self {
166        self.outlier_threshold = threshold;
167        self
168    }
169}
170
171impl Default for BenchmarkConfig {
172    fn default() -> Self {
173        Self::new()
174    }
175}
176
177/// Enhanced benchmark result with comprehensive metrics
178#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct BenchmarkResult {
180    /// Name of the benchmark
181    pub name: String,
182    /// Mean execution time
183    pub mean_time: Duration,
184    /// Standard deviation of execution time
185    pub std_dev_time: Duration,
186    /// Minimum execution time
187    pub min_time: Duration,
188    /// Maximum execution time
189    pub max_time: Duration,
190    /// Median execution time
191    pub median_time: Duration,
192    /// 95th percentile execution time
193    pub p95_time: Duration,
194    /// 99th percentile execution time
195    pub p99_time: Duration,
196    /// Number of samples processed per second
197    pub throughput: Option<f64>,
198    /// Memory usage statistics
199    pub memory_usage: Option<MemoryUsage>,
200    /// CPU utilization metrics
201    pub cpu_metrics: Option<CpuMetrics>,
202    /// Cache performance metrics
203    pub cache_metrics: Option<CacheMetrics>,
204    /// Parallel execution metrics
205    pub parallel_metrics: Option<ParallelMetrics>,
206    /// Input data dimensions
207    pub data_dimensions: (usize, usize),
208    /// Performance classification
209    pub performance_class: PerformanceClass,
210    /// Statistical confidence interval
211    pub confidence_interval: Option<(Duration, Duration)>,
212    /// Number of outliers detected
213    pub outliers_detected: usize,
214    /// Timestamp of benchmark execution
215    pub timestamp: chrono::DateTime<chrono::Utc>,
216    /// Custom metrics and metadata
217    pub custom_metrics: HashMap<String, f64>,
218}
219
220impl BenchmarkResult {
221    /// Create a new benchmark result
222    #[must_use]
223    pub fn new(name: String, times: Vec<Duration>, data_dimensions: (usize, usize)) -> Self {
224        let mean_time = Duration::from_nanos(
225            (times
226                .iter()
227                .map(std::time::Duration::as_nanos)
228                .sum::<u128>()
229                / times.len() as u128) as u64,
230        );
231
232        let mean_nanos = mean_time.as_nanos() as f64;
233        let variance = times
234            .iter()
235            .map(|d| {
236                let diff = d.as_nanos() as f64 - mean_nanos;
237                diff * diff
238            })
239            .sum::<f64>()
240            / times.len() as f64;
241
242        let std_dev_time = Duration::from_nanos(variance.sqrt() as u64);
243        let min_time = times.iter().min().copied().unwrap_or(Duration::ZERO);
244        let max_time = times.iter().max().copied().unwrap_or(Duration::ZERO);
245
246        // Calculate percentiles
247        let mut sorted_times = times.clone();
248        sorted_times.sort();
249
250        let median_time = Self::calculate_percentile(&sorted_times, 50.0);
251        let p95_time = Self::calculate_percentile(&sorted_times, 95.0);
252        let p99_time = Self::calculate_percentile(&sorted_times, 99.0);
253
254        Self {
255            name,
256            mean_time,
257            std_dev_time,
258            min_time,
259            max_time,
260            median_time,
261            p95_time,
262            p99_time,
263            throughput: None,
264            memory_usage: None,
265            cpu_metrics: None,
266            cache_metrics: None,
267            parallel_metrics: None,
268            data_dimensions,
269            performance_class: PerformanceClass::Normal,
270            confidence_interval: None,
271            outliers_detected: 0,
272            timestamp: chrono::Utc::now(),
273            custom_metrics: HashMap::new(),
274        }
275    }
276
277    /// Set throughput
278    #[must_use]
279    pub fn with_throughput(mut self, throughput: f64) -> Self {
280        self.throughput = Some(throughput);
281        self
282    }
283
284    /// Set memory usage
285    #[must_use]
286    pub fn with_memory_usage(mut self, memory_usage: MemoryUsage) -> Self {
287        self.memory_usage = Some(memory_usage);
288        self
289    }
290
291    /// Calculate performance score (higher is better)
292    #[must_use]
293    pub fn performance_score(&self) -> f64 {
294        let time_score = 1.0 / self.mean_time.as_secs_f64();
295        let throughput_score = self.throughput.unwrap_or(1.0);
296        let memory_score = if let Some(ref mem) = self.memory_usage {
297            1.0 / (mem.peak_usage_mb + 1.0)
298        } else {
299            1.0
300        };
301
302        // Weighted combination
303        0.5 * time_score + 0.3 * throughput_score + 0.2 * memory_score
304    }
305
306    /// Calculate percentile from sorted duration vector
307    ///
308    /// Uses linear interpolation between closest values for fractional indices.
309    ///
310    /// # Arguments
311    /// * `sorted_times` - Pre-sorted vector of durations
312    /// * `percentile` - Percentile to calculate (0-100)
313    ///
314    /// # Returns
315    /// Duration at the specified percentile
316    fn calculate_percentile(sorted_times: &[Duration], percentile: f64) -> Duration {
317        if sorted_times.is_empty() {
318            return Duration::ZERO;
319        }
320
321        if sorted_times.len() == 1 {
322            return sorted_times[0];
323        }
324
325        // Calculate index using linear interpolation
326        // Percentile rank formula: index = (percentile / 100) * (n - 1)
327        let index = (percentile / 100.0) * (sorted_times.len() - 1) as f64;
328        let lower_index = index.floor() as usize;
329        let upper_index = index.ceil() as usize;
330
331        if lower_index == upper_index {
332            // Exact index
333            sorted_times[lower_index]
334        } else {
335            // Linear interpolation between two values
336            let lower_value = sorted_times[lower_index].as_nanos() as f64;
337            let upper_value = sorted_times[upper_index].as_nanos() as f64;
338            let fraction = index - lower_index as f64;
339            let interpolated = lower_value + fraction * (upper_value - lower_value);
340
341            Duration::from_nanos(interpolated as u64)
342        }
343    }
344}
345
346/// Memory usage statistics
347#[derive(Debug, Clone, Serialize, Deserialize)]
348pub struct MemoryUsage {
349    /// Peak memory usage in MB
350    pub peak_usage_mb: f64,
351    /// Average memory usage in MB
352    pub average_usage_mb: f64,
353    /// Number of allocations
354    pub allocations: usize,
355    /// Number of deallocations
356    pub deallocations: usize,
357    /// Memory leak detection (bytes not freed)
358    pub memory_leaks_bytes: u64,
359    /// Allocation pattern efficiency score (0.0 to 1.0)
360    pub allocation_efficiency: f64,
361}
362
363/// CPU utilization metrics
364#[derive(Debug, Clone, Serialize, Deserialize)]
365pub struct CpuMetrics {
366    /// Average CPU utilization (0.0 to 1.0)
367    pub average_utilization: f64,
368    /// Peak CPU utilization (0.0 to 1.0)
369    pub peak_utilization: f64,
370    /// User mode CPU time percentage
371    pub user_time_percent: f64,
372    /// Kernel mode CPU time percentage
373    pub kernel_time_percent: f64,
374    /// Context switches per second
375    pub context_switches_per_sec: f64,
376    /// CPU efficiency score (0.0 to 1.0)
377    pub efficiency_score: f64,
378}
379
380/// Cache performance metrics
381#[derive(Debug, Clone, Serialize, Deserialize)]
382pub struct CacheMetrics {
383    /// L1 cache hit rate (0.0 to 1.0)
384    pub l1_hit_rate: f64,
385    /// L2 cache hit rate (0.0 to 1.0)
386    pub l2_hit_rate: f64,
387    /// L3 cache hit rate (0.0 to 1.0)
388    pub l3_hit_rate: f64,
389    /// Cache miss penalty (average cycles)
390    pub miss_penalty_cycles: f64,
391    /// Cache efficiency score (0.0 to 1.0)
392    pub cache_efficiency: f64,
393}
394
395/// Parallel execution metrics
396#[derive(Debug, Clone, Serialize, Deserialize)]
397pub struct ParallelMetrics {
398    /// Number of threads used
399    pub thread_count: usize,
400    /// Parallel efficiency (0.0 to 1.0)
401    pub parallel_efficiency: f64,
402    /// Load balancing score (0.0 to 1.0)
403    pub load_balance_score: f64,
404    /// Thread contention events
405    pub contention_events: usize,
406    /// Synchronization overhead percentage
407    pub sync_overhead_percent: f64,
408    /// Speedup achieved vs single thread
409    pub speedup_ratio: f64,
410}
411
412/// Performance classification categories
413#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
414pub enum PerformanceClass {
415    /// Excellent performance
416    Excellent,
417    /// Good performance
418    Good,
419    /// Normal performance
420    Normal,
421    /// Acceptable performance
422    Acceptable,
423    /// Poor performance needing optimization
424    Poor,
425    /// Critical performance issues
426    Critical,
427}
428
429/// Benchmark suite for comparing multiple strategies
430#[derive(Debug)]
431pub struct BenchmarkSuite {
432    config: BenchmarkConfig,
433    results: HashMap<String, Vec<BenchmarkResult>>,
434}
435
436impl BenchmarkSuite {
437    /// Create a new benchmark suite
438    #[must_use]
439    pub fn new(config: BenchmarkConfig) -> Self {
440        Self {
441            config,
442            results: HashMap::new(),
443        }
444    }
445
446    /// Benchmark a transformation strategy
447    pub fn benchmark_transformer<T>(&mut self, name: &str, transformer: &T) -> SklResult<()>
448    where
449        T: for<'a> Transform<ArrayView2<'a, f64>, Array2<f64>>,
450    {
451        let mut strategy_results = Vec::new();
452
453        for &n_samples in &self.config.sample_sizes {
454            for &n_features in &self.config.feature_counts {
455                let data = self.generate_data(n_samples, n_features);
456                let result = self.benchmark_single_transform(
457                    &format!("{}_{}_{}_{}", name, "transform", n_samples, n_features),
458                    transformer,
459                    &data,
460                    (n_samples, n_features),
461                )?;
462                strategy_results.push(result);
463            }
464        }
465
466        self.results.insert(name.to_string(), strategy_results);
467        Ok(())
468    }
469
470    /// Benchmark a single transformation
471    fn benchmark_single_transform<T>(
472        &self,
473        bench_name: &str,
474        transformer: &T,
475        data: &Array2<f64>,
476        dimensions: (usize, usize),
477    ) -> SklResult<BenchmarkResult>
478    where
479        T: for<'a> Transform<ArrayView2<'a, f64>, Array2<f64>>,
480    {
481        let mut times = Vec::new();
482
483        // Warmup
484        for _ in 0..self.config.warmup_iterations {
485            let _ = transformer.transform(&data.view())?;
486        }
487
488        // Benchmark
489        for _ in 0..self.config.iterations {
490            let start = Instant::now();
491            let _ = transformer.transform(&data.view())?;
492            times.push(start.elapsed());
493        }
494
495        let mut result = BenchmarkResult::new(bench_name.to_string(), times, dimensions);
496
497        // Calculate throughput
498        if self.config.measure_throughput {
499            let samples_per_sec = dimensions.0 as f64 / result.mean_time.as_secs_f64();
500            result = result.with_throughput(samples_per_sec);
501        }
502
503        // Measure memory usage (simplified version)
504        if self.config.measure_memory {
505            let memory_usage = self.estimate_memory_usage(dimensions);
506            result = result.with_memory_usage(memory_usage);
507        }
508
509        Ok(result)
510    }
511
512    /// Benchmark pipeline composition strategies
513    pub fn benchmark_composition_strategies(&mut self) -> SklResult<()> {
514        // This would benchmark different composition approaches
515        // For now, we'll simulate some benchmark results
516
517        let strategies = vec![
518            "sequential_pipeline",
519            "parallel_feature_union",
520            "dag_pipeline",
521            "zero_cost_composition",
522        ];
523
524        for strategy in strategies {
525            let mut strategy_results = Vec::new();
526
527            for &n_samples in &self.config.sample_sizes {
528                for &n_features in &self.config.feature_counts {
529                    // Simulate benchmark results for different strategies
530                    let base_time =
531                        Duration::from_micros(100 + (n_samples * n_features / 1000) as u64);
532                    let strategy_multiplier = match strategy {
533                        "sequential_pipeline" => 1.0,
534                        "parallel_feature_union" => 0.6, // Faster due to parallelization
535                        "dag_pipeline" => 0.8,           // Moderate speedup
536                        "zero_cost_composition" => 0.4,  // Fastest due to zero-cost abstractions
537                        _ => 1.0,
538                    };
539
540                    let adjusted_time = Duration::from_nanos(
541                        (base_time.as_nanos() as f64 * strategy_multiplier) as u64,
542                    );
543
544                    let times = vec![adjusted_time; self.config.iterations];
545                    let mut result = BenchmarkResult::new(
546                        format!(
547                            "{}_{}_{}_{}",
548                            strategy, "composition", n_samples, n_features
549                        ),
550                        times,
551                        (n_samples, n_features),
552                    );
553
554                    if self.config.measure_throughput {
555                        let throughput = n_samples as f64 / adjusted_time.as_secs_f64();
556                        result = result.with_throughput(throughput);
557                    }
558
559                    if self.config.measure_memory {
560                        let memory_usage = self.estimate_memory_usage((n_samples, n_features));
561                        result = result.with_memory_usage(memory_usage);
562                    }
563
564                    strategy_results.push(result);
565                }
566            }
567
568            self.results.insert(strategy.to_string(), strategy_results);
569        }
570
571        Ok(())
572    }
573
574    /// Generate test data
575    fn generate_data(&self, n_samples: usize, n_features: usize) -> Array2<f64> {
576        use scirs2_core::random::rngs::StdRng;
577        use scirs2_core::random::SeedableRng;
578
579        let mut rng = StdRng::seed_from_u64(42);
580        Array2::from_shape_fn((n_samples, n_features), |_| rng.random_range(-1.0..1.0))
581    }
582
583    /// Estimate memory usage (simplified)
584    fn estimate_memory_usage(&self, dimensions: (usize, usize)) -> MemoryUsage {
585        let (n_samples, n_features) = dimensions;
586        let data_size_mb = (n_samples * n_features * 8) as f64 / (1024.0 * 1024.0);
587
588        /// MemoryUsage
589        MemoryUsage {
590            peak_usage_mb: data_size_mb * 2.5, // Assume some overhead
591            average_usage_mb: data_size_mb * 1.8,
592            allocations: n_samples + n_features,
593            deallocations: n_samples + n_features,
594            allocation_efficiency: 0.95, // Default efficiency
595            memory_leaks_bytes: 0,       // No leaks assumed
596        }
597    }
598
599    /// Get benchmark results
600    #[must_use]
601    pub fn results(&self) -> &HashMap<String, Vec<BenchmarkResult>> {
602        &self.results
603    }
604
605    /// Generate comparison report
606    #[must_use]
607    pub fn comparison_report(&self) -> BenchmarkReport {
608        BenchmarkReport::new(self.results.clone())
609    }
610
611    /// Get the best performing strategy for given dimensions
612    #[must_use]
613    pub fn best_strategy(&self, dimensions: (usize, usize)) -> Option<(&str, &BenchmarkResult)> {
614        let mut best_strategy = None;
615        let mut best_score = 0.0;
616
617        for (strategy_name, results) in &self.results {
618            for result in results {
619                if result.data_dimensions == dimensions {
620                    let score = result.performance_score();
621                    if score > best_score {
622                        best_score = score;
623                        best_strategy = Some((strategy_name.as_str(), result));
624                    }
625                }
626            }
627        }
628
629        best_strategy
630    }
631}
632
633/// Comprehensive benchmark report
634#[derive(Debug, Clone)]
635pub struct BenchmarkReport {
636    /// Results by strategy
637    pub strategy_results: HashMap<String, Vec<BenchmarkResult>>,
638    /// Performance rankings
639    pub performance_rankings: Vec<(String, f64)>,
640    /// Scalability analysis
641    pub scalability_analysis: HashMap<String, ScalabilityMetrics>,
642}
643
644impl BenchmarkReport {
645    /// Create a new benchmark report
646    #[must_use]
647    pub fn new(strategy_results: HashMap<String, Vec<BenchmarkResult>>) -> Self {
648        let performance_rankings = Self::calculate_performance_rankings(&strategy_results);
649        let scalability_analysis = Self::analyze_scalability(&strategy_results);
650
651        Self {
652            strategy_results,
653            performance_rankings,
654            scalability_analysis,
655        }
656    }
657
658    /// Calculate performance rankings across all strategies
659    fn calculate_performance_rankings(
660        results: &HashMap<String, Vec<BenchmarkResult>>,
661    ) -> Vec<(String, f64)> {
662        let mut rankings = Vec::new();
663
664        for (strategy, strategy_results) in results {
665            let avg_score = strategy_results
666                .iter()
667                .map(BenchmarkResult::performance_score)
668                .sum::<f64>()
669                / strategy_results.len() as f64;
670            rankings.push((strategy.clone(), avg_score));
671        }
672
673        rankings.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
674        rankings
675    }
676
677    /// Analyze scalability characteristics
678    fn analyze_scalability(
679        results: &HashMap<String, Vec<BenchmarkResult>>,
680    ) -> HashMap<String, ScalabilityMetrics> {
681        let mut analysis = HashMap::new();
682
683        for (strategy, strategy_results) in results {
684            let mut sample_scalability = Vec::new();
685            let mut feature_scalability = Vec::new();
686
687            // Analyze how performance scales with data size
688            for result in strategy_results {
689                let (n_samples, n_features) = result.data_dimensions;
690                let time_per_sample = result.mean_time.as_secs_f64() / n_samples as f64;
691                let time_per_feature = result.mean_time.as_secs_f64() / n_features as f64;
692
693                sample_scalability.push((n_samples, time_per_sample));
694                feature_scalability.push((n_features, time_per_feature));
695            }
696
697            let metrics = ScalabilityMetrics {
698                sample_complexity: Self::estimate_complexity(&sample_scalability),
699                feature_complexity: Self::estimate_complexity(&feature_scalability),
700                memory_efficiency: strategy_results
701                    .iter()
702                    .filter_map(|r| r.memory_usage.as_ref())
703                    .map(|m| m.peak_usage_mb)
704                    .sum::<f64>()
705                    / strategy_results.len() as f64,
706            };
707
708            analysis.insert(strategy.clone(), metrics);
709        }
710
711        analysis
712    }
713
714    /// Estimate computational complexity
715    fn estimate_complexity(data_points: &[(usize, f64)]) -> ComplexityClass {
716        if data_points.len() < 2 {
717            return ComplexityClass::Unknown;
718        }
719
720        // Simple heuristic: if time grows linearly with size, it's O(n)
721        // If it grows quadratically, it's O(n²), etc.
722        let mut growth_ratios = Vec::new();
723
724        for i in 1..data_points.len() {
725            let (size1, time1) = data_points[i - 1];
726            let (size2, time2) = data_points[i];
727
728            if size1 > 0 && time1 > 0.0 {
729                let size_ratio = size2 as f64 / size1 as f64;
730                let time_ratio = time2 / time1;
731
732                if size_ratio > 1.0 {
733                    growth_ratios.push(time_ratio / size_ratio);
734                }
735            }
736        }
737
738        if growth_ratios.is_empty() {
739            return ComplexityClass::Unknown;
740        }
741
742        let avg_growth = growth_ratios.iter().sum::<f64>() / growth_ratios.len() as f64;
743
744        match avg_growth {
745            x if x < 1.2 => ComplexityClass::Linear,
746            x if x < 2.0 => ComplexityClass::LogLinear,
747            x if x < 4.0 => ComplexityClass::Quadratic,
748            _ => ComplexityClass::Higher,
749        }
750    }
751
752    /// Generate summary statistics
753    #[must_use]
754    pub fn summary(&self) -> String {
755        let mut summary = String::new();
756        summary.push_str("Benchmark Summary\n");
757        summary.push_str("================\n\n");
758
759        summary.push_str("Performance Rankings:\n");
760        for (i, (strategy, score)) in self.performance_rankings.iter().enumerate() {
761            let _ = write!(summary, "{}. {} (score: {:.3})\n", i + 1, strategy, score);
762        }
763
764        summary.push_str("\nScalability Analysis:\n");
765        for (strategy, metrics) in &self.scalability_analysis {
766            let _ = write!(
767                summary,
768                "{}: Sample complexity: {:?}, Feature complexity: {:?}\n",
769                strategy, metrics.sample_complexity, metrics.feature_complexity
770            );
771        }
772
773        summary
774    }
775
776    /// Get recommendations based on use case
777    #[must_use]
778    pub fn recommendations(&self, use_case: UseCase) -> Vec<String> {
779        let mut recommendations = Vec::new();
780
781        match use_case {
782            UseCase::HighThroughput => {
783                // Recommend strategies with best throughput
784                let mut throughput_rankings = self
785                    .strategy_results
786                    .iter()
787                    .map(|(name, results)| {
788                        let avg_throughput =
789                            results.iter().filter_map(|r| r.throughput).sum::<f64>()
790                                / results.len() as f64;
791                        (name, avg_throughput)
792                    })
793                    .collect::<Vec<_>>();
794
795                throughput_rankings
796                    .sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
797
798                for (strategy, throughput) in throughput_rankings.iter().take(3) {
799                    recommendations.push(format!(
800                        "{strategy} (throughput: {throughput:.2} samples/sec)"
801                    ));
802                }
803            }
804            UseCase::LowLatency => {
805                // Recommend strategies with lowest latency
806                let mut latency_rankings = self
807                    .strategy_results
808                    .iter()
809                    .map(|(name, results)| {
810                        let avg_latency = results
811                            .iter()
812                            .map(|r| r.mean_time.as_secs_f64())
813                            .sum::<f64>()
814                            / results.len() as f64;
815                        (name, avg_latency)
816                    })
817                    .collect::<Vec<_>>();
818
819                latency_rankings
820                    .sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
821
822                for (strategy, latency) in latency_rankings.iter().take(3) {
823                    recommendations.push(format!(
824                        "{} (latency: {:.3}ms)",
825                        strategy,
826                        latency * 1000.0
827                    ));
828                }
829            }
830            UseCase::MemoryConstrained => {
831                // Recommend strategies with lowest memory usage
832                let mut memory_rankings = self
833                    .strategy_results
834                    .iter()
835                    .map(|(name, results)| {
836                        let avg_memory = results
837                            .iter()
838                            .filter_map(|r| r.memory_usage.as_ref())
839                            .map(|m| m.peak_usage_mb)
840                            .sum::<f64>()
841                            / results.len() as f64;
842                        (name, avg_memory)
843                    })
844                    .collect::<Vec<_>>();
845
846                memory_rankings
847                    .sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
848
849                for (strategy, memory) in memory_rankings.iter().take(3) {
850                    recommendations.push(format!("{strategy} (memory: {memory:.2} MB)"));
851                }
852            }
853        }
854
855        recommendations
856    }
857}
858
859/// Scalability metrics for a strategy
860#[derive(Debug, Clone)]
861pub struct ScalabilityMetrics {
862    /// Computational complexity with respect to sample count
863    pub sample_complexity: ComplexityClass,
864    /// Computational complexity with respect to feature count
865    pub feature_complexity: ComplexityClass,
866    /// Memory efficiency score
867    pub memory_efficiency: f64,
868}
869
870/// Computational complexity classification
871#[derive(Debug, Clone, PartialEq)]
872pub enum ComplexityClass {
873    /// O(1) - Constant time
874    Constant,
875    /// O(log n) - Logarithmic time
876    Logarithmic,
877    /// O(n) - Linear time
878    Linear,
879    /// O(n log n) - Linearithmic time
880    LogLinear,
881    /// O(n²) - Quadratic time
882    Quadratic,
883    /// O(n³) or higher - Higher polynomial time
884    Higher,
885    /// Unknown complexity
886    Unknown,
887}
888
889/// Use case for benchmark recommendations
890#[derive(Debug, Clone, PartialEq)]
891pub enum UseCase {
892    /// High throughput processing
893    HighThroughput,
894    /// Low latency requirements
895    LowLatency,
896    /// Memory-constrained environments
897    MemoryConstrained,
898}
899
900/// Advanced benchmarking extensions for specialized analysis
901pub mod advanced_benchmarking {
902    use super::{BTreeMap, BenchmarkResult, HashMap};
903
904    /// Advanced benchmark analyzer with statistical modeling
905    pub struct AdvancedBenchmarkAnalyzer {
906        results_database: HashMap<String, Vec<BenchmarkResult>>,
907        statistical_models: HashMap<String, StatisticalModel>,
908        trend_analyzer: TrendAnalyzer,
909    }
910
911    /// Statistical model for performance prediction
912    #[derive(Debug, Clone)]
913    pub struct StatisticalModel {
914        pub model_type: ModelType,
915        pub coefficients: Vec<f64>,
916        pub r_squared: f64,
917        pub confidence_intervals: Vec<(f64, f64)>,
918        pub prediction_accuracy: f64,
919    }
920
921    /// Types of statistical models
922    #[derive(Debug, Clone, PartialEq, Eq)]
923    pub enum ModelType {
924        /// Linear
925        Linear,
926        /// Polynomial
927        Polynomial,
928        /// Exponential
929        Exponential,
930        /// Logarithmic
931        Logarithmic,
932        /// PowerLaw
933        PowerLaw,
934    }
935
936    /// Trend analysis for performance over time
937    pub struct TrendAnalyzer {
938        historical_data: BTreeMap<chrono::DateTime<chrono::Utc>, f64>,
939        trend_models: HashMap<String, TrendModel>,
940    }
941
942    /// Trend model for performance forecasting
943    #[derive(Debug, Clone)]
944    pub struct TrendModel {
945        pub trend_direction: TrendDirection,
946        pub slope: f64,
947        pub seasonal_component: Option<f64>,
948        pub confidence: f64,
949        pub forecast_horizon: chrono::Duration,
950    }
951
952    /// Trend directions
953    #[derive(Debug, Clone, PartialEq, Eq)]
954    pub enum TrendDirection {
955        /// Improving
956        Improving,
957        /// Degrading
958        Degrading,
959        /// Stable
960        Stable,
961        /// Cyclical
962        Cyclical,
963        /// Unknown
964        Unknown,
965    }
966
967    /// Resource efficiency analyzer
968    pub struct ResourceEfficiencyAnalyzer {
969        cpu_profiles: HashMap<String, CpuProfile>,
970        memory_profiles: HashMap<String, MemoryProfile>,
971        energy_profiles: HashMap<String, EnergyProfile>,
972    }
973
974    /// CPU usage profile
975    #[derive(Debug, Clone)]
976    pub struct CpuProfile {
977        pub utilization_history: Vec<f64>,
978        pub peak_utilization: f64,
979        pub average_utilization: f64,
980        pub efficiency_score: f64,
981        pub hotspots: Vec<String>,
982    }
983
984    /// Memory usage profile
985    #[derive(Debug, Clone)]
986    pub struct MemoryProfile {
987        pub allocation_pattern: AllocationPattern,
988        pub peak_usage_mb: f64,
989        pub average_usage_mb: f64,
990        pub fragmentation_score: f64,
991        pub gc_impact: f64,
992    }
993
994    /// Memory allocation patterns
995    #[derive(Debug, Clone, PartialEq, Eq)]
996    pub enum AllocationPattern {
997        /// Constant
998        Constant,
999        /// Linear
1000        Linear,
1001        /// Exponential
1002        Exponential,
1003        /// Spiky
1004        Spiky,
1005        /// Cyclical
1006        Cyclical,
1007    }
1008
1009    /// Energy consumption profile
1010    #[derive(Debug, Clone)]
1011    pub struct EnergyProfile {
1012        pub total_energy_joules: f64,
1013        pub average_power_watts: f64,
1014        pub efficiency_score: f64,
1015        pub carbon_footprint_kg: f64,
1016    }
1017
1018    /// Comparative benchmark analysis
1019    pub struct ComparativeBenchmarkAnalysis {
1020        pub baseline_component: String,
1021        pub comparison_components: Vec<String>,
1022        pub metrics: Vec<ComparisonMetric>,
1023        pub statistical_significance: f64,
1024        pub effect_sizes: HashMap<String, f64>,
1025        pub confidence_intervals: HashMap<String, (f64, f64)>,
1026    }
1027
1028    /// Comparison metrics
1029    #[derive(Debug, Clone)]
1030    pub struct ComparisonMetric {
1031        pub name: String,
1032        pub baseline_value: f64,
1033        pub comparison_values: HashMap<String, f64>,
1034        pub relative_improvements: HashMap<String, f64>,
1035        pub statistical_tests: HashMap<String, StatisticalTest>,
1036    }
1037
1038    /// Statistical test results
1039    #[derive(Debug, Clone)]
1040    pub struct StatisticalTest {
1041        pub test_type: TestType,
1042        pub p_value: f64,
1043        pub test_statistic: f64,
1044        pub is_significant: bool,
1045        pub effect_size: f64,
1046    }
1047
1048    /// Types of statistical tests
1049    #[derive(Debug, Clone, PartialEq, Eq)]
1050    pub enum TestType {
1051        /// TTest
1052        TTest,
1053        /// WilcoxonTest
1054        WilcoxonTest,
1055        /// MannWhitneyU
1056        MannWhitneyU,
1057        /// KruskalWallis
1058        KruskalWallis,
1059        /// ANOVA
1060        ANOVA,
1061    }
1062
1063    impl Default for AdvancedBenchmarkAnalyzer {
1064        fn default() -> Self {
1065            Self::new()
1066        }
1067    }
1068
1069    impl AdvancedBenchmarkAnalyzer {
1070        /// Create a new advanced benchmark analyzer
1071        #[must_use]
1072        pub fn new() -> Self {
1073            Self {
1074                results_database: HashMap::new(),
1075                statistical_models: HashMap::new(),
1076                trend_analyzer: TrendAnalyzer::new(),
1077            }
1078        }
1079
1080        /// Add benchmark results for analysis
1081        pub fn add_results(&mut self, component_name: String, results: Vec<BenchmarkResult>) {
1082            self.results_database.insert(component_name, results);
1083        }
1084
1085        /// Build statistical model for performance prediction
1086        pub fn build_model(&mut self, component_name: &str) -> Option<StatisticalModel> {
1087            if let Some(results) = self.results_database.get(component_name) {
1088                let data_points: Vec<(f64, f64)> = results
1089                    .iter()
1090                    .enumerate()
1091                    .map(|(i, result)| (i as f64, result.mean_time.as_millis() as f64))
1092                    .collect();
1093
1094                let model = self.fit_model(&data_points);
1095                self.statistical_models
1096                    .insert(component_name.to_string(), model.clone());
1097                Some(model)
1098            } else {
1099                None
1100            }
1101        }
1102
1103        /// Predict performance for given input size
1104        #[must_use]
1105        pub fn predict_performance(&self, component_name: &str, input_size: f64) -> Option<f64> {
1106            self.statistical_models
1107                .get(component_name)
1108                .map(|model| self.apply_model(model, input_size))
1109        }
1110
1111        /// Analyze performance trends over time
1112        pub fn analyze_trends(&mut self, component_name: &str) -> Option<TrendModel> {
1113            self.trend_analyzer.analyze_component_trends(component_name)
1114        }
1115
1116        /// Generate comprehensive analysis report
1117        #[must_use]
1118        pub fn generate_analysis_report(&self) -> AdvancedAnalysisReport {
1119            let mut component_analyses = HashMap::new();
1120
1121            for (component_name, results) in &self.results_database {
1122                let analysis = ComponentAnalysis {
1123                    component_name: component_name.clone(),
1124                    total_benchmarks: results.len(),
1125                    performance_model: self.statistical_models.get(component_name).cloned(),
1126                    trend_analysis: None, // Would be populated from trend analyzer
1127                    efficiency_scores: self.calculate_efficiency_scores(results),
1128                    recommendations: self
1129                        .generate_component_recommendations(component_name, results),
1130                };
1131                component_analyses.insert(component_name.clone(), analysis);
1132            }
1133
1134            /// AdvancedAnalysisReport
1135            AdvancedAnalysisReport {
1136                analysis_timestamp: chrono::Utc::now(),
1137                total_components: component_analyses.len(),
1138                component_analyses,
1139                cross_component_insights: self.generate_cross_component_insights(),
1140                optimization_recommendations: self.generate_optimization_recommendations(),
1141            }
1142        }
1143
1144        /// Perform comparative analysis between components
1145        #[must_use]
1146        pub fn comparative_analysis(
1147            &self,
1148            baseline: &str,
1149            comparisons: &[&str],
1150        ) -> Option<ComparativeBenchmarkAnalysis> {
1151            if let Some(baseline_results) = self.results_database.get(baseline) {
1152                let mut comparison_values = HashMap::new();
1153                let mut effect_sizes = HashMap::new();
1154
1155                for &component in comparisons {
1156                    if let Some(comp_results) = self.results_database.get(component) {
1157                        let baseline_mean = self.calculate_mean_performance(baseline_results);
1158                        let comp_mean = self.calculate_mean_performance(comp_results);
1159
1160                        comparison_values.insert(component.to_string(), comp_mean);
1161
1162                        // Calculate effect size (Cohen's d)
1163                        let effect_size =
1164                            self.calculate_effect_size(baseline_results, comp_results);
1165                        effect_sizes.insert(component.to_string(), effect_size);
1166                    }
1167                }
1168
1169                Some(ComparativeBenchmarkAnalysis {
1170                    baseline_component: baseline.to_string(),
1171                    comparison_components: comparisons.iter().map(|s| (*s).to_string()).collect(),
1172                    metrics: vec![], // Would be populated with detailed metrics
1173                    statistical_significance: 0.95, // Placeholder
1174                    effect_sizes,
1175                    confidence_intervals: HashMap::new(), // Would calculate actual CIs
1176                })
1177            } else {
1178                None
1179            }
1180        }
1181
1182        // Private implementation methods
1183
1184        fn fit_model(&self, data: &[(f64, f64)]) -> StatisticalModel {
1185            // Simplified linear regression
1186            let n = data.len() as f64;
1187            let sum_x: f64 = data.iter().map(|(x, _)| x).sum();
1188            let sum_y: f64 = data.iter().map(|(_, y)| y).sum();
1189            let sum_xy: f64 = data.iter().map(|(x, y)| x * y).sum();
1190            let sum_x2: f64 = data.iter().map(|(x, _)| x * x).sum();
1191
1192            let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x);
1193            let intercept = (sum_y - slope * sum_x) / n;
1194
1195            // Calculate R-squared
1196            let mean_y = sum_y / n;
1197            let ss_tot: f64 = data.iter().map(|(_, y)| (y - mean_y).powi(2)).sum();
1198            let ss_res: f64 = data
1199                .iter()
1200                .map(|(x, y)| (y - (slope * x + intercept)).powi(2))
1201                .sum();
1202            let r_squared = 1.0 - (ss_res / ss_tot);
1203
1204            /// StatisticalModel
1205            StatisticalModel {
1206                model_type: ModelType::Linear,
1207                coefficients: vec![intercept, slope],
1208                r_squared,
1209                confidence_intervals: vec![], // Would calculate actual CIs
1210                prediction_accuracy: r_squared,
1211            }
1212        }
1213
1214        fn apply_model(&self, model: &StatisticalModel, input: f64) -> f64 {
1215            match model.model_type {
1216                ModelType::Linear => model.coefficients[0] + model.coefficients[1] * input,
1217                ModelType::Polynomial => model
1218                    .coefficients
1219                    .iter()
1220                    .enumerate()
1221                    .map(|(i, &coef)| coef * input.powi(i as i32))
1222                    .sum(),
1223                _ => model.coefficients[0] + model.coefficients[1] * input, // Fallback to linear
1224            }
1225        }
1226
1227        fn calculate_mean_performance(&self, results: &[BenchmarkResult]) -> f64 {
1228            results
1229                .iter()
1230                .map(|r| r.mean_time.as_millis() as f64)
1231                .sum::<f64>()
1232                / results.len() as f64
1233        }
1234
1235        fn calculate_effect_size(
1236            &self,
1237            baseline: &[BenchmarkResult],
1238            comparison: &[BenchmarkResult],
1239        ) -> f64 {
1240            let baseline_mean = self.calculate_mean_performance(baseline);
1241            let comparison_mean = self.calculate_mean_performance(comparison);
1242
1243            // Simplified effect size calculation
1244            let pooled_std = 1.0; // Would calculate actual pooled standard deviation
1245            (comparison_mean - baseline_mean) / pooled_std
1246        }
1247
1248        fn calculate_efficiency_scores(
1249            &self,
1250            _results: &[BenchmarkResult],
1251        ) -> HashMap<String, f64> {
1252            let mut scores = HashMap::new();
1253            scores.insert("cpu_efficiency".to_string(), 0.85);
1254            scores.insert("memory_efficiency".to_string(), 0.78);
1255            scores.insert("energy_efficiency".to_string(), 0.82);
1256            scores
1257        }
1258
1259        fn generate_component_recommendations(
1260            &self,
1261            _component_name: &str,
1262            _results: &[BenchmarkResult],
1263        ) -> Vec<String> {
1264            vec![
1265                "Consider optimizing memory allocation patterns".to_string(),
1266                "Investigate opportunities for parallel processing".to_string(),
1267                "Profile CPU-intensive operations for bottlenecks".to_string(),
1268            ]
1269        }
1270
1271        fn generate_cross_component_insights(&self) -> Vec<String> {
1272            vec![
1273                "Linear scaling algorithms show better performance on large datasets".to_string(),
1274                "Memory-intensive components benefit from batch processing".to_string(),
1275                "CPU-bound operations should prioritize algorithmic optimizations".to_string(),
1276            ]
1277        }
1278
1279        fn generate_optimization_recommendations(&self) -> Vec<String> {
1280            vec![
1281                "Implement adaptive batch sizing based on available memory".to_string(),
1282                "Consider using SIMD instructions for vectorizable operations".to_string(),
1283                "Implement memory pooling for frequently allocated objects".to_string(),
1284            ]
1285        }
1286    }
1287
1288    impl TrendAnalyzer {
1289        #[must_use]
1290        pub fn new() -> Self {
1291            Self {
1292                historical_data: BTreeMap::new(),
1293                trend_models: HashMap::new(),
1294            }
1295        }
1296
1297        #[must_use]
1298        pub fn analyze_component_trends(&self, _component_name: &str) -> Option<TrendModel> {
1299            // Simplified trend analysis
1300            Some(TrendModel {
1301                trend_direction: TrendDirection::Stable,
1302                slope: 0.01,
1303                seasonal_component: None,
1304                confidence: 0.8,
1305                forecast_horizon: chrono::Duration::days(30),
1306            })
1307        }
1308    }
1309
1310    /// Component-specific analysis results
1311    #[derive(Debug, Clone)]
1312    pub struct ComponentAnalysis {
1313        pub component_name: String,
1314        pub total_benchmarks: usize,
1315        pub performance_model: Option<StatisticalModel>,
1316        pub trend_analysis: Option<TrendModel>,
1317        pub efficiency_scores: HashMap<String, f64>,
1318        pub recommendations: Vec<String>,
1319    }
1320
1321    /// Advanced analysis report
1322    #[derive(Debug, Clone)]
1323    pub struct AdvancedAnalysisReport {
1324        pub analysis_timestamp: chrono::DateTime<chrono::Utc>,
1325        pub total_components: usize,
1326        pub component_analyses: HashMap<String, ComponentAnalysis>,
1327        pub cross_component_insights: Vec<String>,
1328        pub optimization_recommendations: Vec<String>,
1329    }
1330
1331    impl Default for TrendAnalyzer {
1332        fn default() -> Self {
1333            Self::new()
1334        }
1335    }
1336}
1337
1338#[allow(non_snake_case)]
1339#[cfg(test)]
1340mod tests {
1341    use super::*;
1342    use crate::mock::MockTransformer;
1343
1344    #[test]
1345    fn test_benchmark_config() {
1346        let config = BenchmarkConfig::new()
1347            .iterations(50)
1348            .sample_sizes(vec![100, 500])
1349            .feature_counts(vec![5, 10]);
1350
1351        assert_eq!(config.iterations, 50);
1352        assert_eq!(config.sample_sizes, vec![100, 500]);
1353        assert_eq!(config.feature_counts, vec![5, 10]);
1354    }
1355
1356    #[test]
1357    fn test_benchmark_result() {
1358        let times = vec![
1359            Duration::from_millis(10),
1360            Duration::from_millis(12),
1361            Duration::from_millis(11),
1362        ];
1363
1364        let result = BenchmarkResult::new("test".to_string(), times, (100, 5));
1365        assert_eq!(result.name, "test");
1366        assert_eq!(result.data_dimensions, (100, 5));
1367        assert!(result.performance_score() > 0.0);
1368    }
1369
1370    #[test]
1371    fn test_benchmark_suite() {
1372        let config = BenchmarkConfig::new()
1373            .iterations(5)
1374            .sample_sizes(vec![10])
1375            .feature_counts(vec![3]);
1376
1377        let mut suite = BenchmarkSuite::new(config);
1378        let transformer = MockTransformer::new();
1379
1380        suite
1381            .benchmark_transformer("mock", &transformer)
1382            .unwrap_or_default();
1383        assert!(suite.results().contains_key("mock"));
1384    }
1385
1386    #[test]
1387    fn test_benchmark_report() {
1388        let mut results = HashMap::new();
1389        let times = vec![Duration::from_millis(10); 3];
1390        let result = BenchmarkResult::new("test".to_string(), times, (100, 5));
1391        results.insert("strategy1".to_string(), vec![result]);
1392
1393        let report = BenchmarkReport::new(results);
1394        assert_eq!(report.performance_rankings.len(), 1);
1395        assert!(!report.summary().is_empty());
1396    }
1397
1398    #[test]
1399    fn test_complexity_estimation() {
1400        let data = vec![(10, 0.1), (20, 0.2), (30, 0.3)];
1401        let complexity = BenchmarkReport::estimate_complexity(&data);
1402        assert_eq!(complexity, ComplexityClass::Linear);
1403    }
1404
1405    #[test]
1406    fn test_use_case_recommendations() {
1407        let mut results = HashMap::new();
1408        let times = vec![Duration::from_millis(10); 3];
1409        let result =
1410            BenchmarkResult::new("test".to_string(), times, (100, 5)).with_throughput(1000.0);
1411        results.insert("fast_strategy".to_string(), vec![result]);
1412
1413        let report = BenchmarkReport::new(results);
1414        let recommendations = report.recommendations(UseCase::HighThroughput);
1415        assert!(!recommendations.is_empty());
1416    }
1417}