sklears_inspection/
profiling.rs

1//! Profile-guided optimization for explanation algorithms
2//!
3//! This module provides profiling and optimization capabilities to improve
4//! performance based on actual usage patterns and runtime characteristics.
5
6use crate::types::*;
7// ✅ SciRS2 Policy Compliant Import
8use std::collections::HashMap;
9use std::sync::{Arc, Mutex};
10use std::time::{Duration, Instant};
11
12/// Profile-guided optimization manager
13pub struct ProfileGuidedOptimizer {
14    /// Performance profiles for different methods
15    method_profiles: Arc<Mutex<HashMap<String, MethodProfile>>>,
16    /// Runtime statistics
17    runtime_stats: Arc<Mutex<RuntimeStatistics>>,
18    /// Optimization configuration
19    config: OptimizationConfig,
20    /// Hot paths identified through profiling
21    hot_paths: Arc<Mutex<Vec<HotPath>>>,
22}
23
24/// Performance profile for a specific method
25#[derive(Clone, Debug)]
26pub struct MethodProfile {
27    /// Method identifier
28    pub method_id: String,
29    /// Execution times for different input sizes
30    pub execution_times: Vec<(usize, Duration)>,
31    /// Memory usage patterns
32    pub memory_usage: Vec<(usize, usize)>,
33    /// Cache hit rates
34    pub cache_hit_rates: Vec<f64>,
35    /// Optimization opportunities
36    pub optimization_opportunities: Vec<OptimizationOpportunity>,
37    /// Number of executions
38    pub execution_count: usize,
39    /// Total execution time
40    pub total_execution_time: Duration,
41}
42
43/// Runtime statistics collector
44#[derive(Clone, Debug, Default)]
45pub struct RuntimeStatistics {
46    /// Total executions
47    pub total_executions: usize,
48    /// Average execution time
49    pub avg_execution_time: f64,
50    /// Peak memory usage
51    pub peak_memory_usage: usize,
52    /// Method call frequency
53    pub method_frequencies: HashMap<String, usize>,
54    /// Input size distribution
55    pub input_size_distribution: HashMap<usize, usize>,
56    /// Performance bottlenecks
57    pub bottlenecks: Vec<PerformanceBottleneck>,
58}
59
60/// Configuration for profile-guided optimization
61#[derive(Clone, Debug)]
62pub struct OptimizationConfig {
63    /// Enable profiling
64    pub enable_profiling: bool,
65    /// Profiling sample rate (0.0 to 1.0)
66    pub sample_rate: f64,
67    /// Minimum execution time to profile
68    pub min_profile_time_ms: u64,
69    /// Maximum profile data size
70    pub max_profile_data_size: usize,
71    /// Enable hot path detection
72    pub enable_hot_path_detection: bool,
73    /// Hot path threshold (execution frequency)
74    pub hot_path_threshold: usize,
75    /// Enable automatic optimization
76    pub enable_auto_optimization: bool,
77}
78
79/// Hot path in execution
80#[derive(Clone, Debug)]
81pub struct HotPath {
82    /// Path identifier
83    pub path_id: String,
84    /// Execution frequency
85    pub frequency: usize,
86    /// Average execution time
87    pub avg_execution_time: Duration,
88    /// Optimization suggestions
89    pub optimization_suggestions: Vec<String>,
90}
91
92/// Optimization opportunity
93#[derive(Clone, Debug)]
94pub struct OptimizationOpportunity {
95    /// Opportunity type
96    pub opportunity_type: OptimizationType,
97    /// Estimated performance gain
98    pub estimated_gain: f64,
99    /// Implementation complexity
100    pub complexity: OptimizationComplexity,
101    /// Description
102    pub description: String,
103}
104
105/// Performance bottleneck
106#[derive(Clone, Debug)]
107pub struct PerformanceBottleneck {
108    /// Location of bottleneck
109    pub location: String,
110    /// Type of bottleneck
111    pub bottleneck_type: BottleneckType,
112    /// Impact severity
113    pub severity: Severity,
114    /// Suggested fix
115    pub suggested_fix: String,
116}
117
118/// Types of optimizations
119#[derive(Clone, Debug, PartialEq)]
120pub enum OptimizationType {
121    /// Vectorization optimization
122    Vectorization,
123    /// Memory access optimization
124    MemoryAccess,
125    /// Cache optimization
126    Cache,
127    /// Parallel processing
128    Parallelization,
129    /// Algorithm selection
130    AlgorithmSelection,
131    /// Data structure optimization
132    DataStructure,
133}
134
135/// Optimization complexity levels
136#[derive(Clone, Debug, PartialEq)]
137pub enum OptimizationComplexity {
138    /// Low complexity (easy to implement)
139    Low,
140    /// Medium complexity
141    Medium,
142    /// High complexity (significant changes required)
143    High,
144}
145
146/// Types of performance bottlenecks
147#[derive(Clone, Debug, PartialEq)]
148pub enum BottleneckType {
149    /// CPU-bound bottleneck
150    Cpu,
151    /// Memory-bound bottleneck
152    Memory,
153    /// I/O-bound bottleneck
154    Io,
155    /// Cache miss bottleneck
156    CacheMiss,
157    /// Synchronization bottleneck
158    Synchronization,
159}
160
161/// Severity levels
162#[derive(Clone, Debug, PartialEq)]
163pub enum Severity {
164    /// Low impact
165    Low,
166    /// Medium impact
167    Medium,
168    /// High impact
169    High,
170    /// Critical impact
171    Critical,
172}
173
174impl Default for OptimizationConfig {
175    fn default() -> Self {
176        Self {
177            enable_profiling: true,
178            sample_rate: 0.1, // Profile 10% of executions
179            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, // Manual optimization by default
184        }
185    }
186}
187
188impl ProfileGuidedOptimizer {
189    /// Create a new profile-guided optimizer
190    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    /// Profile a method execution
200    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        // Check if we should profile this execution
210        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        // Execute computation
218        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        // Record profile data
225        self.record_execution(method_id, input_size, execution_time, memory_usage);
226
227        Ok(result)
228    }
229
230    /// Check if we should profile this execution
231    fn should_profile(&self) -> bool {
232        if !self.config.enable_profiling {
233            return false;
234        }
235
236        // Sample based on configured rate
237        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    /// Record execution data
243    fn record_execution(
244        &self,
245        method_id: &str,
246        input_size: usize,
247        execution_time: Duration,
248        memory_usage: usize,
249    ) {
250        // Update method profile
251        {
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            // Limit profile data size
271            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        // Update runtime statistics
278        {
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        // Update hot paths
298        if self.config.enable_hot_path_detection {
299            self.update_hot_paths(method_id, execution_time);
300        }
301    }
302
303    /// Update hot path detection
304    fn update_hot_paths(&self, method_id: &str, execution_time: Duration) {
305        let mut hot_paths = self.hot_paths.lock().unwrap();
306
307        // Find existing hot path or create new one
308        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            // Limit hot paths
317            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        // Generate optimization suggestions for frequently used paths
326        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    /// Generate optimization suggestions for a method
337    fn generate_optimization_suggestions(&self, method_id: &str) -> Vec<String> {
338        let mut suggestions = Vec::new();
339
340        // Analyze method profile
341        if let Some(profile) = self.method_profiles.lock().unwrap().get(method_id) {
342            // Check for vectorization opportunities
343            if method_id.contains("permutation") || method_id.contains("shap") {
344                suggestions.push("Consider SIMD vectorization for batch operations".to_string());
345            }
346
347            // Check for parallelization opportunities
348            if profile.execution_count > 50 {
349                suggestions.push("Consider parallel processing for large datasets".to_string());
350            }
351
352            // Check for memory optimization opportunities
353            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                // > 100MB
361                suggestions
362                    .push("Consider streaming computation for large memory usage".to_string());
363            }
364
365            // Check for caching opportunities
366            if profile.execution_count > 20 {
367                suggestions.push("Consider caching computation results".to_string());
368            }
369        }
370
371        suggestions
372    }
373
374    /// Analyze performance and generate optimization opportunities
375    pub fn analyze_performance(&self) -> Vec<OptimizationOpportunity> {
376        let mut opportunities = Vec::new();
377
378        // Analyze method profiles
379        let profiles = self.method_profiles.lock().unwrap();
380        for profile in profiles.values() {
381            opportunities.extend(self.analyze_method_profile(profile));
382        }
383
384        // Analyze runtime statistics
385        let stats = self.runtime_stats.lock().unwrap();
386        opportunities.extend(self.analyze_runtime_statistics(&stats));
387
388        opportunities
389    }
390
391    /// Analyze a specific method profile
392    fn analyze_method_profile(&self, profile: &MethodProfile) -> Vec<OptimizationOpportunity> {
393        let mut opportunities = Vec::new();
394
395        // Check execution time scaling
396        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        // Check memory usage
412        if let Some(&(_, max_memory)) = profile.memory_usage.iter().max_by_key(|(_, mem)| mem) {
413            if max_memory > 500 * 1024 * 1024 {
414                // > 500MB
415                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        // Check execution frequency
429        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    /// Analyze runtime statistics
445    fn analyze_runtime_statistics(
446        &self,
447        stats: &RuntimeStatistics,
448    ) -> Vec<OptimizationOpportunity> {
449        let mut opportunities = Vec::new();
450
451        // Check for hot methods
452        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        // Check memory usage
464        if stats.peak_memory_usage > 1024 * 1024 * 1024 {
465            // > 1GB
466            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    /// Compute scaling factor for execution times
478    fn compute_scaling_factor(&self, execution_times: &[(usize, Duration)]) -> f64 {
479        if execution_times.len() < 2 {
480            return 1.0;
481        }
482
483        // Simple linear regression to estimate scaling
484        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    /// Estimate current memory usage
509    fn estimate_memory_usage(&self) -> usize {
510        // Simple estimation - in a real implementation, you'd use system APIs
511        // or more sophisticated memory tracking
512        std::process::id() as usize * 1024 // Placeholder
513    }
514
515    /// Get method profile
516    pub fn get_method_profile(&self, method_id: &str) -> Option<MethodProfile> {
517        self.method_profiles.lock().unwrap().get(method_id).cloned()
518    }
519
520    /// Get runtime statistics
521    pub fn get_runtime_statistics(&self) -> RuntimeStatistics {
522        self.runtime_stats.lock().unwrap().clone()
523    }
524
525    /// Get hot paths
526    pub fn get_hot_paths(&self) -> Vec<HotPath> {
527        self.hot_paths.lock().unwrap().clone()
528    }
529
530    /// Apply automatic optimizations
531    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                    // Other optimizations require manual implementation
559                    applied_optimizations.push(format!(
560                        "Manual optimization needed: {}",
561                        opportunity.description
562                    ));
563                }
564            }
565        }
566
567        Ok(applied_optimizations)
568    }
569
570    /// Generate performance report
571    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        // Runtime statistics
580        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        // Hot paths
591        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        // Optimization opportunities
605        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, // Profile all executions
640            ..Default::default()
641        };
642        let optimizer = ProfileGuidedOptimizer::new(config);
643
644        // Profile some executions
645        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        // Create a method profile with many executions
662        {
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, // High frequency
673                    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, // Profile all executions for testing
688            ..Default::default()
689        };
690        let optimizer = ProfileGuidedOptimizer::new(config);
691
692        // Execute method multiple times
693        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        // Should have detected the hot path
699        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}