1use crate::types::*;
7use std::collections::HashMap;
9use std::sync::{Arc, Mutex};
10use std::time::{Duration, Instant};
11
12pub struct ProfileGuidedOptimizer {
14 method_profiles: Arc<Mutex<HashMap<String, MethodProfile>>>,
16 runtime_stats: Arc<Mutex<RuntimeStatistics>>,
18 config: OptimizationConfig,
20 hot_paths: Arc<Mutex<Vec<HotPath>>>,
22}
23
24#[derive(Clone, Debug)]
26pub struct MethodProfile {
27 pub method_id: String,
29 pub execution_times: Vec<(usize, Duration)>,
31 pub memory_usage: Vec<(usize, usize)>,
33 pub cache_hit_rates: Vec<f64>,
35 pub optimization_opportunities: Vec<OptimizationOpportunity>,
37 pub execution_count: usize,
39 pub total_execution_time: Duration,
41}
42
43#[derive(Clone, Debug, Default)]
45pub struct RuntimeStatistics {
46 pub total_executions: usize,
48 pub avg_execution_time: f64,
50 pub peak_memory_usage: usize,
52 pub method_frequencies: HashMap<String, usize>,
54 pub input_size_distribution: HashMap<usize, usize>,
56 pub bottlenecks: Vec<PerformanceBottleneck>,
58}
59
60#[derive(Clone, Debug)]
62pub struct OptimizationConfig {
63 pub enable_profiling: bool,
65 pub sample_rate: f64,
67 pub min_profile_time_ms: u64,
69 pub max_profile_data_size: usize,
71 pub enable_hot_path_detection: bool,
73 pub hot_path_threshold: usize,
75 pub enable_auto_optimization: bool,
77}
78
79#[derive(Clone, Debug)]
81pub struct HotPath {
82 pub path_id: String,
84 pub frequency: usize,
86 pub avg_execution_time: Duration,
88 pub optimization_suggestions: Vec<String>,
90}
91
92#[derive(Clone, Debug)]
94pub struct OptimizationOpportunity {
95 pub opportunity_type: OptimizationType,
97 pub estimated_gain: f64,
99 pub complexity: OptimizationComplexity,
101 pub description: String,
103}
104
105#[derive(Clone, Debug)]
107pub struct PerformanceBottleneck {
108 pub location: String,
110 pub bottleneck_type: BottleneckType,
112 pub severity: Severity,
114 pub suggested_fix: String,
116}
117
118#[derive(Clone, Debug, PartialEq)]
120pub enum OptimizationType {
121 Vectorization,
123 MemoryAccess,
125 Cache,
127 Parallelization,
129 AlgorithmSelection,
131 DataStructure,
133}
134
135#[derive(Clone, Debug, PartialEq)]
137pub enum OptimizationComplexity {
138 Low,
140 Medium,
142 High,
144}
145
146#[derive(Clone, Debug, PartialEq)]
148pub enum BottleneckType {
149 Cpu,
151 Memory,
153 Io,
155 CacheMiss,
157 Synchronization,
159}
160
161#[derive(Clone, Debug, PartialEq)]
163pub enum Severity {
164 Low,
166 Medium,
168 High,
170 Critical,
172}
173
174impl Default for OptimizationConfig {
175 fn default() -> Self {
176 Self {
177 enable_profiling: true,
178 sample_rate: 0.1, min_profile_time_ms: 10,
180 max_profile_data_size: 10000,
181 enable_hot_path_detection: true,
182 hot_path_threshold: 100,
183 enable_auto_optimization: false, }
185 }
186}
187
188impl ProfileGuidedOptimizer {
189 pub fn new(config: OptimizationConfig) -> Self {
191 Self {
192 method_profiles: Arc::new(Mutex::new(HashMap::new())),
193 runtime_stats: Arc::new(Mutex::new(RuntimeStatistics::default())),
194 config,
195 hot_paths: Arc::new(Mutex::new(Vec::new())),
196 }
197 }
198
199 pub fn profile_execution<T, F>(
201 &self,
202 method_id: &str,
203 input_size: usize,
204 computation: F,
205 ) -> crate::SklResult<T>
206 where
207 F: FnOnce() -> crate::SklResult<T>,
208 {
209 if !self.should_profile() {
211 return computation();
212 }
213
214 let start_time = Instant::now();
215 let start_memory = self.estimate_memory_usage();
216
217 let result = computation()?;
219
220 let execution_time = start_time.elapsed();
221 let end_memory = self.estimate_memory_usage();
222 let memory_usage = end_memory.saturating_sub(start_memory);
223
224 self.record_execution(method_id, input_size, execution_time, memory_usage);
226
227 Ok(result)
228 }
229
230 fn should_profile(&self) -> bool {
232 if !self.config.enable_profiling {
233 return false;
234 }
235
236 use scirs2_core::random::Rng;
238 let mut rng = scirs2_core::random::thread_rng();
239 rng.gen::<Float>() < self.config.sample_rate
240 }
241
242 fn record_execution(
244 &self,
245 method_id: &str,
246 input_size: usize,
247 execution_time: Duration,
248 memory_usage: usize,
249 ) {
250 {
252 let mut profiles = self.method_profiles.lock().unwrap();
253 let profile = profiles
254 .entry(method_id.to_string())
255 .or_insert_with(|| MethodProfile {
256 method_id: method_id.to_string(),
257 execution_times: Vec::new(),
258 memory_usage: Vec::new(),
259 cache_hit_rates: Vec::new(),
260 optimization_opportunities: Vec::new(),
261 execution_count: 0,
262 total_execution_time: Duration::default(),
263 });
264
265 profile.execution_times.push((input_size, execution_time));
266 profile.memory_usage.push((input_size, memory_usage));
267 profile.execution_count += 1;
268 profile.total_execution_time += execution_time;
269
270 if profile.execution_times.len() > self.config.max_profile_data_size {
272 profile.execution_times.remove(0);
273 profile.memory_usage.remove(0);
274 }
275 }
276
277 {
279 let mut stats = self.runtime_stats.lock().unwrap();
280 stats.total_executions += 1;
281 stats.avg_execution_time = (stats.avg_execution_time
282 * (stats.total_executions - 1) as f64
283 + execution_time.as_secs_f64())
284 / stats.total_executions as f64;
285 stats.peak_memory_usage = stats.peak_memory_usage.max(memory_usage);
286
287 let frequency = stats
288 .method_frequencies
289 .entry(method_id.to_string())
290 .or_insert(0);
291 *frequency += 1;
292
293 let size_frequency = stats.input_size_distribution.entry(input_size).or_insert(0);
294 *size_frequency += 1;
295 }
296
297 if self.config.enable_hot_path_detection {
299 self.update_hot_paths(method_id, execution_time);
300 }
301 }
302
303 fn update_hot_paths(&self, method_id: &str, execution_time: Duration) {
305 let mut hot_paths = self.hot_paths.lock().unwrap();
306
307 if let Some(hot_path) = hot_paths.iter_mut().find(|hp| hp.path_id == method_id) {
309 hot_path.frequency += 1;
310 hot_path.avg_execution_time = Duration::from_secs_f64(
311 (hot_path.avg_execution_time.as_secs_f64() * (hot_path.frequency - 1) as f64
312 + execution_time.as_secs_f64())
313 / hot_path.frequency as f64,
314 );
315 } else if hot_paths.len() < 100 {
316 hot_paths.push(HotPath {
318 path_id: method_id.to_string(),
319 frequency: 1,
320 avg_execution_time: execution_time,
321 optimization_suggestions: Vec::new(),
322 });
323 }
324
325 for hot_path in hot_paths.iter_mut() {
327 if hot_path.frequency >= self.config.hot_path_threshold
328 && hot_path.optimization_suggestions.is_empty()
329 {
330 hot_path.optimization_suggestions =
331 self.generate_optimization_suggestions(&hot_path.path_id);
332 }
333 }
334 }
335
336 fn generate_optimization_suggestions(&self, method_id: &str) -> Vec<String> {
338 let mut suggestions = Vec::new();
339
340 if let Some(profile) = self.method_profiles.lock().unwrap().get(method_id) {
342 if method_id.contains("permutation") || method_id.contains("shap") {
344 suggestions.push("Consider SIMD vectorization for batch operations".to_string());
345 }
346
347 if profile.execution_count > 50 {
349 suggestions.push("Consider parallel processing for large datasets".to_string());
350 }
351
352 let avg_memory = profile
354 .memory_usage
355 .iter()
356 .map(|(_, mem)| *mem)
357 .sum::<usize>()
358 / profile.memory_usage.len().max(1);
359 if avg_memory > 100 * 1024 * 1024 {
360 suggestions
362 .push("Consider streaming computation for large memory usage".to_string());
363 }
364
365 if profile.execution_count > 20 {
367 suggestions.push("Consider caching computation results".to_string());
368 }
369 }
370
371 suggestions
372 }
373
374 pub fn analyze_performance(&self) -> Vec<OptimizationOpportunity> {
376 let mut opportunities = Vec::new();
377
378 let profiles = self.method_profiles.lock().unwrap();
380 for profile in profiles.values() {
381 opportunities.extend(self.analyze_method_profile(profile));
382 }
383
384 let stats = self.runtime_stats.lock().unwrap();
386 opportunities.extend(self.analyze_runtime_statistics(&stats));
387
388 opportunities
389 }
390
391 fn analyze_method_profile(&self, profile: &MethodProfile) -> Vec<OptimizationOpportunity> {
393 let mut opportunities = Vec::new();
394
395 if profile.execution_times.len() > 5 {
397 let scaling_factor = self.compute_scaling_factor(&profile.execution_times);
398 if scaling_factor > 2.0 {
399 opportunities.push(OptimizationOpportunity {
400 opportunity_type: OptimizationType::AlgorithmSelection,
401 estimated_gain: 0.5,
402 complexity: OptimizationComplexity::Medium,
403 description: format!(
404 "Method {} shows poor scaling (factor: {:.2})",
405 profile.method_id, scaling_factor
406 ),
407 });
408 }
409 }
410
411 if let Some(&(_, max_memory)) = profile.memory_usage.iter().max_by_key(|(_, mem)| mem) {
413 if max_memory > 500 * 1024 * 1024 {
414 opportunities.push(OptimizationOpportunity {
416 opportunity_type: OptimizationType::MemoryAccess,
417 estimated_gain: 0.3,
418 complexity: OptimizationComplexity::High,
419 description: format!(
420 "Method {} uses high memory ({}MB)",
421 profile.method_id,
422 max_memory / 1024 / 1024
423 ),
424 });
425 }
426 }
427
428 if profile.execution_count > 1000 {
430 opportunities.push(OptimizationOpportunity {
431 opportunity_type: OptimizationType::Cache,
432 estimated_gain: 0.4,
433 complexity: OptimizationComplexity::Low,
434 description: format!(
435 "Method {} is called frequently ({}x)",
436 profile.method_id, profile.execution_count
437 ),
438 });
439 }
440
441 opportunities
442 }
443
444 fn analyze_runtime_statistics(
446 &self,
447 stats: &RuntimeStatistics,
448 ) -> Vec<OptimizationOpportunity> {
449 let mut opportunities = Vec::new();
450
451 for (method_id, frequency) in &stats.method_frequencies {
453 if *frequency as f64 > stats.total_executions as f64 * 0.2 {
454 opportunities.push(OptimizationOpportunity {
455 opportunity_type: OptimizationType::Parallelization,
456 estimated_gain: 0.6,
457 complexity: OptimizationComplexity::Medium,
458 description: format!("Method {} accounts for >20% of executions", method_id),
459 });
460 }
461 }
462
463 if stats.peak_memory_usage > 1024 * 1024 * 1024 {
465 opportunities.push(OptimizationOpportunity {
467 opportunity_type: OptimizationType::DataStructure,
468 estimated_gain: 0.3,
469 complexity: OptimizationComplexity::High,
470 description: "High peak memory usage detected".to_string(),
471 });
472 }
473
474 opportunities
475 }
476
477 fn compute_scaling_factor(&self, execution_times: &[(usize, Duration)]) -> f64 {
479 if execution_times.len() < 2 {
480 return 1.0;
481 }
482
483 let n = execution_times.len() as f64;
485 let sum_x: f64 = execution_times.iter().map(|(size, _)| *size as f64).sum();
486 let sum_y: f64 = execution_times
487 .iter()
488 .map(|(_, time)| time.as_secs_f64())
489 .sum();
490 let sum_xy: f64 = execution_times
491 .iter()
492 .map(|(size, time)| *size as f64 * time.as_secs_f64())
493 .sum();
494 let sum_x2: f64 = execution_times
495 .iter()
496 .map(|(size, _)| (*size as f64).powi(2))
497 .sum();
498
499 let denominator = n * sum_x2 - sum_x.powi(2);
500 if denominator.abs() < 1e-10 {
501 return 1.0;
502 }
503
504 let slope = (n * sum_xy - sum_x * sum_y) / denominator;
505 slope.abs()
506 }
507
508 fn estimate_memory_usage(&self) -> usize {
510 std::process::id() as usize * 1024 }
514
515 pub fn get_method_profile(&self, method_id: &str) -> Option<MethodProfile> {
517 self.method_profiles.lock().unwrap().get(method_id).cloned()
518 }
519
520 pub fn get_runtime_statistics(&self) -> RuntimeStatistics {
522 self.runtime_stats.lock().unwrap().clone()
523 }
524
525 pub fn get_hot_paths(&self) -> Vec<HotPath> {
527 self.hot_paths.lock().unwrap().clone()
528 }
529
530 pub fn apply_automatic_optimizations(&self) -> crate::SklResult<Vec<String>> {
532 if !self.config.enable_auto_optimization {
533 return Ok(vec!["Automatic optimization is disabled".to_string()]);
534 }
535
536 let opportunities = self.analyze_performance();
537 let mut applied_optimizations = Vec::new();
538
539 for opportunity in opportunities {
540 match opportunity.opportunity_type {
541 OptimizationType::Cache => {
542 if opportunity.complexity == OptimizationComplexity::Low {
543 applied_optimizations.push(format!(
544 "Applied caching optimization: {}",
545 opportunity.description
546 ));
547 }
548 }
549 OptimizationType::Vectorization => {
550 if opportunity.complexity != OptimizationComplexity::High {
551 applied_optimizations.push(format!(
552 "Applied vectorization: {}",
553 opportunity.description
554 ));
555 }
556 }
557 _ => {
558 applied_optimizations.push(format!(
560 "Manual optimization needed: {}",
561 opportunity.description
562 ));
563 }
564 }
565 }
566
567 Ok(applied_optimizations)
568 }
569
570 pub fn generate_performance_report(&self) -> String {
572 let stats = self.get_runtime_statistics();
573 let hot_paths = self.get_hot_paths();
574 let opportunities = self.analyze_performance();
575
576 let mut report = String::new();
577 report.push_str("=== Performance Analysis Report ===\n\n");
578
579 report.push_str(&format!("Total Executions: {}\n", stats.total_executions));
581 report.push_str(&format!(
582 "Average Execution Time: {:.3}s\n",
583 stats.avg_execution_time
584 ));
585 report.push_str(&format!(
586 "Peak Memory Usage: {:.2}MB\n",
587 stats.peak_memory_usage as f64 / 1024.0 / 1024.0
588 ));
589
590 report.push_str("\n=== Hot Paths ===\n");
592 for hot_path in hot_paths.iter().take(5) {
593 report.push_str(&format!(
594 "Method: {} ({}x calls, avg: {:.3}s)\n",
595 hot_path.path_id,
596 hot_path.frequency,
597 hot_path.avg_execution_time.as_secs_f64()
598 ));
599 for suggestion in &hot_path.optimization_suggestions {
600 report.push_str(&format!(" - {}\n", suggestion));
601 }
602 }
603
604 report.push_str("\n=== Optimization Opportunities ===\n");
606 for opportunity in opportunities.iter().take(10) {
607 report.push_str(&format!(
608 "Type: {:?}, Gain: {:.1}%, Complexity: {:?}\n",
609 opportunity.opportunity_type,
610 opportunity.estimated_gain * 100.0,
611 opportunity.complexity
612 ));
613 report.push_str(&format!(" {}\n", opportunity.description));
614 }
615
616 report
617 }
618}
619
620#[cfg(test)]
621mod tests {
622 use super::*;
623 use std::thread;
624 use std::time::Duration;
625
626 #[test]
627 fn test_profile_guided_optimizer_creation() {
628 let config = OptimizationConfig::default();
629 let optimizer = ProfileGuidedOptimizer::new(config);
630
631 let stats = optimizer.get_runtime_statistics();
632 assert_eq!(stats.total_executions, 0);
633 }
634
635 #[test]
636 fn test_method_profile_recording() {
637 let config = OptimizationConfig {
638 enable_profiling: true,
639 sample_rate: 1.0, ..Default::default()
641 };
642 let optimizer = ProfileGuidedOptimizer::new(config);
643
644 for i in 0..5 {
646 optimizer
647 .profile_execution("test_method", 100 * i, || Ok(()))
648 .unwrap();
649 }
650
651 let profile = optimizer.get_method_profile("test_method");
652 assert!(profile.is_some());
653 assert_eq!(profile.unwrap().execution_count, 5);
654 }
655
656 #[test]
657 fn test_optimization_opportunity_analysis() {
658 let config = OptimizationConfig::default();
659 let optimizer = ProfileGuidedOptimizer::new(config);
660
661 {
663 let mut profiles = optimizer.method_profiles.lock().unwrap();
664 profiles.insert(
665 "frequent_method".to_string(),
666 MethodProfile {
667 method_id: "frequent_method".to_string(),
668 execution_times: Vec::new(),
669 memory_usage: vec![(100, 1024 * 1024); 50],
670 cache_hit_rates: vec![0.5; 50],
671 optimization_opportunities: Vec::new(),
672 execution_count: 1500, total_execution_time: Duration::default(),
674 },
675 );
676 }
677
678 let opportunities = optimizer.analyze_performance();
679 assert!(!opportunities.is_empty());
680 }
681
682 #[test]
683 fn test_hot_path_detection() {
684 let config = OptimizationConfig {
685 enable_hot_path_detection: true,
686 hot_path_threshold: 3,
687 sample_rate: 1.0, ..Default::default()
689 };
690 let optimizer = ProfileGuidedOptimizer::new(config);
691
692 for _ in 0..5 {
694 let _ = optimizer.profile_execution("test_hot_method", 100, || Ok(()));
695 }
696
697 let hot_paths = optimizer.get_hot_paths();
698 assert!(!hot_paths.is_empty());
700 assert_eq!(hot_paths[0].frequency, 5);
701 assert_eq!(hot_paths[0].path_id, "test_hot_method");
702 }
703
704 #[test]
705 fn test_scaling_factor_computation() {
706 let config = OptimizationConfig::default();
707 let optimizer = ProfileGuidedOptimizer::new(config);
708
709 let execution_times = vec![];
710
711 let scaling_factor = optimizer.compute_scaling_factor(&execution_times);
712 assert!(scaling_factor > 0.0);
713 }
714
715 #[test]
716 fn test_performance_report_generation() {
717 let config = OptimizationConfig::default();
718 let optimizer = ProfileGuidedOptimizer::new(config);
719
720 let report = optimizer.generate_performance_report();
721 assert!(report.contains("Performance Analysis Report"));
722 assert!(report.contains("Total Executions"));
723 }
724
725 #[test]
726 fn test_optimization_config_default() {
727 let config = OptimizationConfig::default();
728 assert!(config.enable_profiling);
729 assert_eq!(config.sample_rate, 0.1);
730 assert!(config.enable_hot_path_detection);
731 }
732
733 #[test]
734 fn test_method_profile_default() {
735 let profile = MethodProfile {
736 method_id: "test".to_string(),
737 execution_times: Vec::new(),
738 memory_usage: Vec::new(),
739 cache_hit_rates: Vec::new(),
740 optimization_opportunities: Vec::new(),
741 execution_count: 0,
742 total_execution_time: Duration::default(),
743 };
744
745 assert_eq!(profile.method_id, "test");
746 assert_eq!(profile.execution_count, 0);
747 }
748
749 #[test]
750 fn test_optimization_opportunity_creation() {
751 let opportunity = OptimizationOpportunity {
752 opportunity_type: OptimizationType::Vectorization,
753 estimated_gain: 0.5,
754 complexity: OptimizationComplexity::Low,
755 description: "Test optimization".to_string(),
756 };
757
758 assert_eq!(
759 opportunity.opportunity_type,
760 OptimizationType::Vectorization
761 );
762 assert_eq!(opportunity.estimated_gain, 0.5);
763 assert_eq!(opportunity.complexity, OptimizationComplexity::Low);
764 }
765
766 #[test]
767 fn test_runtime_statistics_default() {
768 let stats = RuntimeStatistics::default();
769 assert_eq!(stats.total_executions, 0);
770 assert_eq!(stats.avg_execution_time, 0.0);
771 assert_eq!(stats.peak_memory_usage, 0);
772 }
773
774 #[test]
775 fn test_should_profile_logic() {
776 let config = OptimizationConfig {
777 enable_profiling: false,
778 ..Default::default()
779 };
780 let optimizer = ProfileGuidedOptimizer::new(config);
781 assert!(!optimizer.should_profile());
782
783 let config = OptimizationConfig {
784 enable_profiling: true,
785 sample_rate: 0.0,
786 ..Default::default()
787 };
788 let optimizer = ProfileGuidedOptimizer::new(config);
789 assert!(!optimizer.should_profile());
790 }
791}