1#![allow(dead_code)]
28use crate::{SparseFormat, SparseTensor, TorshResult};
29use std::collections::HashMap;
30use std::time::{Duration, Instant};
31
32use super::core::PerformanceMeasurement;
33
34#[derive(Debug, Clone)]
39pub struct MemoryAnalysis {
40 pub format: SparseFormat,
42 pub nnz: usize,
44 pub estimated_memory: usize,
46 pub dense_memory: usize,
48 pub compression_ratio: f32,
50 pub overhead_per_nnz: f32,
52 pub matrix_dimensions: (usize, usize),
54 pub memory_breakdown: MemoryBreakdown,
56 pub metrics: HashMap<String, f64>,
58}
59
60#[derive(Debug, Clone)]
62pub struct MemoryBreakdown {
63 pub values_memory: usize,
65 pub indices_memory: usize,
67 pub structure_memory: usize,
69 pub metadata_memory: usize,
71}
72
73impl MemoryAnalysis {
74 pub fn new(format: SparseFormat, nnz: usize, matrix_dimensions: (usize, usize)) -> Self {
76 Self {
77 format,
78 nnz,
79 estimated_memory: 0,
80 dense_memory: 0,
81 compression_ratio: 1.0,
82 overhead_per_nnz: 0.0,
83 matrix_dimensions,
84 memory_breakdown: MemoryBreakdown::default(),
85 metrics: HashMap::new(),
86 }
87 }
88
89 pub fn compression_effectiveness(&self) -> f32 {
91 if self.dense_memory == 0 {
92 return 0.0;
93 }
94 1.0 - (self.estimated_memory as f32 / self.dense_memory as f32)
95 }
96
97 pub fn sparsity_ratio(&self) -> f32 {
99 let total_elements = self.matrix_dimensions.0 * self.matrix_dimensions.1;
100 if total_elements == 0 {
101 return 0.0;
102 }
103 1.0 - (self.nnz as f32 / total_elements as f32)
104 }
105
106 pub fn is_memory_efficient(&self) -> bool {
108 self.compression_ratio > 2.0 }
110
111 pub fn memory_efficiency_rating(&self) -> String {
113 match self.compression_ratio {
114 r if r >= 10.0 => "Excellent".to_string(),
115 r if r >= 5.0 => "Good".to_string(),
116 r if r >= 2.0 => "Fair".to_string(),
117 _ => "Poor".to_string(),
118 }
119 }
120
121 pub fn add_metric(&mut self, key: String, value: f64) {
123 self.metrics.insert(key, value);
124 }
125}
126
127impl Default for MemoryBreakdown {
128 fn default() -> Self {
129 Self {
130 values_memory: 0,
131 indices_memory: 0,
132 structure_memory: 0,
133 metadata_memory: 0,
134 }
135 }
136}
137
138impl MemoryBreakdown {
139 pub fn total_memory(&self) -> usize {
141 self.values_memory + self.indices_memory + self.structure_memory + self.metadata_memory
142 }
143
144 pub fn memory_distribution(&self) -> HashMap<String, f64> {
146 let total = self.total_memory() as f64;
147 if total == 0.0 {
148 return HashMap::new();
149 }
150
151 let mut distribution = HashMap::new();
152 distribution.insert(
153 "values".to_string(),
154 (self.values_memory as f64 / total) * 100.0,
155 );
156 distribution.insert(
157 "indices".to_string(),
158 (self.indices_memory as f64 / total) * 100.0,
159 );
160 distribution.insert(
161 "structure".to_string(),
162 (self.structure_memory as f64 / total) * 100.0,
163 );
164 distribution.insert(
165 "metadata".to_string(),
166 (self.metadata_memory as f64 / total) * 100.0,
167 );
168 distribution
169 }
170}
171
172#[derive(Debug, Clone)]
174pub struct CachePerformanceResult {
175 pub operation: String,
177 pub measurements: Vec<PerformanceMeasurement>,
179 pub cache_efficiency_score: f64,
181 pub recommendations: Vec<String>,
183 pub cache_miss_ratio: f64,
185 pub access_pattern: MemoryAccessPattern,
187}
188
189#[derive(Debug, Clone)]
191pub enum MemoryAccessPattern {
192 Sequential,
193 Random,
194 Strided { stride: usize },
195 Blocked { block_size: usize },
196 Mixed,
197}
198
199impl std::fmt::Display for MemoryAccessPattern {
200 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201 match self {
202 MemoryAccessPattern::Sequential => write!(f, "Sequential"),
203 MemoryAccessPattern::Random => write!(f, "Random"),
204 MemoryAccessPattern::Strided { stride } => write!(f, "Strided (stride: {})", stride),
205 MemoryAccessPattern::Blocked { block_size } => {
206 write!(f, "Blocked (block size: {})", block_size)
207 }
208 MemoryAccessPattern::Mixed => write!(f, "Mixed"),
209 }
210 }
211}
212
213#[derive(Debug)]
215pub struct MemoryTracker {
216 baseline_memory: usize,
218 peak_memory: usize,
220 current_memory: usize,
222 samples: Vec<(Instant, usize)>,
224 sampling_interval: Duration,
226}
227
228impl Default for MemoryTracker {
229 fn default() -> Self {
230 Self::new()
231 }
232}
233
234impl MemoryTracker {
235 pub fn new() -> Self {
237 let current_memory = get_current_memory_usage();
238 Self {
239 baseline_memory: current_memory,
240 peak_memory: current_memory,
241 current_memory,
242 samples: Vec::new(),
243 sampling_interval: Duration::from_millis(10),
244 }
245 }
246
247 pub fn track_operation<F, R>(&mut self, operation: F) -> TorshResult<(R, MemoryUsageResult)>
249 where
250 F: FnOnce() -> TorshResult<R>,
251 {
252 self.reset();
254
255 let start_time = Instant::now();
257 let start_memory = get_current_memory_usage();
258 self.baseline_memory = start_memory;
259 self.current_memory = start_memory;
260 self.peak_memory = start_memory;
261
262 self.add_sample(start_time, start_memory);
264
265 let result = operation()?;
267
268 let end_memory = get_current_memory_usage();
270 self.current_memory = end_memory;
271 self.add_sample(Instant::now(), end_memory);
272
273 let usage_result = MemoryUsageResult {
275 baseline_memory: self.baseline_memory,
276 peak_memory: self.peak_memory,
277 final_memory: end_memory,
278 memory_delta: end_memory as i64 - start_memory as i64,
279 peak_delta: self.peak_memory.saturating_sub(start_memory),
280 samples: self.samples.clone(),
281 };
282
283 Ok((result, usage_result))
284 }
285
286 pub fn reset(&mut self) {
288 self.samples.clear();
289 self.current_memory = get_current_memory_usage();
290 self.baseline_memory = self.current_memory;
291 self.peak_memory = self.current_memory;
292 }
293
294 fn add_sample(&mut self, timestamp: Instant, memory_usage: usize) {
296 self.samples.push((timestamp, memory_usage));
297 if memory_usage > self.peak_memory {
298 self.peak_memory = memory_usage;
299 }
300 }
301
302 pub fn memory_growth_rate(&self) -> f64 {
304 if self.samples.len() < 2 {
305 return 0.0;
306 }
307
308 let first = &self.samples[0];
309 let last = &self.samples[self.samples.len() - 1];
310
311 let time_diff = last.0.duration_since(first.0).as_secs_f64();
312 let memory_diff = last.1 as i64 - first.1 as i64;
313
314 if time_diff > 0.0 {
315 memory_diff as f64 / time_diff
316 } else {
317 0.0
318 }
319 }
320}
321
322#[derive(Debug, Clone)]
324pub struct MemoryUsageResult {
325 pub baseline_memory: usize,
327 pub peak_memory: usize,
329 pub final_memory: usize,
331 pub memory_delta: i64,
333 pub peak_delta: usize,
335 pub samples: Vec<(Instant, usize)>,
337}
338
339impl MemoryUsageResult {
340 pub fn has_potential_leak(&self, threshold_bytes: usize) -> bool {
342 self.memory_delta > threshold_bytes as i64
343 }
344
345 pub fn efficiency_score(&self) -> f64 {
347 if self.peak_delta == 0 {
348 return 1.0;
349 }
350
351 let final_delta = self.memory_delta.max(0) as usize;
353 if self.peak_delta <= final_delta {
354 1.0
355 } else {
356 final_delta as f64 / self.peak_delta as f64
357 }
358 }
359}
360
361pub fn analyze_sparse_memory(sparse: &dyn SparseTensor) -> TorshResult<MemoryAnalysis> {
363 let format = sparse.format();
364 let shape = sparse.shape();
365 let nnz = sparse.nnz();
366
367 let mut analysis = MemoryAnalysis::new(format, nnz, (shape.dims()[0], shape.dims()[1]));
368
369 analysis.memory_breakdown = calculate_memory_breakdown(sparse)?;
371 analysis.estimated_memory = analysis.memory_breakdown.total_memory();
372
373 let element_size = match sparse.dtype() {
375 torsh_core::DType::F32 => 4,
376 torsh_core::DType::F64 => 8,
377 torsh_core::DType::I32 => 4,
378 torsh_core::DType::I64 => 8,
379 _ => 4, };
381 analysis.dense_memory = shape.dims()[0] * shape.dims()[1] * element_size;
382
383 if analysis.estimated_memory > 0 {
385 analysis.compression_ratio =
386 analysis.dense_memory as f32 / analysis.estimated_memory as f32;
387 }
388
389 if nnz > 0 {
390 analysis.overhead_per_nnz = analysis.estimated_memory as f32 / nnz as f32;
391 }
392
393 analysis.add_metric(
395 "sparsity_ratio".to_string(),
396 analysis.sparsity_ratio() as f64,
397 );
398 analysis.add_metric(
399 "compression_effectiveness".to_string(),
400 analysis.compression_effectiveness() as f64,
401 );
402
403 Ok(analysis)
404}
405
406fn calculate_memory_breakdown(sparse: &dyn SparseTensor) -> TorshResult<MemoryBreakdown> {
408 let nnz = sparse.nnz();
409 let shape = sparse.shape();
410
411 let element_size = match sparse.dtype() {
412 torsh_core::DType::F32 => 4,
413 torsh_core::DType::F64 => 8,
414 torsh_core::DType::I32 => 4,
415 torsh_core::DType::I64 => 8,
416 _ => 4,
417 };
418
419 let index_size = 4; let mut breakdown = MemoryBreakdown::default();
422
423 match sparse.format() {
424 SparseFormat::Coo => {
425 breakdown.values_memory = nnz * element_size;
427 breakdown.indices_memory = nnz * 2 * index_size; breakdown.structure_memory = 0;
429 breakdown.metadata_memory = 32; }
431 SparseFormat::Csr => {
432 breakdown.values_memory = nnz * element_size;
434 breakdown.indices_memory = nnz * index_size; breakdown.structure_memory = (shape.dims()[0] + 1) * index_size; breakdown.metadata_memory = 32;
437 }
438 SparseFormat::Csc => {
439 breakdown.values_memory = nnz * element_size;
441 breakdown.indices_memory = nnz * index_size; breakdown.structure_memory = (shape.dims()[1] + 1) * index_size; breakdown.metadata_memory = 32;
444 }
445 SparseFormat::Bsr => {
446 breakdown.values_memory = nnz * element_size;
448 breakdown.indices_memory = nnz * index_size; breakdown.structure_memory = nnz * index_size; breakdown.metadata_memory = 64; }
452 SparseFormat::Dia => {
453 breakdown.values_memory = nnz * element_size;
455 breakdown.indices_memory = 0; breakdown.structure_memory = nnz * index_size; breakdown.metadata_memory = 32;
458 }
459 SparseFormat::Dsr => {
460 breakdown.values_memory = nnz * element_size;
462 breakdown.indices_memory = nnz * index_size;
463 breakdown.structure_memory = nnz * index_size;
464 breakdown.metadata_memory = 32;
465 }
466 SparseFormat::Ell => {
467 breakdown.values_memory = nnz * element_size;
469 breakdown.indices_memory = nnz * index_size;
470 breakdown.structure_memory = 0;
471 breakdown.metadata_memory = 32;
472 }
473 SparseFormat::Rle => {
474 breakdown.values_memory = nnz * element_size;
476 breakdown.indices_memory = nnz * index_size;
477 breakdown.structure_memory = nnz * index_size; breakdown.metadata_memory = 32;
479 }
480 SparseFormat::Symmetric => {
481 breakdown.values_memory = nnz * element_size;
483 breakdown.indices_memory = nnz * index_size;
484 breakdown.structure_memory = nnz * index_size; breakdown.metadata_memory = 64; }
487 }
488
489 Ok(breakdown)
490}
491
492pub fn benchmark_cache_performance(
494 sparse: &dyn SparseTensor,
495 operation_name: String,
496) -> TorshResult<CachePerformanceResult> {
497 let mut measurements = Vec::new();
498 let _cache_metrics: HashMap<String, f64> = HashMap::new();
499
500 for iteration in 0..5 {
502 let measurement_name = format!("{}_cache_iteration_{}", operation_name, iteration);
503 let mut measurement = PerformanceMeasurement::new(measurement_name);
504
505 let start = Instant::now();
506
507 simulate_cache_sensitive_operation(sparse)?;
509
510 measurement.duration = start.elapsed();
511 measurements.push(measurement);
512 }
513
514 let cache_efficiency_score = calculate_cache_efficiency(&measurements);
516 let cache_miss_ratio = estimate_cache_miss_ratio(sparse);
517 let access_pattern = analyze_access_pattern(sparse);
518 let recommendations = generate_cache_recommendations(cache_efficiency_score, &access_pattern);
519
520 Ok(CachePerformanceResult {
521 operation: operation_name,
522 measurements,
523 cache_efficiency_score,
524 recommendations,
525 cache_miss_ratio,
526 access_pattern,
527 })
528}
529
530fn simulate_cache_sensitive_operation(sparse: &dyn SparseTensor) -> TorshResult<()> {
532 let nnz = sparse.nnz();
535 let mut sum = 0.0f64;
536
537 match sparse.format() {
539 SparseFormat::Csr => {
540 for i in 0..nnz.min(1000) {
542 sum += (i as f64).sin(); }
544 }
545 SparseFormat::Csc => {
546 for i in 0..nnz.min(1000) {
548 sum += (i as f64).cos(); }
550 }
551 SparseFormat::Coo => {
552 for i in 0..nnz.min(1000) {
554 sum += (i as f64).tan(); }
556 }
557 _ => {
558 for i in 0..nnz.min(1000) {
560 sum += (i as f64).sqrt(); }
562 }
563 }
564
565 std::hint::black_box(sum);
567 Ok(())
568}
569
570fn calculate_cache_efficiency(measurements: &[PerformanceMeasurement]) -> f64 {
572 if measurements.len() < 2 {
573 return 0.5; }
575
576 let times: Vec<f64> = measurements
578 .iter()
579 .map(|m| m.duration.as_secs_f64())
580 .collect();
581 let mean = times.iter().sum::<f64>() / times.len() as f64;
582 let variance = times.iter().map(|t| (t - mean).powi(2)).sum::<f64>() / times.len() as f64;
583 let coefficient_of_variation = if mean > 0.0 {
584 variance.sqrt() / mean
585 } else {
586 1.0
587 };
588
589 (1.0 - coefficient_of_variation.min(1.0)).max(0.0)
591}
592
593fn estimate_cache_miss_ratio(sparse: &dyn SparseTensor) -> f64 {
595 let shape = sparse.shape();
596 let nnz = sparse.nnz();
597 let sparsity = 1.0 - (nnz as f64 / (shape.dims()[0] * shape.dims()[1]) as f64);
598
599 match sparse.format() {
601 SparseFormat::Csr => 0.1 + sparsity * 0.3, SparseFormat::Csc => 0.1 + sparsity * 0.3,
603 SparseFormat::Coo => 0.2 + sparsity * 0.5, _ => 0.15 + sparsity * 0.4, }
606}
607
608fn analyze_access_pattern(sparse: &dyn SparseTensor) -> MemoryAccessPattern {
610 let shape = sparse.shape();
611 let nnz = sparse.nnz();
612
613 match sparse.format() {
614 SparseFormat::Csr => {
615 if nnz < shape.dims()[0] * 2 {
617 MemoryAccessPattern::Random
618 } else {
619 MemoryAccessPattern::Sequential
620 }
621 }
622 SparseFormat::Csc => {
623 if nnz < shape.dims()[1] * 2 {
625 MemoryAccessPattern::Random
626 } else {
627 MemoryAccessPattern::Sequential
628 }
629 }
630 SparseFormat::Coo => {
631 MemoryAccessPattern::Random
633 }
634 _ => {
635 MemoryAccessPattern::Random
637 }
638 }
639}
640
641fn generate_cache_recommendations(
643 efficiency_score: f64,
644 access_pattern: &MemoryAccessPattern,
645) -> Vec<String> {
646 let mut recommendations = Vec::new();
647
648 if efficiency_score < 0.5 {
649 recommendations.push("Consider reordering data to improve cache locality".to_string());
650 }
651
652 match access_pattern {
653 MemoryAccessPattern::Random => {
654 recommendations.push(
655 "Random access pattern detected - consider data layout optimization".to_string(),
656 );
657 recommendations.push("Try blocking algorithms to improve spatial locality".to_string());
658 }
659 MemoryAccessPattern::Sequential => {
660 recommendations
661 .push("Good sequential access pattern - consider prefetching".to_string());
662 }
663 MemoryAccessPattern::Strided { stride } => {
664 if *stride > 8 {
665 recommendations.push(format!(
666 "Large stride ({}) detected - consider data reordering",
667 stride
668 ));
669 }
670 }
671 MemoryAccessPattern::Blocked { .. } => {
672 recommendations
673 .push("Blocked access pattern - ensure block size matches cache line".to_string());
674 }
675 MemoryAccessPattern::Mixed => {
676 recommendations
677 .push("Mixed access pattern - consider hybrid optimization strategies".to_string());
678 }
679 }
680
681 if recommendations.is_empty() {
682 recommendations.push("Cache performance appears optimal".to_string());
683 }
684
685 recommendations
686}
687
688pub fn generate_memory_optimization_recommendations(
693 memory_analyses: &[MemoryAnalysis],
694 total_memory_budget: Option<usize>,
695 target_operations: &[String],
696) -> Vec<String> {
697 let mut recommendations = Vec::new();
698
699 if memory_analyses.is_empty() {
700 recommendations.push("No memory analysis data available for recommendations".to_string());
701 return recommendations;
702 }
703
704 let avg_compression_ratio: f32 = memory_analyses
706 .iter()
707 .map(|analysis| analysis.compression_ratio)
708 .sum::<f32>()
709 / memory_analyses.len() as f32;
710
711 let format_distribution = get_format_distribution(memory_analyses);
713
714 if avg_compression_ratio < 2.0 {
716 recommendations.push(
717 "Low compression ratios detected: Consider switching to more memory-efficient sparse formats".to_string()
718 );
719
720 if format_distribution.get(&SparseFormat::Coo).unwrap_or(&0) > &0 {
722 recommendations.push(
723 "COO format detected with low compression: Consider CSR/CSC for better memory efficiency".to_string()
724 );
725 }
726 } else if avg_compression_ratio > 10.0 {
727 recommendations.push(
728 "Excellent compression ratios achieved: Current format choices are optimal".to_string(),
729 );
730 }
731
732 let high_overhead_count = memory_analyses
734 .iter()
735 .filter(|analysis| analysis.overhead_per_nnz > 16.0)
736 .count();
737
738 if high_overhead_count > 0 {
739 let percentage = (high_overhead_count * 100) / memory_analyses.len();
740 recommendations.push(format!(
741 "High memory overhead detected in {}% of tensors: Consider hybrid sparse formats",
742 percentage
743 ));
744
745 recommendations.push(
747 "For matrices with mixed sparsity patterns, consider HybridTensor format".to_string(),
748 );
749 }
750
751 if let Some(budget) = total_memory_budget {
753 let total_estimated_memory: usize = memory_analyses
754 .iter()
755 .map(|analysis| analysis.estimated_memory)
756 .sum();
757
758 let memory_utilization = (total_estimated_memory as f64) / (budget as f64);
759
760 if memory_utilization > 0.8 {
761 recommendations.push(
762 "Memory utilization approaching budget limit: Consider more aggressive compression"
763 .to_string(),
764 );
765 recommendations.push(
766 "Recommend enabling memory-efficient storage options or reducing precision"
767 .to_string(),
768 );
769 } else if memory_utilization < 0.3 {
770 recommendations.push(
771 "Memory utilization is low: Consider using less compressed formats for better performance".to_string()
772 );
773 }
774 }
775
776 for operation in target_operations {
778 match operation.as_str() {
779 "matmul" | "matrix_multiplication" => {
780 recommendations.push(
781 "For matrix multiplication: CSR format recommended for sparse-dense operations"
782 .to_string(),
783 );
784 }
785 "transpose" => {
786 recommendations.push(
787 "For transpose operations: Consider maintaining both CSR and CSC representations".to_string()
788 );
789 }
790 "element_access" => {
791 recommendations.push(
792 "For element access: COO format provides fastest random access".to_string(),
793 );
794 }
795 "reduction" => {
796 recommendations.push(
797 "For reduction operations: CSR/CSC formats optimize sequential access patterns"
798 .to_string(),
799 );
800 }
801 _ => {}
802 }
803 }
804
805 let sparsity_variance = calculate_sparsity_variance(memory_analyses);
807 if sparsity_variance > 0.1 {
808 recommendations.push(
809 "High sparsity variance detected: Consider adaptive format selection per tensor"
810 .to_string(),
811 );
812 }
813
814 let total_memory_usage: usize = memory_analyses.iter().map(|a| a.estimated_memory).sum();
816 if total_memory_usage > 100 * 1024 * 1024 {
817 recommendations.push(
819 "Large memory usage detected: Consider memory pooling for better allocation efficiency"
820 .to_string(),
821 );
822 }
823
824 recommendations
825}
826
827fn get_format_distribution(memory_analyses: &[MemoryAnalysis]) -> HashMap<SparseFormat, usize> {
829 let mut distribution = HashMap::new();
830 for analysis in memory_analyses {
831 *distribution.entry(analysis.format).or_insert(0) += 1;
832 }
833 distribution
834}
835
836fn calculate_sparsity_variance(memory_analyses: &[MemoryAnalysis]) -> f32 {
838 if memory_analyses.len() < 2 {
839 return 0.0;
840 }
841
842 let sparsity_levels: Vec<f32> = memory_analyses
843 .iter()
844 .map(|analysis| {
845 let total_elements = analysis.matrix_dimensions.0 * analysis.matrix_dimensions.1;
846 1.0 - (analysis.nnz as f32 / total_elements as f32)
847 })
848 .collect();
849
850 let mean_sparsity = sparsity_levels.iter().sum::<f32>() / sparsity_levels.len() as f32;
851
852 let variance = sparsity_levels
853 .iter()
854 .map(|&sparsity| (sparsity - mean_sparsity).powi(2))
855 .sum::<f32>()
856 / sparsity_levels.len() as f32;
857
858 variance.sqrt()
859}
860
861fn get_current_memory_usage() -> usize {
863 #[cfg(target_os = "linux")]
866 {
867 64 * 1024 * 1024 }
870 #[cfg(target_os = "macos")]
871 {
872 64 * 1024 * 1024 }
875 #[cfg(target_os = "windows")]
876 {
877 64 * 1024 * 1024 }
880 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
881 {
882 64 * 1024 * 1024 }
884}
885
886#[cfg(test)]
887mod tests {
888 use super::*;
889 use crate::CooTensor;
890 use torsh_core::Shape;
891
892 fn create_test_sparse_tensor() -> CooTensor {
893 let row_indices = vec![0, 1, 2, 10, 20, 30, 40, 50, 60, 70];
896 let col_indices = vec![0, 1, 2, 10, 20, 30, 40, 50, 60, 70];
897 let values = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
898 let shape = Shape::new(vec![100, 100]);
899 CooTensor::new(row_indices, col_indices, values, shape).expect("Coo Tensor should succeed")
900 }
901
902 #[test]
903 fn test_memory_analysis_creation() {
904 let analysis = MemoryAnalysis::new(SparseFormat::Coo, 100, (1000, 1000));
905
906 assert_eq!(analysis.format, SparseFormat::Coo);
907 assert_eq!(analysis.nnz, 100);
908 assert_eq!(analysis.matrix_dimensions, (1000, 1000));
909 assert_eq!(analysis.estimated_memory, 0);
910 assert_eq!(analysis.compression_ratio, 1.0);
911 }
912
913 #[test]
914 fn test_memory_analysis_metrics() {
915 let mut analysis = MemoryAnalysis::new(SparseFormat::Coo, 100, (1000, 1000));
916 analysis.estimated_memory = 1000;
917 analysis.dense_memory = 4000000; analysis.compression_ratio =
919 analysis.dense_memory as f32 / analysis.estimated_memory as f32;
920
921 assert_eq!(analysis.compression_ratio, 4000.0);
922 assert!(analysis.is_memory_efficient());
923 assert_eq!(analysis.memory_efficiency_rating(), "Excellent");
924 assert_eq!(analysis.sparsity_ratio(), 0.9999); }
926
927 #[test]
928 fn test_memory_breakdown() {
929 let mut breakdown = MemoryBreakdown::default();
930 breakdown.values_memory = 400;
931 breakdown.indices_memory = 800;
932 breakdown.structure_memory = 100;
933 breakdown.metadata_memory = 32;
934
935 assert_eq!(breakdown.total_memory(), 1332);
936
937 let distribution = breakdown.memory_distribution();
938 assert!((distribution["values"] - 30.03).abs() < 0.1); assert!((distribution["indices"] - 60.06).abs() < 0.1); }
941
942 #[test]
943 fn test_memory_tracker() {
944 let mut tracker = MemoryTracker::new();
945
946 let result = tracker.track_operation(|| -> TorshResult<i32> {
948 std::thread::sleep(Duration::from_millis(1));
950 Ok(42)
951 });
952
953 assert!(result.is_ok());
954 let (value, usage_result) = result.expect("operation should succeed");
955 assert_eq!(value, 42);
956 assert!(usage_result.samples.len() >= 2);
957 }
958
959 #[test]
960 fn test_memory_usage_result() {
961 let result = MemoryUsageResult {
962 baseline_memory: 1000,
963 peak_memory: 1500,
964 final_memory: 1200,
965 memory_delta: 200,
966 peak_delta: 500,
967 samples: Vec::new(),
968 };
969
970 assert!(result.has_potential_leak(100)); assert!(!result.has_potential_leak(300)); let efficiency = result.efficiency_score();
974 assert!((efficiency - 0.4).abs() < 0.01); }
976
977 #[test]
978 fn test_analyze_sparse_memory() {
979 let sparse_tensor = create_test_sparse_tensor();
980 let analysis = analyze_sparse_memory(&sparse_tensor);
981
982 assert!(analysis.is_ok());
983 let analysis = analysis.expect("operation should succeed");
984
985 assert_eq!(analysis.format, SparseFormat::Coo);
986 assert_eq!(analysis.nnz, 10);
987 assert_eq!(analysis.matrix_dimensions, (100, 100));
988 assert!(analysis.compression_ratio > 1.0);
989 }
990
991 #[test]
992 fn test_calculate_memory_breakdown_coo() {
993 let sparse_tensor = create_test_sparse_tensor();
994 let breakdown = calculate_memory_breakdown(&sparse_tensor);
995
996 assert!(breakdown.is_ok());
997 let breakdown = breakdown.expect("operation should succeed");
998
999 assert_eq!(breakdown.values_memory, 40); assert_eq!(breakdown.indices_memory, 80); assert_eq!(breakdown.structure_memory, 0);
1003 assert_eq!(breakdown.metadata_memory, 32);
1004 }
1005
1006 #[test]
1007 fn test_cache_performance_analysis() {
1008 let sparse_tensor = create_test_sparse_tensor();
1009 let result = benchmark_cache_performance(&sparse_tensor, "test_operation".to_string());
1010
1011 assert!(result.is_ok());
1012 let result = result.expect("operation should succeed");
1013
1014 assert_eq!(result.operation, "test_operation");
1015 assert_eq!(result.measurements.len(), 5);
1016 assert!(result.cache_efficiency_score >= 0.0 && result.cache_efficiency_score <= 1.0);
1017 assert!(!result.recommendations.is_empty());
1018 }
1019
1020 #[test]
1021 fn test_memory_access_patterns() {
1022 use MemoryAccessPattern::*;
1023
1024 let sequential = Sequential;
1025 let random = Random;
1026 let strided = Strided { stride: 4 };
1027 let blocked = Blocked { block_size: 64 };
1028 let mixed = Mixed;
1029
1030 assert_eq!(format!("{}", sequential), "Sequential");
1031 assert_eq!(format!("{}", random), "Random");
1032 assert_eq!(format!("{}", strided), "Strided (stride: 4)");
1033 assert_eq!(format!("{}", blocked), "Blocked (block size: 64)");
1034 assert_eq!(format!("{}", mixed), "Mixed");
1035 }
1036
1037 #[test]
1038 fn test_cache_efficiency_calculation() {
1039 let mut measurements = Vec::new();
1040
1041 for i in 0..5 {
1043 let mut measurement = PerformanceMeasurement::new(format!("test_{}", i));
1044 measurement.duration = Duration::from_millis(100); measurements.push(measurement);
1046 }
1047
1048 let efficiency = calculate_cache_efficiency(&measurements);
1049 assert!(efficiency > 0.8); let mut outlier = PerformanceMeasurement::new("outlier".to_string());
1053 outlier.duration = Duration::from_millis(500); measurements.push(outlier);
1055
1056 let efficiency_with_outlier = calculate_cache_efficiency(&measurements);
1057 assert!(efficiency_with_outlier < efficiency); }
1059
1060 #[test]
1061 fn test_generate_cache_recommendations() {
1062 let recommendations = generate_cache_recommendations(0.3, &MemoryAccessPattern::Random);
1064 assert!(recommendations.len() >= 2);
1065 assert!(recommendations.iter().any(|r| r.contains("cache locality")));
1066 assert!(recommendations
1067 .iter()
1068 .any(|r| r.contains("Random access pattern")));
1069
1070 let recommendations = generate_cache_recommendations(0.8, &MemoryAccessPattern::Sequential);
1072 assert!(recommendations
1073 .iter()
1074 .any(|r| r.contains("prefetching") || r.contains("optimal")));
1075 }
1076}