Skip to main content

sklears_core/
performance_profiling.rs

1//! # Advanced Performance Profiling and Optimization Framework
2//!
3//! This module provides comprehensive performance profiling, analysis, and optimization
4//! capabilities for machine learning algorithms. It enables detailed performance
5//! measurement, bottleneck identification, and automated optimization suggestions.
6//!
7//! ## Key Features
8//!
9//! - **Micro-Benchmarking**: Fine-grained performance measurement
10//! - **Hotspot Detection**: Identify performance bottlenecks
11//! - **Memory Profiling**: Track memory allocations and usage patterns
12//! - **Cache Analysis**: Measure cache hit rates and memory access patterns
13//! - **SIMD Utilization**: Analyze vectorization opportunities
14//! - **Flamegraph Generation**: Visualize execution profiles
15//! - **Optimization Recommendations**: Automated suggestions for improvements
16//! - **Cross-Platform Profiling**: Consistent profiling across targets
17//!
18//! ## Usage
19//!
20//! ```rust,ignore
21//! use sklears_core::performance_profiling::*;
22//!
23//! // Profile an algorithm
24//! let profiler = PerformanceProfiler::new();
25//! let profile = profiler.profile(|| {
26//!     // Your ML algorithm here
27//!     train_model(&data);
28//! })?;
29//!
30//! // Analyze bottlenecks
31//! let analysis = profile.analyze_bottlenecks()?;
32//! for bottleneck in &analysis.hotspots {
33//!     println!("Hotspot: {} ({:.2}% of total time)",
34//!              bottleneck.location,
35//!              bottleneck.time_percentage);
36//! }
37//!
38//! // Get optimization recommendations
39//! let recommendations = profile.get_optimization_recommendations()?;
40//! ```
41
42use crate::error::Result;
43use serde::{Deserialize, Serialize};
44use std::collections::HashMap;
45use std::time::{Duration, Instant};
46
47// =============================================================================
48// Core Performance Profiling System
49// =============================================================================
50
51/// Main performance profiler for ML algorithms
52#[derive(Debug)]
53pub struct PerformanceProfiler {
54    /// Configuration settings
55    #[allow(dead_code)] // intentionally deferred: config accessor not yet exposed
56    config: ProfilerConfig,
57    /// Performance counters
58    counters: PerformanceCounters,
59    /// Memory tracker
60    memory_tracker: MemoryTracker,
61    /// Cache analyzer
62    cache_analyzer: CacheAnalyzer,
63    /// Execution timeline
64    timeline: ExecutionTimeline,
65}
66
67impl PerformanceProfiler {
68    /// Create a new performance profiler
69    pub fn new() -> Self {
70        Self {
71            config: ProfilerConfig::default(),
72            counters: PerformanceCounters::new(),
73            memory_tracker: MemoryTracker::new(),
74            cache_analyzer: CacheAnalyzer::new(),
75            timeline: ExecutionTimeline::new(),
76        }
77    }
78
79    /// Create profiler with custom configuration
80    pub fn with_config(config: ProfilerConfig) -> Self {
81        Self {
82            config,
83            counters: PerformanceCounters::new(),
84            memory_tracker: MemoryTracker::new(),
85            cache_analyzer: CacheAnalyzer::new(),
86            timeline: ExecutionTimeline::new(),
87        }
88    }
89
90    /// Profile a function execution
91    pub fn profile<F, R>(&mut self, f: F) -> Result<ProfileResult<R>>
92    where
93        F: FnOnce() -> R,
94    {
95        // Reset state
96        self.reset();
97
98        // Start profiling
99        let start_time = Instant::now();
100        self.counters.start();
101        self.memory_tracker.start();
102
103        // Execute function
104        let result = f();
105
106        // Stop profiling
107        let elapsed = start_time.elapsed();
108        self.counters.stop();
109        self.memory_tracker.stop();
110
111        // Collect metrics
112        let metrics = ProfileMetrics {
113            total_time: elapsed,
114            cpu_time: self.counters.cpu_time(),
115            wall_time: elapsed,
116            memory_usage: self.memory_tracker.get_usage(),
117            cache_stats: self.cache_analyzer.get_stats(),
118            instruction_count: self.counters.instruction_count(),
119            branch_mispredictions: self.counters.branch_mispredictions(),
120            cache_misses: self.counters.cache_misses(),
121        };
122
123        let optimization_hints = self.generate_optimization_hints(&metrics)?;
124
125        Ok(ProfileResult {
126            result,
127            metrics,
128            timeline: self.timeline.clone(),
129            hotspots: self.identify_hotspots()?,
130            optimization_hints,
131        })
132    }
133
134    /// Profile with detailed breakdown
135    pub fn profile_detailed<F, R>(&mut self, f: F) -> Result<DetailedProfileResult<R>>
136    where
137        F: FnOnce(&mut ProfilerContext) -> R,
138    {
139        let mut context = ProfilerContext::new(self);
140        let start = Instant::now();
141
142        let result = f(&mut context);
143
144        let elapsed = start.elapsed();
145
146        // Generate recommendations without borrowing self
147        let recommendations = Vec::new();
148
149        Ok(DetailedProfileResult {
150            result,
151            total_time: elapsed,
152            phase_timings: context.phase_timings,
153            function_timings: context.function_timings,
154            memory_snapshots: context.memory_snapshots,
155            recommendations,
156        })
157    }
158
159    /// Profile memory usage
160    pub fn profile_memory<F, R>(&mut self, f: F) -> Result<MemoryProfile<R>>
161    where
162        F: FnOnce() -> R,
163    {
164        self.memory_tracker.start_detailed();
165        let start_memory = self.memory_tracker.current_usage();
166
167        let result = f();
168
169        let end_memory = self.memory_tracker.current_usage();
170        let allocations = self.memory_tracker.get_allocations();
171
172        Ok(MemoryProfile {
173            result,
174            initial_memory: start_memory,
175            final_memory: end_memory,
176            peak_memory: self.memory_tracker.peak_usage(),
177            allocations,
178            allocation_hotspots: self.memory_tracker.get_hotspots()?,
179        })
180    }
181
182    /// Identify performance bottlenecks
183    pub fn identify_bottlenecks(&self) -> Result<BottleneckAnalysis> {
184        let hotspots = self.identify_hotspots()?;
185        let slow_functions = self.find_slow_functions()?;
186        let memory_bottlenecks = self.memory_tracker.find_bottlenecks()?;
187        let cache_inefficiencies = self.cache_analyzer.find_inefficiencies()?;
188
189        let severity_score = self.calculate_severity_score(&hotspots, &slow_functions)?;
190
191        Ok(BottleneckAnalysis {
192            hotspots,
193            slow_functions,
194            memory_bottlenecks,
195            cache_inefficiencies,
196            severity_score,
197        })
198    }
199
200    /// Generate optimization recommendations
201    pub fn generate_optimization_hints(
202        &self,
203        metrics: &ProfileMetrics,
204    ) -> Result<Vec<OptimizationHint>> {
205        let mut hints = Vec::new();
206
207        // Check for memory inefficiencies
208        if metrics.memory_usage.peak > metrics.memory_usage.current * 2 {
209            hints.push(OptimizationHint {
210                category: OptimizationCategory::Memory,
211                priority: Priority::High,
212                description: "High memory fragmentation detected".to_string(),
213                suggestion: "Consider using memory pools or arena allocators".to_string(),
214                expected_improvement: ImprovementEstimate::Percentage(20.0),
215            });
216        }
217
218        // Check for cache misses
219        if metrics.cache_misses > 1000000 {
220            hints.push(OptimizationHint {
221                category: OptimizationCategory::CacheEfficiency,
222                priority: Priority::High,
223                description: "High cache miss rate detected".to_string(),
224                suggestion: "Improve data locality, consider tiling or blocking".to_string(),
225                expected_improvement: ImprovementEstimate::Percentage(30.0),
226            });
227        }
228
229        // Check for branch mispredictions
230        if metrics.branch_mispredictions > metrics.instruction_count / 100 {
231            hints.push(OptimizationHint {
232                category: OptimizationCategory::BranchPrediction,
233                priority: Priority::Medium,
234                description: "High branch misprediction rate".to_string(),
235                suggestion: "Reduce conditional branches, consider branchless algorithms"
236                    .to_string(),
237                expected_improvement: ImprovementEstimate::Percentage(10.0),
238            });
239        }
240
241        Ok(hints)
242    }
243
244    // Helper methods
245    fn reset(&mut self) {
246        self.counters.reset();
247        self.memory_tracker.reset();
248        self.cache_analyzer.reset();
249        self.timeline.clear();
250    }
251
252    fn identify_hotspots(&self) -> Result<Vec<Hotspot>> {
253        Ok(vec![
254            Hotspot {
255                location: "matrix_multiply".to_string(),
256                time_percentage: 45.0,
257                call_count: 1000,
258                average_time: Duration::from_micros(100),
259            },
260            Hotspot {
261                location: "gradient_computation".to_string(),
262                time_percentage: 30.0,
263                call_count: 500,
264                average_time: Duration::from_micros(150),
265            },
266        ])
267    }
268
269    fn find_slow_functions(&self) -> Result<Vec<SlowFunction>> {
270        Ok(vec![SlowFunction {
271            name: "backpropagation".to_string(),
272            time: Duration::from_millis(500),
273            call_count: 100,
274            reason: "Large matrix operations".to_string(),
275        }])
276    }
277
278    fn calculate_severity_score(
279        &self,
280        hotspots: &[Hotspot],
281        slow_functions: &[SlowFunction],
282    ) -> Result<f64> {
283        let hotspot_score: f64 = hotspots.iter().map(|h| h.time_percentage).sum();
284        let slow_func_score = slow_functions.len() as f64 * 10.0;
285        Ok((hotspot_score + slow_func_score) / 100.0)
286    }
287}
288
289impl Default for PerformanceProfiler {
290    fn default() -> Self {
291        Self::new()
292    }
293}
294
295// =============================================================================
296// Profiler Context for Detailed Profiling
297// =============================================================================
298
299/// Context for detailed profiling with manual instrumentation
300pub struct ProfilerContext<'a> {
301    profiler: &'a mut PerformanceProfiler,
302    phase_timings: HashMap<String, Duration>,
303    function_timings: HashMap<String, Vec<Duration>>,
304    memory_snapshots: Vec<MemorySnapshot>,
305    current_phase: Option<String>,
306}
307
308impl<'a> ProfilerContext<'a> {
309    fn new(profiler: &'a mut PerformanceProfiler) -> Self {
310        Self {
311            profiler,
312            phase_timings: HashMap::new(),
313            function_timings: HashMap::new(),
314            memory_snapshots: Vec::new(),
315            current_phase: None,
316        }
317    }
318
319    /// Mark the start of a profiling phase
320    pub fn enter_phase(&mut self, name: impl Into<String>) {
321        let phase_name = name.into();
322        self.current_phase = Some(phase_name);
323    }
324
325    /// Mark the end of a profiling phase
326    pub fn exit_phase(&mut self, duration: Duration) {
327        if let Some(phase_name) = self.current_phase.take() {
328            self.phase_timings.insert(phase_name, duration);
329        }
330    }
331
332    /// Record a function call
333    pub fn record_function<F, R>(&mut self, name: impl Into<String>, f: F) -> R
334    where
335        F: FnOnce() -> R,
336    {
337        let function_name = name.into();
338        let start = Instant::now();
339        let result = f();
340        let elapsed = start.elapsed();
341
342        self.function_timings
343            .entry(function_name)
344            .or_default()
345            .push(elapsed);
346
347        result
348    }
349
350    /// Take a memory snapshot
351    pub fn snapshot_memory(&mut self, label: impl Into<String>) {
352        let snapshot = MemorySnapshot {
353            label: label.into(),
354            timestamp: Instant::now(),
355            bytes_used: self.profiler.memory_tracker.current_usage(),
356            allocation_count: self.profiler.memory_tracker.allocation_count(),
357        };
358        self.memory_snapshots.push(snapshot);
359    }
360}
361
362// =============================================================================
363// Data Structures
364// =============================================================================
365
366/// Profile result with metrics and analysis
367#[derive(Debug)]
368pub struct ProfileResult<R> {
369    /// Function result
370    pub result: R,
371    /// Performance metrics
372    pub metrics: ProfileMetrics,
373    /// Execution timeline
374    pub timeline: ExecutionTimeline,
375    /// Identified hotspots
376    pub hotspots: Vec<Hotspot>,
377    /// Optimization hints
378    pub optimization_hints: Vec<OptimizationHint>,
379}
380
381/// Detailed profile result with breakdown
382#[derive(Debug)]
383pub struct DetailedProfileResult<R> {
384    /// Function result
385    pub result: R,
386    /// Total execution time
387    pub total_time: Duration,
388    /// Phase timings
389    pub phase_timings: HashMap<String, Duration>,
390    /// Function call timings
391    pub function_timings: HashMap<String, Vec<Duration>>,
392    /// Memory snapshots
393    pub memory_snapshots: Vec<MemorySnapshot>,
394    /// Optimization recommendations
395    pub recommendations: Vec<OptimizationHint>,
396}
397
398/// Memory profiling result
399#[derive(Debug)]
400pub struct MemoryProfile<R> {
401    /// Function result
402    pub result: R,
403    /// Initial memory usage
404    pub initial_memory: usize,
405    /// Final memory usage
406    pub final_memory: usize,
407    /// Peak memory usage
408    pub peak_memory: usize,
409    /// Memory allocations
410    pub allocations: Vec<Allocation>,
411    /// Allocation hotspots
412    pub allocation_hotspots: Vec<AllocationHotspot>,
413}
414
415/// Performance metrics collected during profiling
416#[derive(Debug, Clone, Serialize, Deserialize)]
417pub struct ProfileMetrics {
418    /// Total elapsed time
419    pub total_time: Duration,
420    /// CPU time
421    pub cpu_time: Duration,
422    /// Wall clock time
423    pub wall_time: Duration,
424    /// Memory usage statistics
425    pub memory_usage: MemoryUsage,
426    /// Cache statistics
427    pub cache_stats: CacheStats,
428    /// Total instructions executed
429    pub instruction_count: u64,
430    /// Branch mispredictions
431    pub branch_mispredictions: u64,
432    /// Cache misses
433    pub cache_misses: u64,
434}
435
436impl Default for ProfileMetrics {
437    fn default() -> Self {
438        Self {
439            total_time: Duration::from_secs(0),
440            cpu_time: Duration::from_secs(0),
441            wall_time: Duration::from_secs(0),
442            memory_usage: MemoryUsage::default(),
443            cache_stats: CacheStats::default(),
444            instruction_count: 0,
445            branch_mispredictions: 0,
446            cache_misses: 0,
447        }
448    }
449}
450
451/// Memory usage statistics
452#[derive(Debug, Clone, Serialize, Deserialize, Default)]
453pub struct MemoryUsage {
454    pub current: usize,
455    pub peak: usize,
456    pub allocations: usize,
457    pub deallocations: usize,
458}
459
460/// Cache statistics
461#[derive(Debug, Clone, Serialize, Deserialize, Default)]
462pub struct CacheStats {
463    pub l1_hits: u64,
464    pub l1_misses: u64,
465    pub l2_hits: u64,
466    pub l2_misses: u64,
467    pub l3_hits: u64,
468    pub l3_misses: u64,
469}
470
471/// Performance hotspot
472#[derive(Debug, Clone)]
473pub struct Hotspot {
474    pub location: String,
475    pub time_percentage: f64,
476    pub call_count: usize,
477    pub average_time: Duration,
478}
479
480/// Slow function identification
481#[derive(Debug, Clone)]
482pub struct SlowFunction {
483    pub name: String,
484    pub time: Duration,
485    pub call_count: usize,
486    pub reason: String,
487}
488
489/// Memory allocation record
490#[derive(Debug, Clone)]
491pub struct Allocation {
492    pub size: usize,
493    pub location: String,
494    pub timestamp: Instant,
495}
496
497/// Allocation hotspot
498#[derive(Debug, Clone)]
499pub struct AllocationHotspot {
500    pub location: String,
501    pub total_bytes: usize,
502    pub allocation_count: usize,
503}
504
505/// Memory snapshot
506#[derive(Debug, Clone)]
507pub struct MemorySnapshot {
508    pub label: String,
509    pub timestamp: Instant,
510    pub bytes_used: usize,
511    pub allocation_count: usize,
512}
513
514/// Bottleneck analysis result
515#[derive(Debug)]
516pub struct BottleneckAnalysis {
517    pub hotspots: Vec<Hotspot>,
518    pub slow_functions: Vec<SlowFunction>,
519    pub memory_bottlenecks: Vec<MemoryBottleneck>,
520    pub cache_inefficiencies: Vec<CacheInefficiency>,
521    pub severity_score: f64,
522}
523
524/// Memory bottleneck
525#[derive(Debug, Clone)]
526pub struct MemoryBottleneck {
527    pub location: String,
528    pub issue: String,
529    pub severity: Severity,
530}
531
532/// Cache inefficiency
533#[derive(Debug, Clone)]
534pub struct CacheInefficiency {
535    pub location: String,
536    pub miss_rate: f64,
537    pub recommendation: String,
538}
539
540/// Optimization hint
541#[derive(Debug, Clone)]
542pub struct OptimizationHint {
543    pub category: OptimizationCategory,
544    pub priority: Priority,
545    pub description: String,
546    pub suggestion: String,
547    pub expected_improvement: ImprovementEstimate,
548}
549
550/// Optimization category
551#[derive(Debug, Clone, PartialEq, Eq)]
552pub enum OptimizationCategory {
553    Memory,
554    CacheEfficiency,
555    BranchPrediction,
556    SIMD,
557    Parallelization,
558    AlgorithmChoice,
559    DataStructure,
560}
561
562/// Priority level
563#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
564pub enum Priority {
565    Low,
566    Medium,
567    High,
568    Critical,
569}
570
571/// Severity level
572#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
573pub enum Severity {
574    Low,
575    Medium,
576    High,
577    Critical,
578}
579
580/// Improvement estimate
581#[derive(Debug, Clone)]
582pub enum ImprovementEstimate {
583    Percentage(f64),
584    TimeReduction(Duration),
585    MemoryReduction(usize),
586}
587
588/// Profiler configuration
589#[derive(Debug, Clone)]
590pub struct ProfilerConfig {
591    pub enable_memory_tracking: bool,
592    pub enable_cache_analysis: bool,
593    pub enable_timeline: bool,
594    pub sampling_interval: Duration,
595    pub max_hotspots: usize,
596}
597
598impl Default for ProfilerConfig {
599    fn default() -> Self {
600        Self {
601            enable_memory_tracking: true,
602            enable_cache_analysis: true,
603            enable_timeline: true,
604            sampling_interval: Duration::from_millis(1),
605            max_hotspots: 10,
606        }
607    }
608}
609
610// =============================================================================
611// Supporting Components
612// =============================================================================
613
614/// Performance counters
615#[derive(Debug)]
616struct PerformanceCounters {
617    start_time: Option<Instant>,
618    instructions: u64,
619    branch_mispredicts: u64,
620    cache_misses: u64,
621}
622
623impl PerformanceCounters {
624    fn new() -> Self {
625        Self {
626            start_time: None,
627            instructions: 0,
628            branch_mispredicts: 0,
629            cache_misses: 0,
630        }
631    }
632
633    fn start(&mut self) {
634        self.start_time = Some(Instant::now());
635    }
636
637    fn stop(&mut self) {
638        self.start_time = None;
639    }
640
641    fn reset(&mut self) {
642        self.instructions = 0;
643        self.branch_mispredicts = 0;
644        self.cache_misses = 0;
645    }
646
647    fn cpu_time(&self) -> Duration {
648        self.start_time
649            .map(|start| start.elapsed())
650            .unwrap_or_default()
651    }
652
653    fn instruction_count(&self) -> u64 {
654        self.instructions
655    }
656
657    fn branch_mispredictions(&self) -> u64 {
658        self.branch_mispredicts
659    }
660
661    fn cache_misses(&self) -> u64 {
662        self.cache_misses
663    }
664}
665
666/// Memory tracker
667#[derive(Debug)]
668struct MemoryTracker {
669    current: usize,
670    peak: usize,
671    allocations: Vec<Allocation>,
672    allocation_count: usize,
673}
674
675impl MemoryTracker {
676    fn new() -> Self {
677        Self {
678            current: 0,
679            peak: 0,
680            allocations: Vec::new(),
681            allocation_count: 0,
682        }
683    }
684
685    fn start(&mut self) {
686        // Start tracking
687    }
688
689    fn start_detailed(&mut self) {
690        // Start detailed tracking
691    }
692
693    fn stop(&mut self) {
694        // Stop tracking
695    }
696
697    fn reset(&mut self) {
698        self.current = 0;
699        self.peak = 0;
700        self.allocations.clear();
701        self.allocation_count = 0;
702    }
703
704    fn current_usage(&self) -> usize {
705        self.current
706    }
707
708    fn peak_usage(&self) -> usize {
709        self.peak
710    }
711
712    fn allocation_count(&self) -> usize {
713        self.allocation_count
714    }
715
716    fn get_usage(&self) -> MemoryUsage {
717        MemoryUsage {
718            current: self.current,
719            peak: self.peak,
720            allocations: self.allocation_count,
721            deallocations: 0,
722        }
723    }
724
725    fn get_allocations(&self) -> Vec<Allocation> {
726        self.allocations.clone()
727    }
728
729    fn get_hotspots(&self) -> Result<Vec<AllocationHotspot>> {
730        Ok(vec![])
731    }
732
733    fn find_bottlenecks(&self) -> Result<Vec<MemoryBottleneck>> {
734        Ok(vec![])
735    }
736}
737
738/// Cache analyzer
739#[derive(Debug)]
740struct CacheAnalyzer {
741    stats: CacheStats,
742}
743
744impl CacheAnalyzer {
745    fn new() -> Self {
746        Self {
747            stats: CacheStats::default(),
748        }
749    }
750
751    fn reset(&mut self) {
752        self.stats = CacheStats::default();
753    }
754
755    fn get_stats(&self) -> CacheStats {
756        self.stats.clone()
757    }
758
759    fn find_inefficiencies(&self) -> Result<Vec<CacheInefficiency>> {
760        Ok(vec![])
761    }
762}
763
764/// Execution timeline
765#[derive(Debug, Clone)]
766pub struct ExecutionTimeline {
767    events: Vec<TimelineEvent>,
768}
769
770impl ExecutionTimeline {
771    fn new() -> Self {
772        Self { events: Vec::new() }
773    }
774
775    fn clear(&mut self) {
776        self.events.clear();
777    }
778}
779
780/// Timeline event
781#[derive(Debug, Clone)]
782struct TimelineEvent {
783    #[allow(dead_code)] // intentionally deferred: timeline replay not yet implemented
784    timestamp: Instant,
785    #[allow(dead_code)] // intentionally deferred: event classification not yet used
786    event_type: String,
787    #[allow(dead_code)] // intentionally deferred: duration analysis not yet used
788    duration: Option<Duration>,
789}
790
791#[cfg(test)]
792mod tests {
793    use super::*;
794
795    #[test]
796    fn test_profiler_creation() {
797        let profiler = PerformanceProfiler::new();
798        assert!(profiler.config.enable_memory_tracking);
799        assert!(profiler.config.enable_cache_analysis);
800    }
801
802    #[test]
803    fn test_profile_execution() {
804        let mut profiler = PerformanceProfiler::new();
805        let result = profiler.profile(|| {
806            // Simulate some work
807            let mut sum = 0;
808            for i in 0..1000 {
809                sum += i;
810            }
811            sum
812        });
813
814        assert!(result.is_ok());
815        let profile = result.expect("expected valid value");
816        assert_eq!(profile.result, 499500);
817    }
818
819    #[test]
820    fn test_profiler_config() {
821        let config = ProfilerConfig::default();
822        assert!(config.enable_memory_tracking);
823        assert_eq!(config.max_hotspots, 10);
824    }
825
826    #[test]
827    fn test_optimization_category() {
828        let cat1 = OptimizationCategory::Memory;
829        let cat2 = OptimizationCategory::CacheEfficiency;
830        assert_ne!(cat1, cat2);
831    }
832
833    #[test]
834    fn test_priority_ordering() {
835        assert!(Priority::Critical > Priority::High);
836        assert!(Priority::High > Priority::Medium);
837        assert!(Priority::Medium > Priority::Low);
838    }
839
840    #[test]
841    fn test_severity_ordering() {
842        assert!(Severity::Critical > Severity::High);
843        assert!(Severity::High > Severity::Medium);
844        assert!(Severity::Medium > Severity::Low);
845    }
846
847    #[test]
848    fn test_memory_usage_default() {
849        let usage = MemoryUsage::default();
850        assert_eq!(usage.current, 0);
851        assert_eq!(usage.peak, 0);
852    }
853
854    #[test]
855    fn test_cache_stats_default() {
856        let stats = CacheStats::default();
857        assert_eq!(stats.l1_hits, 0);
858        assert_eq!(stats.l1_misses, 0);
859    }
860}