Skip to main content

scirs2_core/benchmarking/
performance.rs

1//! # Performance Benchmarking
2//!
3//! This module provides specific benchmarking tools for measuring and validating
4//! performance characteristics of `SciRS2` Core functions and algorithms.
5
6use crate::benchmarking::{BenchmarkConfig, BenchmarkResult, BenchmarkRunner, BenchmarkSuite};
7use crate::error::{CoreError, CoreResult, ErrorContext};
8use std::time::Duration;
9
10/// Performance benchmark categories
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum BenchmarkCategory {
13    /// CPU-intensive computations
14    Computation,
15    /// Memory access patterns
16    Memory,
17    /// I/O operations
18    InputOutput,
19    /// Parallel processing
20    Parallel,
21    /// SIMD operations
22    Simd,
23    /// Algorithm complexity
24    Algorithmic,
25}
26
27/// Performance target specification
28#[derive(Debug, Clone)]
29pub struct PerformanceTarget {
30    /// Category of the benchmark
31    pub category: BenchmarkCategory,
32    /// Target execution time (maximum acceptable)
33    pub target_time: Duration,
34    /// Target throughput (minimum acceptable, operations per second)
35    pub target_throughput: Option<f64>,
36    /// Target memory usage (maximum acceptable, in bytes)
37    pub target_memory: Option<usize>,
38    /// Scaling factor for different input sizes
39    pub scaling_factor: f64,
40}
41
42impl PerformanceTarget {
43    /// Create a new performance target
44    pub fn new(category: BenchmarkCategory, target_time: Duration) -> Self {
45        Self {
46            category,
47            target_time,
48            target_throughput: None,
49            target_memory: None,
50            scaling_factor: 1.0,
51        }
52    }
53
54    /// Set target throughput
55    pub fn with_throughput(mut self, throughput: f64) -> Self {
56        self.target_throughput = Some(throughput);
57        self
58    }
59
60    /// Set target memory usage
61    pub fn with_memory(mut self, memory: usize) -> Self {
62        self.target_memory = Some(memory);
63        self
64    }
65
66    /// Set scaling factor
67    pub fn with_scaling_factor(mut self, factor: f64) -> Self {
68        self.scaling_factor = factor;
69        self
70    }
71
72    /// Check if benchmark result meets this target
73    pub fn is_met_by(&self, result: &BenchmarkResult, input_scale: f64) -> bool {
74        let scaled_target_time = Duration::from_nanos(
75            (self.target_time.as_nanos() as f64 * input_scale.powf(self.scaling_factor)) as u64,
76        );
77
78        // Check execution time
79        if result.statistics.mean_execution_time > scaled_target_time {
80            return false;
81        }
82
83        // Check throughput if specified
84        if let Some(target_throughput) = self.target_throughput {
85            let actual_throughput = 1.0 / result.statistics.mean_execution_time.as_secs_f64();
86            if actual_throughput < target_throughput {
87                return false;
88            }
89        }
90
91        // Check memory usage if specified
92        if let Some(target_memory) = self.target_memory {
93            if result.statistics.mean_memory_usage > target_memory {
94                return false;
95            }
96        }
97
98        true
99    }
100}
101
102/// Performance benchmark result with target validation
103#[derive(Debug, Clone)]
104pub struct PerformanceBenchmarkResult {
105    /// Base benchmark result
106    pub benchmark_result: BenchmarkResult,
107    /// Performance target
108    pub target: PerformanceTarget,
109    /// Input scale factor used
110    pub input_scale: f64,
111    /// Whether target was met
112    pub target_met: bool,
113    /// Performance ratio (actual vs target)
114    pub performance_ratio: f64,
115}
116
117impl PerformanceBenchmarkResult {
118    /// Create a new performance benchmark result
119    pub fn new(
120        benchmark_result: BenchmarkResult,
121        target: PerformanceTarget,
122        input_scale: f64,
123    ) -> Self {
124        let target_met = target.is_met_by(&benchmark_result, input_scale);
125
126        let scaled_target_time = Duration::from_nanos(
127            (target.target_time.as_nanos() as f64 * input_scale.powf(target.scaling_factor)) as u64,
128        );
129
130        let performance_ratio = benchmark_result
131            .statistics
132            .mean_execution_time
133            .as_secs_f64()
134            / scaled_target_time.as_secs_f64();
135
136        Self {
137            benchmark_result,
138            target,
139            input_scale,
140            target_met,
141            performance_ratio,
142        }
143    }
144
145    /// Get performance grade
146    pub fn performance_grade(&self) -> PerformanceGrade {
147        if self.performance_ratio <= 0.5 {
148            PerformanceGrade::Excellent
149        } else if self.performance_ratio <= 0.8 {
150            PerformanceGrade::Good
151        } else if self.performance_ratio <= 1.0 {
152            PerformanceGrade::Acceptable
153        } else if self.performance_ratio <= 1.5 {
154            PerformanceGrade::Poor
155        } else {
156            PerformanceGrade::Unacceptable
157        }
158    }
159}
160
161/// Performance grade classification
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum PerformanceGrade {
164    /// Performance significantly exceeds target
165    Excellent,
166    /// Performance exceeds target
167    Good,
168    /// Performance meets target
169    Acceptable,
170    /// Performance slightly below target
171    Poor,
172    /// Performance significantly below target
173    Unacceptable,
174}
175
176/// Performance benchmarking utilities
177pub struct PerformanceBenchmarker {
178    runner: BenchmarkRunner,
179}
180
181impl PerformanceBenchmarker {
182    /// Create a new performance benchmarker
183    pub fn new(config: BenchmarkConfig) -> Self {
184        Self {
185            runner: BenchmarkRunner::new(config),
186        }
187    }
188
189    /// Run a performance benchmark with target validation
190    pub fn run_with_target<F, T>(
191        &self,
192        name: &str,
193        target: PerformanceTarget,
194        input_scale: f64,
195        benchmark_fn: F,
196    ) -> CoreResult<PerformanceBenchmarkResult>
197    where
198        F: FnMut() -> CoreResult<T>,
199    {
200        let benchmark_result = self.runner.run(name, benchmark_fn)?;
201        Ok(PerformanceBenchmarkResult::new(
202            benchmark_result,
203            target,
204            input_scale,
205        ))
206    }
207
208    /// Run computational performance benchmarks
209    pub fn benchmark_computation<F, T>(
210        &self,
211        name: &str,
212        computation_fn: F,
213        expected_complexity: f64,
214    ) -> CoreResult<PerformanceBenchmarkResult>
215    where
216        F: FnMut() -> CoreResult<T>,
217    {
218        let target =
219            PerformanceTarget::new(BenchmarkCategory::Computation, Duration::from_millis(100))
220                .with_scaling_factor(expected_complexity);
221
222        self.run_with_target(name, target, 1.0, computation_fn)
223    }
224
225    /// Run memory access performance benchmarks
226    pub fn benchmark_memory_access<F, T>(
227        &self,
228        name: &str,
229        memory_fn: F,
230        data_size: usize,
231    ) -> CoreResult<PerformanceBenchmarkResult>
232    where
233        F: FnMut() -> CoreResult<T>,
234    {
235        let target = PerformanceTarget::new(
236            BenchmarkCategory::Memory,
237            Duration::from_micros(10),
238        )
239        .with_memory(data_size * 2) // Allow 2x memory overhead
240        .with_scaling_factor(1.0); // Linear scaling with data size
241
242        let scale = data_size as f64 / 1024.0; // Scale relative to 1KB
243        self.run_with_target(name, target, scale, memory_fn)
244    }
245
246    /// Run algorithmic complexity benchmarks
247    pub fn benchmark_algorithm_scaling<F, T>(
248        &self,
249        name: &str,
250        algorithm_fn: F,
251        input_sizes: Vec<usize>,
252        expected_complexity: f64,
253    ) -> CoreResult<Vec<PerformanceBenchmarkResult>>
254    where
255        F: Fn(usize) -> CoreResult<T> + Clone,
256    {
257        let mut results = Vec::new();
258        let base_target =
259            PerformanceTarget::new(BenchmarkCategory::Algorithmic, Duration::from_millis(10))
260                .with_scaling_factor(expected_complexity);
261
262        for size in &input_sizes {
263            let size_name = format!("{}(n={})", name, size);
264            let algorithm_clone = algorithm_fn.clone();
265
266            let benchmark_result = self.runner.run(&size_name, || algorithm_clone(*size))?;
267            let scale = *size as f64 / input_sizes[0] as f64;
268            let performance_result =
269                PerformanceBenchmarkResult::new(benchmark_result, base_target.clone(), scale);
270
271            results.push(performance_result);
272        }
273
274        Ok(results)
275    }
276
277    /// Benchmark SIMD operations
278    #[cfg(feature = "simd")]
279    pub fn benchmark_simd<F, G, T>(
280        &self,
281        name: &str,
282        simd_fn: F,
283        scalar_fn: G,
284        data_size: usize,
285    ) -> CoreResult<(PerformanceBenchmarkResult, PerformanceBenchmarkResult, f64)>
286    where
287        F: FnMut() -> CoreResult<T>,
288        G: FnMut() -> CoreResult<T>,
289    {
290        // Benchmark SIMD version
291        let simd_target =
292            PerformanceTarget::new(BenchmarkCategory::Simd, Duration::from_micros(100));
293        let simd_result = self.run_with_target(
294            &format!("{}_simd", name),
295            simd_target,
296            data_size as f64 / 1000.0,
297            simd_fn,
298        )?;
299
300        // Benchmark scalar version
301        let scalar_target =
302            PerformanceTarget::new(BenchmarkCategory::Computation, Duration::from_millis(1));
303        let scalar_result = self.run_with_target(
304            &format!("{}_scalar", name),
305            scalar_target,
306            data_size as f64 / 1000.0,
307            scalar_fn,
308        )?;
309
310        // Calculate speedup
311        let speedup = scalar_result
312            .benchmark_result
313            .statistics
314            .mean_execution_time
315            .as_secs_f64()
316            / simd_result
317                .benchmark_result
318                .statistics
319                .mean_execution_time
320                .as_secs_f64();
321
322        Ok((simd_result, scalar_result, speedup))
323    }
324
325    /// Benchmark parallel operations
326    #[cfg(feature = "parallel")]
327    pub fn benchmark_parallel<F, G, T>(
328        &self,
329        name: &str,
330        parallel_fn: F,
331        sequential_fn: G,
332        thread_count: usize,
333    ) -> CoreResult<(PerformanceBenchmarkResult, PerformanceBenchmarkResult, f64)>
334    where
335        F: FnMut() -> CoreResult<T>,
336        G: FnMut() -> CoreResult<T>,
337    {
338        // Benchmark parallel version
339        let parallel_target =
340            PerformanceTarget::new(BenchmarkCategory::Parallel, Duration::from_millis(100));
341        let parallel_result = self.run_with_target(
342            &format!("{}_parallel", name),
343            parallel_target,
344            1.0,
345            parallel_fn,
346        )?;
347
348        // Benchmark sequential version
349        let sequential_target =
350            PerformanceTarget::new(BenchmarkCategory::Computation, Duration::from_millis(500));
351        let sequential_result = self.run_with_target(
352            &format!("{}_sequential", name),
353            sequential_target,
354            1.0,
355            sequential_fn,
356        )?;
357
358        // Calculate efficiency
359        let theoretical_speedup = thread_count as f64;
360        let actual_speedup = sequential_result
361            .benchmark_result
362            .statistics
363            .mean_execution_time
364            .as_secs_f64()
365            / parallel_result
366                .benchmark_result
367                .statistics
368                .mean_execution_time
369                .as_secs_f64();
370        let efficiency = actual_speedup / theoretical_speedup;
371
372        Ok((parallel_result, sequential_result, efficiency))
373    }
374}
375
376/// Create standard performance benchmark suites
377pub struct StandardBenchmarks;
378
379impl StandardBenchmarks {
380    /// Create a computational benchmark suite
381    pub fn create_computation_suite(config: BenchmarkConfig) -> BenchmarkSuite {
382        let mut suite = BenchmarkSuite::new("computation_performance", config);
383
384        // Basic arithmetic operations
385        suite.add_benchmark(|runner| {
386            runner.run("arithmetic_operations", || {
387                let mut sum = 0.0f64;
388                for i in 0..10000 {
389                    sum += (i as f64).sin().cos().sqrt();
390                }
391                Ok(sum)
392            })
393        });
394
395        // Vector operations
396        suite.add_benchmark(|runner| {
397            runner.run("vector_operations", || {
398                let a: Vec<f64> = (0..10000).map(|i| i as f64).collect();
399                let b: Vec<f64> = (0..10000).map(|i| (i as f64) * 2.0).collect();
400                let result: Vec<f64> = a.iter().zip(b.iter()).map(|(x, y)| x + y).collect();
401                Ok(result.iter().sum::<f64>())
402            })
403        });
404
405        // Matrix operations (simplified)
406        suite.add_benchmark(|runner| {
407            runner.run("matrix_multiplication", || {
408                let size = 100;
409                let a: Vec<Vec<f64>> = (0..size)
410                    .map(|i| (0..size).map(|j| (i * j) as f64).collect())
411                    .collect();
412                let b: Vec<Vec<f64>> = (0..size)
413                    .map(|i| (0..size).map(|j| (i + j) as f64).collect())
414                    .collect();
415
416                let mut c = vec![vec![0.0; size]; size];
417                for i in 0..size {
418                    for j in 0..size {
419                        #[allow(clippy::needless_range_loop)]
420                        for k in 0..size {
421                            c[i][j] += a[i][k] * b[k][j];
422                        }
423                    }
424                }
425
426                Ok(c[0][0])
427            })
428        });
429
430        suite
431    }
432
433    /// Create a memory benchmark suite
434    pub fn create_memory_suite(config: BenchmarkConfig) -> BenchmarkSuite {
435        let mut suite = BenchmarkSuite::new("memory_performance", config);
436
437        // Memory allocation
438        suite.add_benchmark(|runner| {
439            runner.run("memory_allocation", || {
440                let mut vectors = Vec::new();
441                for i in 0..1000 {
442                    vectors.push(vec![i as f64; 1000]);
443                }
444                Ok(vectors.len())
445            })
446        });
447
448        // Sequential memory access
449        suite.add_benchmark(|runner| {
450            runner.run("sequential_access", || {
451                let data: Vec<f64> = (0..1000000).map(|i| i as f64).collect();
452                let sum: f64 = data.iter().sum();
453                Ok(sum)
454            })
455        });
456
457        // Random memory access
458        suite.add_benchmark(|runner| {
459            runner.run("random_access", || {
460                let data: Vec<f64> = (0..100000).map(|i| i as f64).collect();
461                let mut sum = 0.0;
462                for i in (0..data.len()).step_by(1000) {
463                    sum += data[i];
464                }
465                Ok(sum)
466            })
467        });
468
469        suite
470    }
471
472    /// Create an I/O benchmark suite
473    pub fn create_io_suite(config: BenchmarkConfig) -> BenchmarkSuite {
474        let mut suite = BenchmarkSuite::new("io_performance", config);
475
476        // File I/O
477        suite.add_benchmark(|runner| {
478            runner.run_with_setup(
479                "file_io",
480                || {
481                    use tempfile::NamedTempFile;
482                    let temp_file = NamedTempFile::new().map_err(|e| {
483                        CoreError::IoError(ErrorContext::new(format!(
484                            "Failed to create temp file: {}",
485                            e
486                        )))
487                    })?;
488                    Ok(temp_file)
489                },
490                |temp_file| {
491                    use std::io::Write;
492                    let data = vec![42u8; 10000];
493                    temp_file
494                        .write_all(&data)
495                        .map_err(|e| CoreError::IoError(ErrorContext::new(format!("{e}"))))?;
496                    temp_file
497                        .flush()
498                        .map_err(|e| CoreError::IoError(ErrorContext::new(format!("{e}"))))?;
499                    Ok(data.len())
500                },
501                |temp_file| {
502                    drop(temp_file);
503                    Ok(())
504                },
505            )
506        });
507
508        suite
509    }
510
511    /// Create a comprehensive benchmark suite
512    pub fn create_comprehensive_suite(config: BenchmarkConfig) -> BenchmarkSuite {
513        let mut suite = BenchmarkSuite::new("comprehensive_performance", config.clone());
514
515        // Add computation benchmarks
516        let comp_suite = Self::create_computation_suite(config.clone());
517        // Note: This is a simplified version - in practice you'd need to extract benchmarks
518        suite.add_benchmark(|runner| {
519            runner.run("comprehensive_computation", || {
520                // Simplified computation benchmark
521                let mut result = 0.0;
522                for i in 0..1000 {
523                    result += (i as f64).sin();
524                }
525                Ok(result)
526            })
527        });
528
529        // Add memory benchmarks
530        suite.add_benchmark(|runner| {
531            runner.run("comprehensive_memory", || {
532                let data: Vec<f64> = (0..10000).map(|i| i as f64).collect();
533                Ok(data.iter().sum::<f64>())
534            })
535        });
536
537        suite
538    }
539}
540
541#[cfg(test)]
542mod tests {
543    use super::*;
544
545    #[test]
546    fn test_performance_target() {
547        let target =
548            PerformanceTarget::new(BenchmarkCategory::Computation, Duration::from_millis(100))
549                .with_throughput(1000.0)
550                .with_memory(1024)
551                .with_scaling_factor(2.0);
552
553        assert_eq!(target.category, BenchmarkCategory::Computation);
554        assert_eq!(target.target_time, Duration::from_millis(100));
555        assert_eq!(target.target_throughput, Some(1000.0));
556        assert_eq!(target.target_memory, Some(1024));
557        assert_eq!(target.scaling_factor, 2.0);
558    }
559
560    #[test]
561    fn test_performance_grade() {
562        let config = BenchmarkConfig::default();
563        let mut result = crate::benchmarking::BenchmarkResult::new("test".to_string(), config);
564
565        // Add some measurements
566        result.add_measurement(crate::benchmarking::BenchmarkMeasurement::new(
567            Duration::from_millis(50),
568        ));
569        result.finalize().expect("Operation failed");
570
571        let target =
572            PerformanceTarget::new(BenchmarkCategory::Computation, Duration::from_millis(100));
573
574        let perf_result = PerformanceBenchmarkResult::new(result, target, 1.0);
575        // With measurement of 50ms and target of 100ms, ratio is 0.5, which is Excellent
576        assert_eq!(perf_result.performance_grade(), PerformanceGrade::Excellent);
577    }
578
579    #[test]
580    fn test_performance_benchmarker() {
581        let config = BenchmarkConfig::new()
582            .with_warmup_iterations(1)
583            .with_measurement_iterations(5);
584        let benchmarker = PerformanceBenchmarker::new(config);
585
586        let result = benchmarker
587            .benchmark_computation(
588                "test_computation",
589                || {
590                    std::thread::sleep(Duration::from_micros(100));
591                    Ok(42)
592                },
593                1.0,
594            )
595            .expect("Operation failed");
596
597        assert!(result.benchmark_result.statistics.mean_execution_time > Duration::from_micros(50));
598        assert_eq!(result.target.category, BenchmarkCategory::Computation);
599    }
600}