Skip to main content

sklears_utils/
profile_guided_optimization.rs

1//! Profile-guided optimization utilities
2//!
3//! This module provides utilities for profile-guided optimization including performance profiling,
4//! hotspot detection, optimization hints, and automatic optimization recommendations for ML workloads.
5
6use std::collections::HashMap;
7use std::sync::{Arc, Mutex, RwLock};
8use std::time::{Duration, Instant};
9
10/// Performance profile data
11#[derive(Debug, Clone)]
12pub struct PerformanceProfile {
13    pub function_profiles: HashMap<String, FunctionProfile>,
14    pub loop_profiles: HashMap<String, LoopProfile>,
15    pub memory_access_patterns: HashMap<String, MemoryAccessPattern>,
16    pub branch_predictions: HashMap<String, BranchProfile>,
17    pub cache_statistics: CacheStatistics,
18    pub instruction_mix: InstructionMix,
19    pub profiling_duration: Duration,
20    pub total_samples: u64,
21}
22
23/// Function-level performance profile
24#[derive(Debug, Clone)]
25pub struct FunctionProfile {
26    pub name: String,
27    pub total_time: Duration,
28    pub self_time: Duration,
29    pub call_count: u64,
30    pub avg_time_per_call: Duration,
31    pub max_time_per_call: Duration,
32    pub min_time_per_call: Duration,
33    pub cpu_cycles: u64,
34    pub cache_misses: u64,
35    pub branch_misses: u64,
36    pub hotness_score: f64,
37    pub optimization_opportunities: Vec<OptimizationOpportunity>,
38}
39
40/// Loop-level performance profile
41#[derive(Debug, Clone)]
42pub struct LoopProfile {
43    pub loop_id: String,
44    pub location: String,
45    pub iteration_count: u64,
46    pub total_time: Duration,
47    pub avg_time_per_iteration: Duration,
48    pub vectorization_efficiency: f64,
49    pub dependency_chains: Vec<DependencyChain>,
50    pub memory_access_stride: i64,
51    pub loop_carried_dependencies: u32,
52    pub optimization_potential: f64,
53}
54
55/// Memory access pattern analysis
56#[derive(Debug, Clone)]
57pub struct MemoryAccessPattern {
58    pub function_name: String,
59    pub access_type: MemoryAccessType,
60    pub access_frequency: u64,
61    pub cache_hit_rate: f64,
62    pub average_latency: Duration,
63    pub stride_pattern: StridePattern,
64    pub prefetch_effectiveness: f64,
65    pub numa_locality: f64,
66}
67
68/// Branch prediction profile
69#[derive(Debug, Clone)]
70pub struct BranchProfile {
71    pub branch_id: String,
72    pub location: String,
73    pub taken_count: u64,
74    pub not_taken_count: u64,
75    pub prediction_accuracy: f64,
76    pub misprediction_penalty: Duration,
77    pub branch_type: BranchType,
78}
79
80/// Cache performance statistics
81#[derive(Debug, Clone)]
82pub struct CacheStatistics {
83    pub l1_hit_rate: f64,
84    pub l2_hit_rate: f64,
85    pub l3_hit_rate: f64,
86    pub tlb_hit_rate: f64,
87    pub cache_line_utilization: f64,
88    pub false_sharing_incidents: u64,
89    pub prefetch_accuracy: f64,
90}
91
92/// Instruction mix analysis
93#[derive(Debug, Clone)]
94pub struct InstructionMix {
95    pub integer_ops: u64,
96    pub floating_point_ops: u64,
97    pub vector_ops: u64,
98    pub memory_ops: u64,
99    pub branch_ops: u64,
100    pub simd_utilization: f64,
101    pub parallel_efficiency: f64,
102}
103
104/// Optimization opportunity
105#[derive(Debug, Clone)]
106pub struct OptimizationOpportunity {
107    pub opportunity_type: OptimizationType,
108    pub description: String,
109    pub potential_speedup: f64,
110    pub implementation_effort: ImplementationEffort,
111    pub confidence: f64,
112    pub code_location: String,
113    pub suggested_actions: Vec<String>,
114}
115
116/// Dependency chain in loops
117#[derive(Debug, Clone)]
118pub struct DependencyChain {
119    pub chain_id: String,
120    pub length: u32,
121    pub critical_path_time: Duration,
122    pub parallelization_potential: f64,
123}
124
125/// Memory access types
126#[derive(Debug, Clone, PartialEq)]
127pub enum MemoryAccessType {
128    Sequential,
129    Random,
130    Strided,
131    Gather,
132    Scatter,
133}
134
135/// Stride patterns
136#[derive(Debug, Clone)]
137pub struct StridePattern {
138    pub primary_stride: i64,
139    pub secondary_stride: Option<i64>,
140    pub regularity: f64,
141    pub predictability: f64,
142}
143
144/// Branch types
145#[derive(Debug, Clone, PartialEq)]
146pub enum BranchType {
147    Conditional,
148    Indirect,
149    Return,
150    Call,
151    Loop,
152}
153
154/// Optimization types
155#[derive(Debug, Clone, PartialEq)]
156pub enum OptimizationType {
157    Vectorization,
158    LoopUnrolling,
159    FunctionInlining,
160    MemoryPrefetching,
161    BranchElimination,
162    CacheOptimization,
163    Parallelization,
164    AlgorithmicImprovement,
165}
166
167/// Implementation effort levels
168#[derive(Debug, Clone, PartialEq)]
169pub enum ImplementationEffort {
170    Low,
171    Medium,
172    High,
173    VeryHigh,
174}
175
176/// Profile-guided optimizer
177pub struct ProfileGuidedOptimizer {
178    profiles: Arc<RwLock<HashMap<String, PerformanceProfile>>>,
179    optimization_rules: Vec<OptimizationRule>,
180    #[allow(dead_code)]
181    performance_targets: PerformanceTargets,
182    profiler_config: ProfilerConfig,
183    optimization_history: Arc<Mutex<Vec<OptimizationApplication>>>,
184}
185
186/// Optimization rule
187#[derive(Debug, Clone)]
188pub struct OptimizationRule {
189    pub name: String,
190    pub trigger_condition: TriggerCondition,
191    pub optimization_type: OptimizationType,
192    pub implementation: String,
193    pub expected_benefit: f64,
194    pub risk_level: RiskLevel,
195}
196
197/// Trigger condition for optimization
198#[derive(Debug, Clone)]
199pub struct TriggerCondition {
200    pub min_hotness_score: f64,
201    pub min_call_frequency: u64,
202    pub max_cache_miss_rate: f64,
203    pub min_loop_iterations: u64,
204    pub function_name_patterns: Vec<String>,
205}
206
207/// Performance targets
208#[derive(Debug, Clone)]
209pub struct PerformanceTargets {
210    pub target_speedup: f64,
211    pub max_memory_increase: f64,
212    pub max_compilation_time: Duration,
213    pub stability_requirement: f64,
214}
215
216/// Profiler configuration
217#[derive(Debug, Clone)]
218pub struct ProfilerConfig {
219    pub sampling_frequency: u64,
220    pub enable_instruction_profiling: bool,
221    pub enable_memory_profiling: bool,
222    pub enable_cache_profiling: bool,
223    pub enable_branch_profiling: bool,
224    pub profiling_duration: Duration,
225}
226
227/// Optimization application record
228#[derive(Debug, Clone)]
229pub struct OptimizationApplication {
230    pub timestamp: Instant,
231    pub rule_name: String,
232    pub target_function: String,
233    pub optimization_type: OptimizationType,
234    pub measured_speedup: Option<f64>,
235    pub success: bool,
236    pub notes: String,
237}
238
239/// Risk levels for optimizations
240#[derive(Debug, Clone, PartialEq)]
241pub enum RiskLevel {
242    Low,
243    Medium,
244    High,
245    Experimental,
246}
247
248impl ProfileGuidedOptimizer {
249    /// Create new profile-guided optimizer
250    pub fn new(config: ProfilerConfig, targets: PerformanceTargets) -> Self {
251        let mut optimizer = Self {
252            profiles: Arc::new(RwLock::new(HashMap::new())),
253            optimization_rules: Vec::new(),
254            performance_targets: targets,
255            profiler_config: config,
256            optimization_history: Arc::new(Mutex::new(Vec::new())),
257        };
258
259        optimizer.initialize_default_rules();
260        optimizer
261    }
262
263    /// Initialize default optimization rules
264    fn initialize_default_rules(&mut self) {
265        self.optimization_rules = vec![
266            OptimizationRule {
267                name: "Hot Function Inlining".to_string(),
268                trigger_condition: TriggerCondition {
269                    min_hotness_score: 0.8,
270                    min_call_frequency: 1000,
271                    max_cache_miss_rate: 1.0,
272                    min_loop_iterations: 0,
273                    function_name_patterns: vec![".*_hot.*".to_string()],
274                },
275                optimization_type: OptimizationType::FunctionInlining,
276                implementation: "#[inline(always)]".to_string(),
277                expected_benefit: 1.15,
278                risk_level: RiskLevel::Low,
279            },
280            OptimizationRule {
281                name: "Loop Vectorization".to_string(),
282                trigger_condition: TriggerCondition {
283                    min_hotness_score: 0.6,
284                    min_call_frequency: 0,
285                    max_cache_miss_rate: 1.0,
286                    min_loop_iterations: 100,
287                    function_name_patterns: vec![".*_vectorizable.*".to_string()],
288                },
289                optimization_type: OptimizationType::Vectorization,
290                implementation: "SIMD optimization".to_string(),
291                expected_benefit: 2.0,
292                risk_level: RiskLevel::Medium,
293            },
294            OptimizationRule {
295                name: "Memory Prefetching".to_string(),
296                trigger_condition: TriggerCondition {
297                    min_hotness_score: 0.5,
298                    min_call_frequency: 0,
299                    max_cache_miss_rate: 0.1,
300                    min_loop_iterations: 0,
301                    function_name_patterns: vec![".*_memory_intensive.*".to_string()],
302                },
303                optimization_type: OptimizationType::MemoryPrefetching,
304                implementation: "Software prefetching".to_string(),
305                expected_benefit: 1.3,
306                risk_level: RiskLevel::Medium,
307            },
308            OptimizationRule {
309                name: "Loop Unrolling".to_string(),
310                trigger_condition: TriggerCondition {
311                    min_hotness_score: 0.7,
312                    min_call_frequency: 0,
313                    max_cache_miss_rate: 1.0,
314                    min_loop_iterations: 10,
315                    function_name_patterns: vec![".*_tight_loop.*".to_string()],
316                },
317                optimization_type: OptimizationType::LoopUnrolling,
318                implementation: "Unroll factor 4".to_string(),
319                expected_benefit: 1.25,
320                risk_level: RiskLevel::Low,
321            },
322        ];
323    }
324
325    /// Add custom optimization rule
326    pub fn add_optimization_rule(&mut self, rule: OptimizationRule) {
327        self.optimization_rules.push(rule);
328    }
329
330    /// Collect performance profile
331    pub fn collect_profile(&self, program_name: &str) -> Result<PerformanceProfile, ProfileError> {
332        // Mock profile collection (in real implementation, this would use hardware counters)
333        let mock_profile = PerformanceProfile {
334            function_profiles: self.generate_mock_function_profiles(),
335            loop_profiles: self.generate_mock_loop_profiles(),
336            memory_access_patterns: self.generate_mock_memory_patterns(),
337            branch_predictions: self.generate_mock_branch_profiles(),
338            cache_statistics: CacheStatistics {
339                l1_hit_rate: 0.95,
340                l2_hit_rate: 0.85,
341                l3_hit_rate: 0.70,
342                tlb_hit_rate: 0.98,
343                cache_line_utilization: 0.75,
344                false_sharing_incidents: 5,
345                prefetch_accuracy: 0.80,
346            },
347            instruction_mix: InstructionMix {
348                integer_ops: 1_000_000,
349                floating_point_ops: 500_000,
350                vector_ops: 100_000,
351                memory_ops: 200_000,
352                branch_ops: 150_000,
353                simd_utilization: 0.60,
354                parallel_efficiency: 0.75,
355            },
356            profiling_duration: self.profiler_config.profiling_duration,
357            total_samples: 1_000_000,
358        };
359
360        self.profiles
361            .write()
362            .expect("operation should succeed")
363            .insert(program_name.to_string(), mock_profile.clone());
364        Ok(mock_profile)
365    }
366
367    /// Generate mock function profiles
368    fn generate_mock_function_profiles(&self) -> HashMap<String, FunctionProfile> {
369        let mut profiles = HashMap::new();
370
371        profiles.insert(
372            "matrix_multiply".to_string(),
373            FunctionProfile {
374                name: "matrix_multiply".to_string(),
375                total_time: Duration::from_millis(500),
376                self_time: Duration::from_millis(450),
377                call_count: 1000,
378                avg_time_per_call: Duration::from_micros(500),
379                max_time_per_call: Duration::from_millis(2),
380                min_time_per_call: Duration::from_micros(100),
381                cpu_cycles: 1_000_000,
382                cache_misses: 5000,
383                branch_misses: 100,
384                hotness_score: 0.9,
385                optimization_opportunities: vec![OptimizationOpportunity {
386                    opportunity_type: OptimizationType::Vectorization,
387                    description: "Loop can be vectorized for SIMD".to_string(),
388                    potential_speedup: 2.5,
389                    implementation_effort: ImplementationEffort::Medium,
390                    confidence: 0.85,
391                    code_location: "matrix_multiply.rs:45".to_string(),
392                    suggested_actions: vec![
393                        "Use SIMD intrinsics".to_string(),
394                        "Enable auto-vectorization".to_string(),
395                    ],
396                }],
397            },
398        );
399
400        profiles.insert(
401            "activation_function".to_string(),
402            FunctionProfile {
403                name: "activation_function".to_string(),
404                total_time: Duration::from_millis(200),
405                self_time: Duration::from_millis(180),
406                call_count: 10_000,
407                avg_time_per_call: Duration::from_micros(20),
408                max_time_per_call: Duration::from_micros(100),
409                min_time_per_call: Duration::from_micros(5),
410                cpu_cycles: 400_000,
411                cache_misses: 1000,
412                branch_misses: 50,
413                hotness_score: 0.7,
414                optimization_opportunities: vec![OptimizationOpportunity {
415                    opportunity_type: OptimizationType::FunctionInlining,
416                    description: "Small function called frequently".to_string(),
417                    potential_speedup: 1.15,
418                    implementation_effort: ImplementationEffort::Low,
419                    confidence: 0.95,
420                    code_location: "activation.rs:12".to_string(),
421                    suggested_actions: vec!["Add inline attribute".to_string()],
422                }],
423            },
424        );
425
426        profiles
427    }
428
429    /// Generate mock loop profiles
430    fn generate_mock_loop_profiles(&self) -> HashMap<String, LoopProfile> {
431        let mut profiles = HashMap::new();
432
433        profiles.insert(
434            "training_loop".to_string(),
435            LoopProfile {
436                loop_id: "training_loop".to_string(),
437                location: "train.rs:100".to_string(),
438                iteration_count: 1000,
439                total_time: Duration::from_millis(1000),
440                avg_time_per_iteration: Duration::from_millis(1),
441                vectorization_efficiency: 0.4,
442                dependency_chains: vec![DependencyChain {
443                    chain_id: "weight_update".to_string(),
444                    length: 3,
445                    critical_path_time: Duration::from_micros(100),
446                    parallelization_potential: 0.8,
447                }],
448                memory_access_stride: 8,
449                loop_carried_dependencies: 1,
450                optimization_potential: 0.6,
451            },
452        );
453
454        profiles
455    }
456
457    /// Generate mock memory access patterns
458    fn generate_mock_memory_patterns(&self) -> HashMap<String, MemoryAccessPattern> {
459        let mut patterns = HashMap::new();
460
461        patterns.insert(
462            "data_loading".to_string(),
463            MemoryAccessPattern {
464                function_name: "data_loading".to_string(),
465                access_type: MemoryAccessType::Sequential,
466                access_frequency: 10_000,
467                cache_hit_rate: 0.85,
468                average_latency: Duration::from_nanos(50),
469                stride_pattern: StridePattern {
470                    primary_stride: 8,
471                    secondary_stride: None,
472                    regularity: 0.95,
473                    predictability: 0.90,
474                },
475                prefetch_effectiveness: 0.75,
476                numa_locality: 0.80,
477            },
478        );
479
480        patterns
481    }
482
483    /// Generate mock branch profiles
484    fn generate_mock_branch_profiles(&self) -> HashMap<String, BranchProfile> {
485        let mut profiles = HashMap::new();
486
487        profiles.insert(
488            "convergence_check".to_string(),
489            BranchProfile {
490                branch_id: "convergence_check".to_string(),
491                location: "optimizer.rs:200".to_string(),
492                taken_count: 950,
493                not_taken_count: 50,
494                prediction_accuracy: 0.95,
495                misprediction_penalty: Duration::from_nanos(20),
496                branch_type: BranchType::Conditional,
497            },
498        );
499
500        profiles
501    }
502
503    /// Analyze profiles and generate optimization recommendations
504    pub fn analyze_and_recommend(
505        &self,
506        program_name: &str,
507    ) -> Result<Vec<OptimizationRecommendation>, ProfileError> {
508        let profiles = self.profiles.read().expect("operation should succeed");
509        let profile = profiles
510            .get(program_name)
511            .ok_or(ProfileError::ProfileNotFound)?;
512
513        let mut recommendations = Vec::new();
514
515        // Analyze function profiles
516        for func_profile in profile.function_profiles.values() {
517            for rule in &self.optimization_rules {
518                if self.matches_trigger_condition(&rule.trigger_condition, func_profile) {
519                    recommendations.push(OptimizationRecommendation {
520                        rule_name: rule.name.clone(),
521                        target_function: func_profile.name.clone(),
522                        optimization_type: rule.optimization_type.clone(),
523                        expected_speedup: rule.expected_benefit,
524                        risk_level: rule.risk_level.clone(),
525                        implementation: rule.implementation.clone(),
526                        priority: self.calculate_priority(func_profile, rule),
527                        estimated_effort: ImplementationEffort::Medium,
528                        confidence: 0.8,
529                    });
530                }
531            }
532        }
533
534        // Sort by priority
535        recommendations.sort_by(|a, b| {
536            b.priority
537                .partial_cmp(&a.priority)
538                .unwrap_or(std::cmp::Ordering::Equal)
539        });
540
541        Ok(recommendations)
542    }
543
544    /// Check if function matches trigger condition
545    fn matches_trigger_condition(
546        &self,
547        condition: &TriggerCondition,
548        profile: &FunctionProfile,
549    ) -> bool {
550        profile.hotness_score >= condition.min_hotness_score
551            && profile.call_count >= condition.min_call_frequency
552            && (profile.cache_misses as f64 / profile.call_count as f64)
553                <= condition.max_cache_miss_rate
554    }
555
556    /// Calculate optimization priority
557    fn calculate_priority(&self, profile: &FunctionProfile, rule: &OptimizationRule) -> f64 {
558        let hotness_factor = profile.hotness_score;
559        let benefit_factor = rule.expected_benefit - 1.0; // Convert to gain
560        let risk_factor = match rule.risk_level {
561            RiskLevel::Low => 1.0,
562            RiskLevel::Medium => 0.8,
563            RiskLevel::High => 0.6,
564            RiskLevel::Experimental => 0.4,
565        };
566
567        hotness_factor * benefit_factor * risk_factor
568    }
569
570    /// Apply optimization recommendation
571    pub fn apply_optimization(
572        &self,
573        recommendation: &OptimizationRecommendation,
574    ) -> Result<(), ProfileError> {
575        // Mock optimization application
576        let application = OptimizationApplication {
577            timestamp: Instant::now(),
578            rule_name: recommendation.rule_name.clone(),
579            target_function: recommendation.target_function.clone(),
580            optimization_type: recommendation.optimization_type.clone(),
581            measured_speedup: Some(recommendation.expected_speedup * 0.9), // Slightly lower than expected
582            success: true,
583            notes: format!(
584                "Applied {} to {}",
585                recommendation.implementation, recommendation.target_function
586            ),
587        };
588
589        self.optimization_history
590            .lock()
591            .expect("operation should succeed")
592            .push(application);
593        Ok(())
594    }
595
596    /// Get optimization history
597    pub fn get_optimization_history(&self) -> Vec<OptimizationApplication> {
598        self.optimization_history
599            .lock()
600            .expect("operation should succeed")
601            .clone()
602    }
603
604    /// Calculate overall performance gain
605    pub fn calculate_performance_gain(&self) -> f64 {
606        let history = self
607            .optimization_history
608            .lock()
609            .expect("operation should succeed");
610        let successful_optimizations: Vec<_> = history
611            .iter()
612            .filter(|app| app.success && app.measured_speedup.is_some())
613            .collect();
614
615        if successful_optimizations.is_empty() {
616            return 1.0;
617        }
618
619        // Compound the speedups
620        successful_optimizations
621            .iter()
622            .map(|app| app.measured_speedup.expect("operation should succeed"))
623            .fold(1.0, |acc, speedup| acc * speedup)
624    }
625
626    /// Generate optimization report
627    pub fn generate_report(&self, program_name: &str) -> Result<OptimizationReport, ProfileError> {
628        let profiles = self.profiles.read().expect("operation should succeed");
629        let profile = profiles
630            .get(program_name)
631            .ok_or(ProfileError::ProfileNotFound)?;
632
633        let recommendations = self.analyze_and_recommend(program_name)?;
634        let history = self.get_optimization_history();
635        let performance_gain = self.calculate_performance_gain();
636
637        let potential_further_gains = recommendations
638            .iter()
639            .map(|r| r.expected_speedup - 1.0)
640            .sum::<f64>();
641
642        Ok(OptimizationReport {
643            program_name: program_name.to_string(),
644            profile_summary: ProfileSummary {
645                total_functions: profile.function_profiles.len(),
646                hot_functions: profile
647                    .function_profiles
648                    .values()
649                    .filter(|f| f.hotness_score > 0.5)
650                    .count(),
651                total_loops: profile.loop_profiles.len(),
652                vectorizable_loops: profile
653                    .loop_profiles
654                    .values()
655                    .filter(|l| l.vectorization_efficiency < 0.5)
656                    .count(),
657                cache_efficiency: profile.cache_statistics.l1_hit_rate,
658                simd_utilization: profile.instruction_mix.simd_utilization,
659            },
660            recommendations,
661            applied_optimizations: history,
662            overall_performance_gain: performance_gain,
663            potential_further_gains,
664            report_timestamp: Instant::now(),
665        })
666    }
667
668    /// Reset profiling data
669    pub fn reset(&self) {
670        self.profiles
671            .write()
672            .expect("operation should succeed")
673            .clear();
674        self.optimization_history
675            .lock()
676            .expect("operation should succeed")
677            .clear();
678    }
679}
680
681/// Optimization recommendation
682#[derive(Debug, Clone)]
683pub struct OptimizationRecommendation {
684    pub rule_name: String,
685    pub target_function: String,
686    pub optimization_type: OptimizationType,
687    pub expected_speedup: f64,
688    pub risk_level: RiskLevel,
689    pub implementation: String,
690    pub priority: f64,
691    pub estimated_effort: ImplementationEffort,
692    pub confidence: f64,
693}
694
695/// Profile summary
696#[derive(Debug, Clone)]
697pub struct ProfileSummary {
698    pub total_functions: usize,
699    pub hot_functions: usize,
700    pub total_loops: usize,
701    pub vectorizable_loops: usize,
702    pub cache_efficiency: f64,
703    pub simd_utilization: f64,
704}
705
706/// Optimization report
707#[derive(Debug, Clone)]
708pub struct OptimizationReport {
709    pub program_name: String,
710    pub profile_summary: ProfileSummary,
711    pub recommendations: Vec<OptimizationRecommendation>,
712    pub applied_optimizations: Vec<OptimizationApplication>,
713    pub overall_performance_gain: f64,
714    pub potential_further_gains: f64,
715    pub report_timestamp: Instant,
716}
717
718/// Profile-guided optimization errors
719#[derive(Debug, thiserror::Error)]
720pub enum ProfileError {
721    #[error("Profile not found")]
722    ProfileNotFound,
723    #[error("Profiling failed: {0}")]
724    ProfilingFailed(String),
725    #[error("Optimization failed: {0}")]
726    OptimizationFailed(String),
727    #[error("Invalid configuration: {0}")]
728    InvalidConfiguration(String),
729}
730
731impl Default for ProfilerConfig {
732    fn default() -> Self {
733        Self {
734            sampling_frequency: 1000,
735            enable_instruction_profiling: true,
736            enable_memory_profiling: true,
737            enable_cache_profiling: true,
738            enable_branch_profiling: true,
739            profiling_duration: Duration::from_secs(10),
740        }
741    }
742}
743
744impl Default for PerformanceTargets {
745    fn default() -> Self {
746        Self {
747            target_speedup: 1.5,
748            max_memory_increase: 0.1,
749            max_compilation_time: Duration::from_secs(60),
750            stability_requirement: 0.95,
751        }
752    }
753}
754
755#[allow(non_snake_case)]
756#[cfg(test)]
757mod tests {
758    use super::*;
759
760    #[test]
761    fn test_optimizer_creation() {
762        let config = ProfilerConfig::default();
763        let targets = PerformanceTargets::default();
764        let optimizer = ProfileGuidedOptimizer::new(config, targets);
765
766        assert!(!optimizer.optimization_rules.is_empty());
767    }
768
769    #[test]
770    fn test_profile_collection() {
771        let config = ProfilerConfig::default();
772        let targets = PerformanceTargets::default();
773        let optimizer = ProfileGuidedOptimizer::new(config, targets);
774
775        let profile = optimizer
776            .collect_profile("test_program")
777            .expect("operation should succeed");
778        assert!(!profile.function_profiles.is_empty());
779        assert!(profile.total_samples > 0);
780    }
781
782    #[test]
783    fn test_optimization_recommendations() {
784        let config = ProfilerConfig::default();
785        let targets = PerformanceTargets::default();
786        let optimizer = ProfileGuidedOptimizer::new(config, targets);
787
788        optimizer
789            .collect_profile("test_program")
790            .expect("operation should succeed");
791        let recommendations = optimizer
792            .analyze_and_recommend("test_program")
793            .expect("operation should succeed");
794
795        assert!(!recommendations.is_empty());
796        assert!(recommendations[0].priority > 0.0);
797    }
798
799    #[test]
800    fn test_optimization_application() {
801        let config = ProfilerConfig::default();
802        let targets = PerformanceTargets::default();
803        let optimizer = ProfileGuidedOptimizer::new(config, targets);
804
805        optimizer
806            .collect_profile("test_program")
807            .expect("operation should succeed");
808        let recommendations = optimizer
809            .analyze_and_recommend("test_program")
810            .expect("operation should succeed");
811
812        if let Some(recommendation) = recommendations.first() {
813            assert!(optimizer.apply_optimization(recommendation).is_ok());
814
815            let history = optimizer.get_optimization_history();
816            assert!(!history.is_empty());
817            assert!(history[0].success);
818        }
819    }
820
821    #[test]
822    fn test_performance_gain_calculation() {
823        let config = ProfilerConfig::default();
824        let targets = PerformanceTargets::default();
825        let optimizer = ProfileGuidedOptimizer::new(config, targets);
826
827        optimizer
828            .collect_profile("test_program")
829            .expect("operation should succeed");
830        let recommendations = optimizer
831            .analyze_and_recommend("test_program")
832            .expect("operation should succeed");
833
834        for recommendation in recommendations.iter().take(2) {
835            optimizer
836                .apply_optimization(recommendation)
837                .expect("operation should succeed");
838        }
839
840        let gain = optimizer.calculate_performance_gain();
841        assert!(gain >= 1.0);
842    }
843
844    #[test]
845    fn test_optimization_report() {
846        let config = ProfilerConfig::default();
847        let targets = PerformanceTargets::default();
848        let optimizer = ProfileGuidedOptimizer::new(config, targets);
849
850        optimizer
851            .collect_profile("test_program")
852            .expect("operation should succeed");
853        let report = optimizer
854            .generate_report("test_program")
855            .expect("operation should succeed");
856
857        assert_eq!(report.program_name, "test_program");
858        assert!(report.profile_summary.total_functions > 0);
859        assert!(!report.recommendations.is_empty());
860    }
861
862    #[test]
863    fn test_custom_optimization_rule() {
864        let config = ProfilerConfig::default();
865        let targets = PerformanceTargets::default();
866        let mut optimizer = ProfileGuidedOptimizer::new(config, targets);
867
868        let custom_rule = OptimizationRule {
869            name: "Custom Parallel".to_string(),
870            trigger_condition: TriggerCondition {
871                min_hotness_score: 0.9,
872                min_call_frequency: 1000,
873                max_cache_miss_rate: 0.05,
874                min_loop_iterations: 1000,
875                function_name_patterns: vec!["parallel_*".to_string()],
876            },
877            optimization_type: OptimizationType::Parallelization,
878            implementation: "Use rayon parallel iterator".to_string(),
879            expected_benefit: 3.0,
880            risk_level: RiskLevel::Medium,
881        };
882
883        let initial_rules = optimizer.optimization_rules.len();
884        optimizer.add_optimization_rule(custom_rule);
885        assert_eq!(optimizer.optimization_rules.len(), initial_rules + 1);
886    }
887
888    #[test]
889    fn test_trigger_condition_matching() {
890        let config = ProfilerConfig::default();
891        let targets = PerformanceTargets::default();
892        let optimizer = ProfileGuidedOptimizer::new(config, targets);
893
894        let condition = TriggerCondition {
895            min_hotness_score: 0.5,
896            min_call_frequency: 100,
897            max_cache_miss_rate: 0.1,
898            min_loop_iterations: 0,
899            function_name_patterns: vec![],
900        };
901
902        let profile = FunctionProfile {
903            name: "test_function".to_string(),
904            total_time: Duration::from_millis(100),
905            self_time: Duration::from_millis(90),
906            call_count: 1000,
907            avg_time_per_call: Duration::from_micros(100),
908            max_time_per_call: Duration::from_millis(1),
909            min_time_per_call: Duration::from_micros(50),
910            cpu_cycles: 200_000,
911            cache_misses: 50, // 0.05 miss rate
912            branch_misses: 10,
913            hotness_score: 0.8,
914            optimization_opportunities: vec![],
915        };
916
917        assert!(optimizer.matches_trigger_condition(&condition, &profile));
918    }
919
920    #[test]
921    fn test_priority_calculation() {
922        let config = ProfilerConfig::default();
923        let targets = PerformanceTargets::default();
924        let optimizer = ProfileGuidedOptimizer::new(config, targets);
925
926        let profile = FunctionProfile {
927            name: "test_function".to_string(),
928            total_time: Duration::from_millis(100),
929            self_time: Duration::from_millis(90),
930            call_count: 1000,
931            avg_time_per_call: Duration::from_micros(100),
932            max_time_per_call: Duration::from_millis(1),
933            min_time_per_call: Duration::from_micros(50),
934            cpu_cycles: 200_000,
935            cache_misses: 50,
936            branch_misses: 10,
937            hotness_score: 0.8,
938            optimization_opportunities: vec![],
939        };
940
941        let rule = OptimizationRule {
942            name: "Test Rule".to_string(),
943            trigger_condition: TriggerCondition {
944                min_hotness_score: 0.5,
945                min_call_frequency: 100,
946                max_cache_miss_rate: 0.1,
947                min_loop_iterations: 0,
948                function_name_patterns: vec![],
949            },
950            optimization_type: OptimizationType::FunctionInlining,
951            implementation: "inline".to_string(),
952            expected_benefit: 1.5,
953            risk_level: RiskLevel::Low,
954        };
955
956        let priority = optimizer.calculate_priority(&profile, &rule);
957        assert!(priority > 0.0);
958    }
959}