1use crate::benchmarking::current_process_memory_bytes;
5use crate::error::{CoreError, CoreResult, ErrorContext};
6#[cfg(feature = "parallel")]
7use crate::parallel_ops::*;
8#[cfg(feature = "serialization")]
9use chrono;
10use std::collections::HashMap;
11use std::fmt;
12use std::sync::{Arc, Mutex};
13use std::time::{Duration, Instant};
14#[derive(Debug, Clone)]
16pub struct CrossModuleBenchConfig {
17 pub iterations: usize,
19 pub warmup_iterations: usize,
21 pub datasizes: Vec<usize>,
23 pub ns: Vec<usize>,
25 pub memory_limits: Vec<usize>,
27 pub enable_profiling: bool,
29 pub enable_regression_detection: bool,
31 pub baseline_file: Option<String>,
33 pub max_regression_percent: f64,
35 pub timeout: Duration,
37}
38impl Default for CrossModuleBenchConfig {
39 fn default() -> Self {
40 Self {
41 iterations: 100,
42 warmup_iterations: 10,
43 datasizes: vec![1024, 1024 * 16, 1024 * 1024, 1024 * 1024 * 16],
44 ns: vec![1, 2, 4, 8],
45 memory_limits: vec![64 * 1024 * 1024, 256 * 1024 * 1024, 1024 * 1024 * 1024],
46 enable_profiling: true,
47 enable_regression_detection: true,
48 baseline_file: None,
49 max_regression_percent: 10.0,
50 timeout: Duration::from_secs(60),
51 }
52 }
53}
54#[derive(Debug, Clone)]
56pub struct PerformanceMeasurement {
57 pub name: String,
59 pub modules: Vec<String>,
61 pub datasize: usize,
63 pub n: usize,
65 pub avg_duration: Duration,
67 pub min_duration: Duration,
69 pub max_duration: Duration,
71 pub std_deviation: Duration,
73 pub throughput: f64,
75 pub memory_usage: usize,
77 pub peak_memory: usize,
79 pub cpu_utilization: f64,
81 pub operations_count: usize,
83 pub timing_breakdown: HashMap<String, Duration>,
85}
86impl PerformanceMeasurement {
87 pub fn new(name: String, modules: Vec<String>) -> Self {
89 Self {
90 name,
91 modules,
92 datasize: 0,
93 n: 1,
94 avg_duration: Duration::from_nanos(0),
95 min_duration: Duration::from_nanos(u64::MAX),
96 max_duration: Duration::from_nanos(0),
97 std_deviation: Duration::from_nanos(0),
98 throughput: 0.0,
99 memory_usage: 0,
100 peak_memory: 0,
101 cpu_utilization: 0.0,
102 operations_count: 0,
103 timing_breakdown: HashMap::new(),
104 }
105 }
106 pub fn efficiency_score(&self) -> f64 {
108 if self.avg_duration.as_nanos() == 0 {
109 return 0.0;
110 }
111 let time_efficiency = 1.0 / (self.avg_duration.as_secs_f64() + 1e-9);
112 let memory_efficiency = if self.memory_usage > 0 {
113 self.throughput / (self.memory_usage as f64 / 1024.0 / 1024.0)
114 } else {
115 self.throughput
116 };
117 ((time_efficiency + memory_efficiency) / 2.0 * 100.0).min(100.0)
118 }
119}
120#[derive(Debug, Clone)]
122pub struct BenchmarkSuiteResult {
123 pub name: String,
125 pub measurements: Vec<PerformanceMeasurement>,
127 pub total_duration: Duration,
129 pub avg_efficiency: f64,
131 pub regression_analysis: Option<RegressionAnalysis>,
133 pub scalability_analysis: ScalabilityAnalysis,
135 pub memory_analysis: MemoryEfficiencyAnalysis,
137}
138#[derive(Debug, Clone)]
140pub struct RegressionAnalysis {
141 pub regression_detected: bool,
143 pub regressions: Vec<RegressionResult>,
145 pub improvements: Vec<RegressionResult>,
147 pub overall_change_percent: f64,
149}
150#[derive(Debug, Clone)]
152pub struct RegressionResult {
153 pub benchmark_name: String,
155 pub baseline_duration: Duration,
157 pub current_duration: Duration,
159 pub change_percent: f64,
161 pub significance: RegressionSignificance,
163}
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub enum RegressionSignificance {
167 Negligible,
169 Minor,
171 Moderate,
173 Major,
175 Critical,
177}
178#[derive(Debug, Clone)]
180pub struct ScalabilityAnalysis {
181 pub thread_scalability: f64,
183 pub data_scalability: f64,
185 pub memory_scalability: f64,
187 pub datasize_breakdown: HashMap<usize, f64>,
189 pub n_breakdown: HashMap<usize, f64>,
191}
192#[derive(Debug, Clone)]
194pub struct MemoryEfficiencyAnalysis {
195 pub avg_memory_per_op: f64,
197 pub peak_to_avg_ratio: f64,
199 pub fragmentation_score: f64,
201 pub zero_copy_efficiency: f64,
203 pub bandwidth_utilization: f64,
205}
206pub struct CrossModuleBenchmarkRunner {
208 config: CrossModuleBenchConfig,
209 results: Arc<Mutex<Vec<BenchmarkSuiteResult>>>,
210}
211impl CrossModuleBenchmarkRunner {
212 pub fn new(config: CrossModuleBenchConfig) -> Self {
214 Self {
215 config,
216 results: Arc::new(Mutex::new(Vec::new())),
217 }
218 }
219 pub fn run_benchmarks(&self) -> CoreResult<BenchmarkSuiteResult> {
221 let start_time = Instant::now();
222 let mut measurements = Vec::new();
223 println!("🚀 Running Cross-Module Performance Benchmarks");
224 println!("==============================================");
225 measurements.extend(self.run_data_pipeline_benchmarks()?);
226 measurements.extend(self.run_memory_efficiency_benchmarks()?);
227 measurements.extend(self.run_scalability_benchmarks()?);
228 measurements.extend(self.run_real_world_benchmarks()?);
229 let total_duration = start_time.elapsed();
230 let avg_efficiency = if measurements.is_empty() {
231 0.0
232 } else {
233 measurements
234 .iter()
235 .map(|m| m.efficiency_score())
236 .sum::<f64>()
237 / measurements.len() as f64
238 };
239 let regression_analysis = if self.config.enable_regression_detection {
240 Some(self.analyze_regressions(&measurements)?)
241 } else {
242 None
243 };
244 let scalability_analysis = self.analyze_scalability(&measurements)?;
245 let memory_analysis = self.analyze_memory_efficiency(&measurements)?;
246 let suite_result = BenchmarkSuiteResult {
247 name: "Cross-Module Performance Suite".to_string(),
248 measurements,
249 total_duration,
250 avg_efficiency,
251 regression_analysis,
252 scalability_analysis,
253 memory_analysis,
254 };
255 {
256 let mut results = self.results.lock().map_err(|_| {
257 CoreError::ComputationError(ErrorContext::new("Failed to lock results".to_string()))
258 })?;
259 results.push(suite_result.clone());
260 }
261 Ok(suite_result)
262 }
263 fn run_data_pipeline_benchmarks(&self) -> CoreResult<Vec<PerformanceMeasurement>> {
265 let mut measurements = Vec::new();
266 println!("📊 Running Data Pipeline Benchmarks...");
267 measurements.push(self.benchmark_linalg_stats_pipeline()?);
268 measurements.push(self.benchmark_signal_fft_pipeline()?);
269 measurements.push(self.benchmark_io_processing_pipeline()?);
270 measurements.push(self.benchmark_ml_pipeline()?);
271 Ok(measurements)
272 }
273 fn benchmark_linalg_stats_pipeline(&self) -> CoreResult<PerformanceMeasurement> {
275 let mut measurement = PerformanceMeasurement::new(
276 "linalg_stats_pipeline".to_string(),
277 vec!["scirs2-linalg".to_string(), "scirs2-stats".to_string()],
278 );
279 for &datasize in &self.config.datasizes {
280 let timing_data = self.time_operation(&format!("{datasize}"), || {
281 self.simulate_linalg_stats_workflow(datasize)
282 })?;
283 if datasize == *self.config.datasizes.last().expect("Operation failed") {
284 measurement.datasize = datasize;
285 measurement.avg_duration = timing_data.avg_duration;
286 measurement.min_duration = timing_data.min_duration;
287 measurement.max_duration = timing_data.max_duration;
288 measurement.throughput = timing_data.throughput;
289 measurement.memory_usage = timing_data.memory_usage;
290 measurement.peak_memory = timing_data.peak_memory;
291 measurement.operations_count = timing_data.operations_count;
292 }
293 }
294 Ok(measurement)
295 }
296 fn simulate_linalg_stats_workflow(&self, datasize: usize) -> CoreResult<()> {
298 let matrix_size = (datasize as f64).sqrt() as usize;
299 let matrix_elements = matrix_size * matrix_size;
300 let operations = matrix_size.pow(3);
301 for _ in 0..operations.min(1000000) {
302 let result = 1.23456 * 7.89012 + 3.45678;
303 }
304 let stats_operations = datasize;
305 for _ in 0..stats_operations.min(1000000) {
306 let result = 1.23456_f64.sin() + 7.89012_f64.cos();
307 }
308 Ok(())
309 }
310 fn benchmark_signal_fft_pipeline(&self) -> CoreResult<PerformanceMeasurement> {
312 let mut measurement = PerformanceMeasurement::new(
313 "signal_fft_pipeline".to_string(),
314 vec!["scirs2-signal".to_string(), "scirs2-fft".to_string()],
315 );
316 for &datasize in &self.config.datasizes {
317 let timing_data = self.time_operation(&format!("{datasize}"), || {
318 self.simulate_signal_fft_workflow(datasize)
319 })?;
320 if datasize == *self.config.datasizes.last().expect("Operation failed") {
321 measurement.datasize = datasize;
322 measurement.avg_duration = timing_data.avg_duration;
323 measurement.throughput = timing_data.throughput;
324 measurement.memory_usage = timing_data.memory_usage;
325 measurement.peak_memory = timing_data.peak_memory;
326 measurement.operations_count = timing_data.operations_count;
327 }
328 }
329 Ok(measurement)
330 }
331 fn simulate_signal_fft_workflow(&self, datasize: usize) -> CoreResult<()> {
333 let signal_length = datasize / std::mem::size_of::<f64>();
334 let filter_operations = signal_length.min(1000000);
335 for _ in 0..filter_operations {
336 let result = 1.23456_f64.sin() * 0.78901 + 2.34567_f64.cos();
337 }
338 let fft_operations = (signal_length as f64 * (signal_length as f64).log2()) as usize;
339 for _ in 0..fft_operations.min(1000000) {
340 let result = std::f64::consts::PI * std::f64::consts::E.exp();
341 }
342 Ok(())
343 }
344 fn benchmark_io_processing_pipeline(&self) -> CoreResult<PerformanceMeasurement> {
346 let mut measurement = PerformanceMeasurement::new(
347 "io_processing_pipeline".to_string(),
348 vec!["scirs2-io".to_string(), "scirs2-core".to_string()],
349 );
350 for &datasize in &self.config.datasizes {
351 let timing_data = self.time_operation(&format!("{datasize}"), || {
352 self.simulate_io_processing_workflow(datasize)
353 })?;
354 if datasize == *self.config.datasizes.last().expect("Operation failed") {
355 measurement.datasize = datasize;
356 measurement.avg_duration = timing_data.avg_duration;
357 measurement.throughput = timing_data.throughput;
358 measurement.memory_usage = timing_data.memory_usage;
359 measurement.peak_memory = timing_data.peak_memory;
360 measurement.operations_count = timing_data.operations_count;
361 }
362 }
363 Ok(measurement)
364 }
365 fn simulate_io_processing_workflow(&self, datasize: usize) -> CoreResult<()> {
367 let buffer = vec![0u8; datasize];
368 let mut checksum = 0u64;
369 for &byte in &buffer {
370 checksum = checksum.wrapping_add(byte as u64);
371 }
372 for i in 0..datasize.min(100000) {
373 let value = (0 as f64) / datasize as f64;
374 if !value.is_finite() {
375 return Err(CoreError::ValidationError(ErrorContext::new(
376 "Invalid value".to_string(),
377 )));
378 }
379 }
380 if checksum == u64::MAX {
381 return Err(CoreError::ComputationError(ErrorContext::new(
382 "Unlikely checksum".to_string(),
383 )));
384 }
385 Ok(())
386 }
387 fn benchmark_ml_pipeline(&self) -> CoreResult<PerformanceMeasurement> {
389 let mut measurement = PerformanceMeasurement::new(
390 "ml_pipeline".to_string(),
391 vec!["scirs2-neural".to_string(), "scirs2-optimize".to_string()],
392 );
393 for &datasize in &self.config.datasizes {
394 let timing_data = self.time_operation(&format!("{datasize}"), || {
395 self.simulate_ml_workflow(datasize)
396 })?;
397 if datasize == *self.config.datasizes.last().expect("Operation failed") {
398 measurement.datasize = datasize;
399 measurement.avg_duration = timing_data.avg_duration;
400 measurement.throughput = timing_data.throughput;
401 measurement.memory_usage = timing_data.memory_usage;
402 measurement.peak_memory = timing_data.peak_memory;
403 measurement.operations_count = timing_data.operations_count;
404 }
405 }
406 Ok(measurement)
407 }
408 fn simulate_ml_workflow(&self, datasize: usize) -> CoreResult<()> {
410 let feature_count = (datasize / 1000).max(10);
411 let sample_count = datasize / feature_count;
412 for _ in 0..sample_count.min(10000) {
413 for _ in 0..feature_count.min(1000) {
414 let activation = 1.0 / (1.0 + (-0.5_f64).exp());
415 }
416 }
417 for _ in 0..(feature_count * sample_count).min(100000) {
418 let gradient = 0.01 * 1.23456;
419 }
420 Ok(())
421 }
422 fn run_memory_efficiency_benchmarks(&self) -> CoreResult<Vec<PerformanceMeasurement>> {
424 let mut measurements = Vec::new();
425 println!("🧠 Running Memory Efficiency Benchmarks...");
426 measurements.push(self.benchmark_zero_copy_operations()?);
427 measurements.push(self.benchmark_memory_mapped_operations()?);
428 measurements.push(self.benchmark_out_of_core_operations()?);
429 Ok(measurements)
430 }
431 fn benchmark_zero_copy_operations(&self) -> CoreResult<PerformanceMeasurement> {
433 let mut measurement = PerformanceMeasurement::new(
434 "zero_copy_operations".to_string(),
435 vec!["scirs2-core".to_string()],
436 );
437 for &datasize in &self.config.datasizes {
438 let timing_data = self.time_operation(&format!("{datasize}"), || {
439 self.simulate_zero_copy_workflow(datasize)
440 })?;
441 if datasize == *self.config.datasizes.last().expect("Operation failed") {
442 measurement.datasize = datasize;
443 measurement.avg_duration = timing_data.avg_duration;
444 measurement.throughput = timing_data.throughput;
445 measurement.memory_usage = timing_data.memory_usage;
446 measurement.peak_memory = timing_data.peak_memory;
447 measurement.operations_count = timing_data.operations_count;
448 }
449 }
450 Ok(measurement)
451 }
452 fn simulate_zero_copy_workflow(&self, datasize: usize) -> CoreResult<()> {
454 let buffer = vec![1.0f64; datasize / std::mem::size_of::<f64>()];
455 let chunk_size = buffer.len() / 4;
456 for i in 0..4 {
457 let start = i * chunk_size;
458 let end = ((i + 1) * chunk_size).min(buffer.len());
459 let slice = &buffer[start..end];
460 let mut sum = 0.0;
461 for &value in slice {
462 sum += value;
463 }
464 if sum < 0.0 {
465 return Err(CoreError::ComputationError(ErrorContext::new(
466 "Invalid sum".to_string(),
467 )));
468 }
469 }
470 Ok(())
471 }
472 fn benchmark_memory_mapped_operations(&self) -> CoreResult<PerformanceMeasurement> {
474 let mut measurement = PerformanceMeasurement::new(
475 "memory_mapped_operations".to_string(),
476 vec!["scirs2-core".to_string(), "scirs2-io".to_string()],
477 );
478 println!(" Simulating memory-mapped operations...");
479 for &datasize in &self.config.datasizes {
480 let timing_data = self.time_operation(&format!("{datasize}"), || {
481 self.simulate_mmap_workflow(datasize)
482 })?;
483 if datasize == *self.config.datasizes.last().expect("Operation failed") {
484 measurement.datasize = datasize;
485 measurement.avg_duration = timing_data.avg_duration;
486 measurement.throughput = timing_data.throughput;
487 measurement.memory_usage = timing_data.memory_usage;
488 measurement.peak_memory = timing_data.peak_memory;
489 measurement.operations_count = timing_data.operations_count;
490 }
491 }
492 Ok(measurement)
493 }
494 fn simulate_mmap_workflow(&self, datasize: usize) -> CoreResult<()> {
496 let element_count = datasize / std::mem::size_of::<f64>();
497 let chunk_size = element_count / 16;
498 for chunk_id in 0..16 {
499 let start_idx = chunk_id * chunk_size;
500 let end_idx = ((chunk_id + 1) * chunk_size).min(element_count);
501 for idx in start_idx..end_idx {
502 let value = (idx as f64).sin();
503 if !value.is_finite() {
504 return Err(CoreError::ComputationError(ErrorContext::new(
505 "Invalid computation".to_string(),
506 )));
507 }
508 }
509 }
510 Ok(())
511 }
512 fn benchmark_out_of_core_operations(&self) -> CoreResult<PerformanceMeasurement> {
514 let mut measurement = PerformanceMeasurement::new(
515 "out_of_core_operations".to_string(),
516 vec!["scirs2-core".to_string()],
517 );
518 println!(" Simulating out-of-core operations...");
519 for &datasize in &self.config.datasizes {
520 let timing_data = self.time_operation(&format!("{datasize}"), || {
521 self.simulate_out_of_core_workflow(datasize)
522 })?;
523 if datasize == *self.config.datasizes.last().expect("Operation failed") {
524 measurement.datasize = datasize;
525 measurement.avg_duration = timing_data.avg_duration;
526 measurement.throughput = timing_data.throughput;
527 measurement.memory_usage = timing_data.memory_usage;
528 measurement.peak_memory = timing_data.peak_memory;
529 measurement.operations_count = timing_data.operations_count;
530 }
531 }
532 Ok(measurement)
533 }
534 fn simulate_out_of_core_workflow(&self, datasize: usize) -> CoreResult<()> {
536 let total_elements = datasize / std::mem::size_of::<f64>();
537 let chunk_size = 1024;
538 let num_chunks = total_elements.div_ceil(chunk_size);
539 for chunk_idx in 0..num_chunks {
540 let start = chunk_idx * chunk_size;
541 let end = (start + chunk_size).min(total_elements);
542 let chunk_len = end - start;
543 let chunk_data = vec![1.0f64; chunk_len];
544 let mut sum = 0.0;
545 for &value in &chunk_data {
546 sum += value * value;
547 }
548 if sum < 0.0 {
549 return Err(CoreError::ComputationError(ErrorContext::new(
550 "Invalid computation result".to_string(),
551 )));
552 }
553 }
554 Ok(())
555 }
556 fn run_scalability_benchmarks(&self) -> CoreResult<Vec<PerformanceMeasurement>> {
558 let mut measurements = Vec::new();
559 println!("📈 Running Scalability Benchmarks...");
560 measurements.push(self.benchmark_thread_scalability()?);
561 measurements.push(self.benchmark_datasize_scalability()?);
562 measurements.push(self.benchmark_memory_scalability()?);
563 Ok(measurements)
564 }
565 fn benchmark_thread_scalability(&self) -> CoreResult<PerformanceMeasurement> {
567 let mut measurement = PerformanceMeasurement::new(
568 "thread_scalability".to_string(),
569 vec!["scirs2-core".to_string()],
570 );
571 #[cfg(feature = "parallel")]
572 {
573 for &n in &self.config.ns {
574 let timing_data = self.time_operation(&format!("{n}"), || {
575 self.simulate_scalable_operation(n * 1024)
576 })?;
577 if n == *self.config.ns.last().expect("Operation failed") {
578 measurement.n = n;
579 measurement.avg_duration = timing_data.avg_duration;
580 measurement.throughput = timing_data.throughput;
581 measurement.operations_count = timing_data.operations_count;
582 }
583 }
584 }
585 #[cfg(not(feature = "parallel"))]
586 {
587 measurement.n = 1;
588 measurement.avg_duration = Duration::from_millis(100);
589 measurement.throughput = 1000.0;
590 measurement.operations_count = 1000;
591 }
592 Ok(measurement)
593 }
594 #[cfg(feature = "parallel")]
596 fn count(n: usize) -> CoreResult<()> {
597 let work_items = 100000;
598 let items_per_thread = work_items / n;
599 crate::parallel_ops::ThreadPoolBuilder::new()
600 .num_threads(n)
601 .build()
602 .map_err(|e| CoreError::ComputationError(ErrorContext::new(format!("{e}"))))?
603 .install(|| {
604 (0..n).into_par_iter().try_for_each(|_| {
605 for _ in 0..items_per_thread {
606 let result = 1.23456_f64.sin() + 7.89012_f64.cos();
607 }
608 Ok::<(), CoreError>(())
609 })
610 })?;
611 Ok(())
612 }
613 #[cfg(not(feature = "parallel"))]
615 fn count(n: usize) -> CoreResult<()> {
616 for _ in 0..100000 {
617 let result = 1.23456_f64.sin() + 7.89012_f64.cos();
618 }
619 Ok(())
620 }
621 fn benchmark_datasize_scalability(&self) -> CoreResult<PerformanceMeasurement> {
623 let mut measurement = PerformanceMeasurement::new(
624 "datasize_scalability".to_string(),
625 vec!["scirs2-core".to_string()],
626 );
627 println!(" Testing data size scalability...");
628 let mut scalability_scores = Vec::new();
629 for &datasize in &self.config.datasizes {
630 let timing_data = self.time_operation(&format!("{datasize}"), || {
631 self.simulate_scalable_operation(datasize)
632 })?;
633 let ops_per_byte = timing_data.throughput / datasize as f64;
634 scalability_scores.push(ops_per_byte);
635 if datasize == *self.config.datasizes.last().expect("Operation failed") {
636 measurement.datasize = datasize;
637 measurement.avg_duration = timing_data.avg_duration;
638 measurement.throughput = timing_data.throughput;
639 measurement.memory_usage = timing_data.memory_usage;
640 measurement.peak_memory = timing_data.peak_memory;
641 measurement.operations_count = timing_data.operations_count;
642 }
643 }
644 Ok(measurement)
645 }
646 fn simulate_scalable_operation(&self, datasize: usize) -> CoreResult<()> {
648 let elements = datasize / std::mem::size_of::<f64>();
649 for i in 0..elements.min(1000000) {
650 let value = (0 as f64) / elements as f64;
651 let result = value.sin() + value.cos();
652 }
653 Ok(())
654 }
655 fn benchmark_memory_scalability(&self) -> CoreResult<PerformanceMeasurement> {
657 let mut measurement = PerformanceMeasurement::new(
658 "memory_scalability".to_string(),
659 vec!["scirs2-core".to_string()],
660 );
661 println!(" Testing memory scalability...");
662 for &memory_limit in &self.config.memory_limits {
663 let timing_data = self.time_operation(&format!("{memory_limit}"), || {
664 self.simulate_scalable_operation(memory_limit)
665 })?;
666 if memory_limit == *self.config.memory_limits.last().expect("Operation failed") {
667 measurement.memory_usage = memory_limit;
668 measurement.avg_duration = timing_data.avg_duration;
669 measurement.throughput = timing_data.throughput;
670 measurement.operations_count = timing_data.operations_count;
671 }
672 }
673 Ok(measurement)
674 }
675 fn limit(n: usize) -> CoreResult<()> {
677 let element_count = (n / std::mem::size_of::<f64>()).min(1000000);
678 let buffer = vec![1.0f64; element_count];
679 let mut result = 0.0;
680 for (i, &value) in buffer.iter().enumerate() {
681 result += value * (i as f64).sqrt();
682 }
683 if result < 0.0 {
684 return Err(CoreError::ComputationError(ErrorContext::new(
685 "Invalid result".to_string(),
686 )));
687 }
688 Ok(())
689 }
690 fn run_real_world_benchmarks(&self) -> CoreResult<Vec<PerformanceMeasurement>> {
692 let mut measurements = Vec::new();
693 println!("🌍 Running Real-World Scenario Benchmarks...");
694 measurements.push(self.benchmark_scientific_simulation()?);
695 measurements.push(self.benchmark_data_analysis_pipeline()?);
696 measurements.push(self.benchmark_machine_learning_training()?);
697 Ok(measurements)
698 }
699 fn benchmark_scientific_simulation(&self) -> CoreResult<PerformanceMeasurement> {
701 let mut measurement = PerformanceMeasurement::new(
702 "scientific_simulation".to_string(),
703 vec!["scirs2-linalg".to_string(), "scirs2-integrate".to_string()],
704 );
705 println!(" Running scientific simulation benchmark...");
706 for &datasize in &self.config.datasizes {
707 let timing_data = self.time_operation(&format!("{datasize}"), || {
708 self.simulate_scientific_workflow(datasize)
709 })?;
710 if datasize == *self.config.datasizes.last().expect("Operation failed") {
711 measurement.datasize = datasize;
712 measurement.avg_duration = timing_data.avg_duration;
713 measurement.throughput = timing_data.throughput;
714 measurement.memory_usage = timing_data.memory_usage;
715 measurement.peak_memory = timing_data.peak_memory;
716 measurement.operations_count = timing_data.operations_count;
717 }
718 }
719 Ok(measurement)
720 }
721 fn simulate_scientific_workflow(&self, datasize: usize) -> CoreResult<()> {
723 let grid_size = (datasize as f64).sqrt() as usize;
724 let time_steps = 100;
725 for i in 0..grid_size {
726 for j in 0..grid_size {
727 let x = 0 as f64 / grid_size as f64;
728 let y = j as f64 / grid_size as f64;
729 let initial_value = (x * x + y * y).exp() * (-x * y).sin();
730 }
731 }
732 for _step in 0..time_steps {
733 for i in 1..(grid_size - 1) {
734 for _j in 1..(grid_size - 1) {
735 let dt = 0.01;
736 let dx = 1.0 / grid_size as f64;
737 let laplacian = dt / (dx * dx);
738 }
739 }
740 let matrix_ops = grid_size * grid_size / 100;
741 for _ in 0..matrix_ops {
742 let result = 1.23456_f64.sin() + 0.78901_f64.cos();
743 }
744 }
745 Ok(())
746 }
747 fn benchmark_data_analysis_pipeline(&self) -> CoreResult<PerformanceMeasurement> {
749 let mut measurement = PerformanceMeasurement::new(
750 "data_analysis_pipeline".to_string(),
751 vec![
752 "scirs2-io".to_string(),
753 "scirs2-stats".to_string(),
754 "scirs2-signal".to_string(),
755 ],
756 );
757 println!(" Running data analysis pipeline benchmark...");
758 for &datasize in &self.config.datasizes {
759 let timing_data = self.time_operation(&format!("{datasize}"), || {
760 self.simulate_data_analysis_workflow(datasize)
761 })?;
762 if datasize == *self.config.datasizes.last().expect("Operation failed") {
763 measurement.datasize = datasize;
764 measurement.avg_duration = timing_data.avg_duration;
765 measurement.throughput = timing_data.throughput;
766 measurement.memory_usage = timing_data.memory_usage;
767 measurement.peak_memory = timing_data.peak_memory;
768 measurement.operations_count = timing_data.operations_count;
769 }
770 }
771 Ok(measurement)
772 }
773 fn simulate_data_analysis_workflow(&self, datasize: usize) -> CoreResult<()> {
775 let sample_count = datasize / std::mem::size_of::<f64>();
776 let raw_data = vec![0.0f64; sample_count];
777 let mut processed_data = Vec::with_capacity(sample_count);
778 for (i, &value) in raw_data.iter().enumerate() {
779 let cleaned_value = value + (i as f64 * 0.01).sin();
780 processed_data.push(cleaned_value);
781 }
782 let mut sum = 0.0;
783 let mut sum_squares = 0.0;
784 for &value in &processed_data {
785 sum += value;
786 sum_squares += value * value;
787 }
788 let mean = sum / processed_data.len() as f64;
789 let variance = (sum_squares / processed_data.len() as f64) - (mean * mean);
790 for (i, &value) in processed_data.iter().enumerate() {
791 let freq = 2.0 * std::f64::consts::PI * (i as f64) / sample_count as f64;
792 let filtered = value * freq.cos();
793 }
794 if variance < 0.0 {
795 return Err(CoreError::ComputationError(ErrorContext::new(
796 "Invalid variance".to_string(),
797 )));
798 }
799 Ok(())
800 }
801 fn benchmark_machine_learning_training(&self) -> CoreResult<PerformanceMeasurement> {
803 let mut measurement = PerformanceMeasurement::new(
804 "ml_training".to_string(),
805 vec![
806 "scirs2-neural".to_string(),
807 "scirs2-optimize".to_string(),
808 "scirs2-linalg".to_string(),
809 ],
810 );
811 println!(" Running ML training benchmark...");
812 for &datasize in &self.config.datasizes {
813 let timing_data = self.time_operation(&format!("{datasize}"), || {
814 self.simulate_ml_training_workflow(datasize)
815 })?;
816 if datasize == *self.config.datasizes.last().expect("Operation failed") {
817 measurement.datasize = datasize;
818 measurement.avg_duration = timing_data.avg_duration;
819 measurement.throughput = timing_data.throughput;
820 measurement.memory_usage = timing_data.memory_usage;
821 measurement.peak_memory = timing_data.peak_memory;
822 measurement.operations_count = timing_data.operations_count;
823 }
824 }
825 Ok(measurement)
826 }
827 fn simulate_ml_training_workflow(&self, datasize: usize) -> CoreResult<()> {
829 let batch_size = 32;
830 let feature_dim = 128;
831 let hidden_dim = 256;
832 let numbatches = (datasize / (batch_size * feature_dim)).max(1);
833 let epochs = 10;
834 for _epoch in 0..epochs {
835 for _batch in 0..numbatches {
836 for i in 0..batch_size {
837 for j in 0..hidden_dim {
838 let mut activation = 0.0;
839 for k in 0..feature_dim {
840 let weight = ((i + j + k) as f64) * 0.01;
841 let input = ((i * k) as f64) * 0.001;
842 activation += weight * input;
843 }
844 let output = 1.0 / (1.0 + (-activation).exp());
845 }
846 }
847 for i in 0..hidden_dim {
848 for j in 0..feature_dim {
849 let gradient = ((i + j) as f64) * 0.001;
850 let weight_update = gradient * 0.01;
851 }
852 }
853 let param_count = hidden_dim * feature_dim;
854 for _ in 0..param_count / 1000 {
855 let momentum_update = 0.9 * 0.01 + 0.1 * 0.001;
856 }
857 }
858 }
859 Ok(())
860 }
861 fn time_operation<F>(&self, name: &str, mut operation: F) -> CoreResult<TimingData>
866 where
867 F: FnMut() -> CoreResult<()>,
868 {
869 let mut durations = Vec::new();
870 let mut memory_deltas = Vec::new();
871 for _ in 0..self.config.warmup_iterations {
872 operation()?;
873 }
874 for _ in 0..self.config.iterations {
875 let memory_before = current_process_memory_bytes();
876 let start = Instant::now();
877 operation()?;
878 let duration = start.elapsed();
879 let memory_after = current_process_memory_bytes();
880 durations.push(duration);
881 memory_deltas.push(memory_after.saturating_sub(memory_before));
882 }
883 let total_duration: Duration = durations.iter().sum();
884 let avg_duration = total_duration / durations.len() as u32;
885 let min_duration = *durations.iter().min().expect("Operation failed");
886 let max_duration = *durations.iter().max().expect("Operation failed");
887 let variance = durations
888 .iter()
889 .map(|d| {
890 let diff = d.as_nanos() as i128 - avg_duration.as_nanos() as i128;
891 (diff * diff) as u128
892 })
893 .sum::<u128>()
894 / durations.len() as u128;
895 let std_deviation = Duration::from_nanos((variance as f64).sqrt() as u64);
896 let throughput = if avg_duration.as_secs_f64() > 0.0 {
897 self.config.iterations as f64 / avg_duration.as_secs_f64()
898 } else {
899 0.0
900 };
901 let memory_usage = memory_deltas.iter().sum::<usize>() / memory_deltas.len().max(1);
902 let peak_memory = memory_deltas.iter().copied().max().unwrap_or(0);
903 Ok(TimingData {
904 name: name.to_string(),
905 avg_duration,
906 min_duration,
907 max_duration,
908 std_deviation,
909 throughput,
910 memory_usage,
911 peak_memory,
912 operations_count: self.config.iterations,
913 })
914 }
915 fn analyze_regressions(
926 &self,
927 measurements: &[PerformanceMeasurement],
928 ) -> CoreResult<RegressionAnalysis> {
929 if measurements.is_empty() {
930 return Ok(RegressionAnalysis {
931 regression_detected: false,
932 regressions: Vec::new(),
933 improvements: Vec::new(),
934 overall_change_percent: 0.0,
935 });
936 }
937 let durations_ns: Vec<f64> = measurements
938 .iter()
939 .map(|m| m.avg_duration.as_nanos() as f64)
940 .collect();
941 let n = durations_ns.len() as f64;
942 let mean_ns = durations_ns.iter().sum::<f64>() / n;
943 let variance_ns = durations_ns
944 .iter()
945 .map(|&d| {
946 let diff = d - mean_ns;
947 diff * diff
948 })
949 .sum::<f64>()
950 / n;
951 let std_ns = variance_ns.sqrt();
952 let mut baseline_map: HashMap<String, f64> = HashMap::new();
953 if let Some(ref path) = self.config.baseline_file {
954 if let Ok(contents) = std::fs::read_to_string(path) {
955 for line in contents.lines() {
956 let parts: Vec<&str> = line.splitn(2, ' ').collect();
957 if parts.len() == 2 {
958 if let Ok(nanos) = parts[1].trim().parse::<f64>() {
959 baseline_map.insert(parts[0].trim().to_string(), nanos);
960 }
961 }
962 }
963 }
964 }
965 let mut regressions: Vec<RegressionResult> = Vec::new();
966 let mut improvements: Vec<RegressionResult> = Vec::new();
967 let mut total_change_sum = 0.0_f64;
968 let threshold_factor = self.config.max_regression_percent / 100.0;
969 for m in measurements {
970 let current_ns = m.avg_duration.as_nanos() as f64;
971 let baseline_ns = baseline_map.get(&m.name).copied().unwrap_or(mean_ns);
972 let change_percent = if baseline_ns > 0.0 {
973 (current_ns - baseline_ns) / baseline_ns * 100.0
974 } else {
975 0.0
976 };
977 let z_score = if std_ns > 0.0 {
978 (current_ns - mean_ns) / std_ns
979 } else {
980 0.0
981 };
982 let is_regression = change_percent > threshold_factor * 100.0 || z_score > 2.0;
983 let is_improvement = change_percent < -(threshold_factor * 100.0) || z_score < -2.0;
984 let significance = {
985 let abs_change = change_percent.abs();
986 if abs_change < 5.0 {
987 RegressionSignificance::Negligible
988 } else if abs_change < 15.0 {
989 RegressionSignificance::Minor
990 } else if abs_change < 30.0 {
991 RegressionSignificance::Moderate
992 } else if abs_change < 50.0 {
993 RegressionSignificance::Major
994 } else {
995 RegressionSignificance::Critical
996 }
997 };
998 let result = RegressionResult {
999 benchmark_name: m.name.clone(),
1000 baseline_duration: Duration::from_nanos(baseline_ns.max(0.0) as u64),
1001 current_duration: m.avg_duration,
1002 change_percent,
1003 significance,
1004 };
1005 if is_regression {
1006 regressions.push(result);
1007 } else if is_improvement {
1008 improvements.push(result);
1009 }
1010 total_change_sum += change_percent;
1011 }
1012 let overall_change_percent = total_change_sum / measurements.len() as f64;
1013 let regression_detected = !regressions.is_empty();
1014 Ok(RegressionAnalysis {
1015 regression_detected,
1016 regressions,
1017 improvements,
1018 overall_change_percent,
1019 })
1020 }
1021 fn analyze_scalability(
1023 &self,
1024 measurements: &[PerformanceMeasurement],
1025 ) -> CoreResult<ScalabilityAnalysis> {
1026 let mut datasize_breakdown = HashMap::new();
1027 let mut n_breakdown = HashMap::new();
1028 for measurement in measurements {
1029 if measurement.datasize > 0 {
1030 datasize_breakdown
1031 .insert(measurement.datasize, measurement.efficiency_score() / 100.0);
1032 }
1033 if measurement.n > 0 {
1034 n_breakdown.insert(measurement.n, measurement.efficiency_score() / 100.0);
1035 }
1036 }
1037 let scalability_analysis = ScalabilityAnalysis {
1038 thread_scalability: Self::retained_efficiency_score(&n_breakdown),
1039 data_scalability: Self::retained_efficiency_score(&datasize_breakdown),
1040 memory_scalability: Self::memory_scalability_score(measurements),
1041 datasize_breakdown,
1042 n_breakdown,
1043 };
1044 Ok(scalability_analysis)
1045 }
1046 fn retained_efficiency_score(breakdown: &HashMap<usize, f64>) -> f64 {
1053 let mut keys: Vec<usize> = breakdown.keys().copied().collect();
1054 keys.sort_unstable();
1055 let (Some(&smallest), Some(&largest)) = (keys.first(), keys.last()) else {
1056 return 1.0;
1057 };
1058 if smallest == largest {
1059 return 1.0;
1060 }
1061 let eff_small = breakdown[&smallest];
1062 let eff_large = breakdown[&largest];
1063 if eff_small <= 0.0 {
1064 return if eff_large > 0.0 { 1.0 } else { 0.0 };
1065 }
1066 (eff_large / eff_small).clamp(0.0, 1.0)
1067 }
1068 fn memory_scalability_score(measurements: &[PerformanceMeasurement]) -> f64 {
1075 let mut by_size: Vec<(usize, usize)> = measurements
1076 .iter()
1077 .filter(|m| m.datasize > 0)
1078 .map(|m| (m.datasize, m.memory_usage))
1079 .collect();
1080 by_size.sort_unstable_by_key(|&(size, _)| size);
1081 let (Some(&(small_size, small_mem)), Some(&(large_size, large_mem))) =
1082 (by_size.first(), by_size.last())
1083 else {
1084 return 1.0;
1085 };
1086 if small_size == large_size || small_mem == 0 {
1087 return 1.0;
1088 }
1089 let data_growth = large_size as f64 / small_size as f64;
1090 let mem_growth = large_mem as f64 / small_mem as f64;
1091 if mem_growth <= 0.0 {
1092 return 1.0;
1093 }
1094 (data_growth / mem_growth).clamp(0.0, 1.0)
1095 }
1096 fn analyze_memory_efficiency(
1098 &self,
1099 measurements: &[PerformanceMeasurement],
1100 ) -> CoreResult<MemoryEfficiencyAnalysis> {
1101 let total_operations: usize = measurements.iter().map(|m| m.operations_count).sum();
1102 let total_memory: usize = measurements.iter().map(|m| m.memory_usage).sum();
1103 let avg_memory_per_op = if total_operations > 0 {
1104 total_memory as f64 / total_operations as f64
1105 } else {
1106 0.0
1107 };
1108 let avg_memory_usage = if measurements.is_empty() {
1109 0.0
1110 } else {
1111 total_memory as f64 / measurements.len() as f64
1112 };
1113 let overall_peak_memory = measurements
1114 .iter()
1115 .map(|m| m.peak_memory)
1116 .max()
1117 .unwrap_or(0);
1118 let peak_to_avg_ratio = if avg_memory_usage > 0.0 {
1119 overall_peak_memory as f64 / avg_memory_usage
1120 } else {
1121 1.0
1122 };
1123 let per_benchmark_peak_ratios: Vec<f64> = measurements
1124 .iter()
1125 .filter(|m| m.memory_usage > 0)
1126 .map(|m| (m.peak_memory as f64 / m.memory_usage as f64 - 1.0).max(0.0))
1127 .collect();
1128 let fragmentation_score = if per_benchmark_peak_ratios.is_empty() {
1129 0.0
1130 } else {
1131 (per_benchmark_peak_ratios.iter().sum::<f64>() / per_benchmark_peak_ratios.len() as f64)
1132 .min(1.0)
1133 };
1134 let cross_module: Vec<&PerformanceMeasurement> = measurements
1135 .iter()
1136 .filter(|m| m.modules.len() > 1)
1137 .collect();
1138 let zero_copy_efficiency = if cross_module.is_empty() {
1139 1.0
1140 } else {
1141 let efficient = cross_module
1142 .iter()
1143 .filter(|m| (m.memory_usage as f64) <= avg_memory_usage.max(1.0))
1144 .count();
1145 efficient as f64 / cross_module.len() as f64
1146 };
1147 let max_throughput = measurements
1148 .iter()
1149 .map(|m| m.throughput)
1150 .fold(0.0_f64, f64::max);
1151 let avg_throughput = if measurements.is_empty() {
1152 0.0
1153 } else {
1154 measurements.iter().map(|m| m.throughput).sum::<f64>() / measurements.len() as f64
1155 };
1156 let bandwidth_utilization = if max_throughput > 0.0 {
1157 (avg_throughput / max_throughput).clamp(0.0, 1.0)
1158 } else {
1159 0.0
1160 };
1161 let memory_analysis = MemoryEfficiencyAnalysis {
1162 avg_memory_per_op,
1163 peak_to_avg_ratio,
1164 fragmentation_score,
1165 zero_copy_efficiency,
1166 bandwidth_utilization,
1167 };
1168 Ok(memory_analysis)
1169 }
1170 pub fn generate_benchmark_report(&self) -> CoreResult<String> {
1172 let results = self.results.lock().map_err(|_| {
1173 CoreError::ComputationError(ErrorContext::new("Failed to lock results".to_string()))
1174 })?;
1175 if results.is_empty() {
1176 return Ok("No benchmark results available.".to_string());
1177 }
1178 let latest = &results[results.len() - 1];
1179 let mut report = String::new();
1180 report.push_str("# SciRS2 Cross-Module Performance Benchmark Report\n\n");
1181 #[cfg(feature = "serialization")]
1182 {
1183 report.push_str(&format!(
1184 "**Generated**: {}\n",
1185 chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC")
1186 ));
1187 }
1188 #[cfg(not(feature = "serialization"))]
1189 {
1190 report.push_str("**Generated**: [timestamp unavailable]\n");
1191 }
1192 report.push_str(&format!("**Suite**: {}\n", latest.name));
1193 report.push_str(&format!(
1194 "**Total Duration**: {:?}\n",
1195 latest.total_duration
1196 ));
1197 report.push_str(&format!(
1198 "**Average Efficiency**: {:.1}%\n\n",
1199 latest.avg_efficiency
1200 ));
1201 report.push_str("## Executive Summary\n\n");
1202 report.push_str(&format!(
1203 "- **Benchmarks Executed**: {}\n",
1204 latest.measurements.len()
1205 ));
1206 report.push_str(&format!(
1207 "- **Overall Efficiency**: {:.1}%\n",
1208 latest.avg_efficiency
1209 ));
1210 report.push_str(&format!(
1211 "- **Thread Scalability**: {:.1}%\n",
1212 latest.scalability_analysis.thread_scalability * 100.0
1213 ));
1214 report.push_str(&format!(
1215 "- **Memory Efficiency**: {:.1}%\n",
1216 (1.0 - latest.memory_analysis.fragmentation_score) * 100.0
1217 ));
1218 report.push_str("\n## Benchmark Results\n\n");
1219 for measurement in &latest.measurements {
1220 report.push_str(&format!(
1221 "### {} ({})\n",
1222 measurement.name,
1223 measurement.modules.join(" + ")
1224 ));
1225 report.push_str(&format!(
1226 "- **Data Size**: {} bytes\n",
1227 measurement.datasize
1228 ));
1229 report.push_str(&format!(
1230 "- **Average Time**: {:?}\n",
1231 measurement.avg_duration
1232 ));
1233 report.push_str(&format!(
1234 "- **Throughput**: {:.2} ops/sec\n",
1235 measurement.throughput
1236 ));
1237 report.push_str(&format!(
1238 "- **Memory Usage**: {} MB\n",
1239 measurement.memory_usage / (1024 * 1024)
1240 ));
1241 report.push_str(&format!(
1242 "- **Efficiency Score**: {:.1}%\n",
1243 measurement.efficiency_score()
1244 ));
1245 report.push('\n');
1246 }
1247 report.push_str("## Scalability Analysis\n\n");
1248 report.push_str(&format!(
1249 "- **Thread Scalability**: {:.1}%\n",
1250 latest.scalability_analysis.thread_scalability * 100.0
1251 ));
1252 report.push_str(&format!(
1253 "- **Data Size Scalability**: {:.1}%\n",
1254 latest.scalability_analysis.data_scalability * 100.0
1255 ));
1256 report.push_str(&format!(
1257 "- **Memory Scalability**: {:.1}%\n",
1258 latest.scalability_analysis.memory_scalability * 100.0
1259 ));
1260 report.push_str("\n## Memory Efficiency Analysis\n\n");
1261 report.push_str(&format!(
1262 "- **Average Memory per Operation**: {:.2} bytes\n",
1263 latest.memory_analysis.avg_memory_per_op
1264 ));
1265 report.push_str(&format!(
1266 "- **Peak to Average Ratio**: {:.2}\n",
1267 latest.memory_analysis.peak_to_avg_ratio
1268 ));
1269 report.push_str(&format!(
1270 "- **Fragmentation Score**: {:.3} (lower is better)\n",
1271 latest.memory_analysis.fragmentation_score
1272 ));
1273 report.push_str(&format!(
1274 "- **Zero-Copy Efficiency**: {:.1}%\n",
1275 latest.memory_analysis.zero_copy_efficiency * 100.0
1276 ));
1277 if let Some(regression) = &latest.regression_analysis {
1278 report.push_str("\n## Regression Analysis\n\n");
1279 if regression.regression_detected {
1280 report.push_str("⚠️ **Performance regressions detected**\n\n");
1281 for reg in ®ression.regressions {
1282 report.push_str(&format!(
1283 "- **{}**: {:.2}% regression\n",
1284 reg.benchmark_name, reg.change_percent
1285 ));
1286 }
1287 } else {
1288 report.push_str("✅ **No significant regressions detected**\n");
1289 }
1290 }
1291 report.push_str("\n## Recommendations\n\n");
1292 if latest.avg_efficiency >= 80.0 {
1293 report.push_str(
1294 "✅ **Excellent Performance**: The cross-module performance is very good.\n",
1295 );
1296 } else if latest.avg_efficiency >= 60.0 {
1297 report.push_str(
1298 "⚠️ **Good Performance**: Consider optimizing bottlenecks identified above.\n",
1299 );
1300 } else {
1301 report
1302 .push_str(
1303 "❌ **Performance Issues**: Significant optimization work needed before 1.0 release.\n",
1304 );
1305 }
1306 Ok(report)
1307 }
1308}
1309#[derive(Debug)]
1311struct TimingData {
1312 name: String,
1313 avg_duration: Duration,
1314 min_duration: Duration,
1315 max_duration: Duration,
1316 std_deviation: Duration,
1317 throughput: f64,
1318 memory_usage: usize,
1319 peak_memory: usize,
1320 operations_count: usize,
1321}
1322impl fmt::Display for RegressionSignificance {
1323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1324 match self {
1325 RegressionSignificance::Negligible => write!(f, "Negligible"),
1326 RegressionSignificance::Minor => write!(f, "Minor"),
1327 RegressionSignificance::Moderate => write!(f, "Moderate"),
1328 RegressionSignificance::Major => write!(f, "Major"),
1329 RegressionSignificance::Critical => write!(f, "Critical"),
1330 }
1331 }
1332}
1333#[allow(dead_code)]
1335pub fn create_default_benchmark_suite() -> CoreResult<CrossModuleBenchmarkRunner> {
1336 let config = CrossModuleBenchConfig::default();
1337 Ok(CrossModuleBenchmarkRunner::new(config))
1338}
1339#[allow(dead_code)]
1341pub fn run_quick_benchmarks() -> CoreResult<BenchmarkSuiteResult> {
1342 let config = CrossModuleBenchConfig {
1343 iterations: 2,
1344 warmup_iterations: 1,
1345 datasizes: vec![1024],
1346 ns: vec![1],
1347 memory_limits: vec![64 * 1024 * 1024],
1348 enable_profiling: false,
1349 enable_regression_detection: false,
1350 timeout: Duration::from_secs(30),
1351 ..Default::default()
1352 };
1353 let runner = CrossModuleBenchmarkRunner::new(config);
1354 runner.run_benchmarks()
1355}
1356
1357#[cfg(test)]
1358mod tests;