Skip to main content

torsh_sparse/performance_tools/
memory.rs

1//! # Memory Analysis and Compression Tracking
2//!
3//! This module provides comprehensive memory analysis capabilities for sparse tensor operations,
4//! including memory usage tracking, compression ratio analysis, and cache performance evaluation.
5//!
6//! ## Key Components
7//!
8//! - **MemoryAnalysis**: Detailed memory usage analysis with compression metrics
9//! - **CachePerformanceResult**: Cache efficiency analysis and recommendations
10//! - **Memory tracking utilities**: Tools for monitoring memory usage patterns
11//!
12//! ## Usage Example
13//!
14//! ```rust,ignore
15//! use torsh_sparse::performance_tools::memory::{analyze_sparse_memory, track_memory_usage};
16//!
17//! // Analyze memory characteristics of a sparse tensor
18//! let analysis = analyze_sparse_memory(&sparse_tensor)?;
19//! println!("Compression ratio: {:.2}x", analysis.compression_ratio);
20//!
21//! // Track memory usage during operations
22//! let tracker = MemoryTracker::new();
23//! let usage = tracker.track_operation(|| perform_sparse_operation())?;
24//! ```
25
26// Framework infrastructure - components designed for future use
27#![allow(dead_code)]
28use crate::{SparseFormat, SparseTensor, TorshResult};
29use std::collections::HashMap;
30use std::time::{Duration, Instant};
31
32use super::core::PerformanceMeasurement;
33
34/// Comprehensive memory analysis result for sparse tensors
35///
36/// This struct provides detailed information about memory usage patterns,
37/// compression characteristics, and efficiency metrics for sparse tensor storage.
38#[derive(Debug, Clone)]
39pub struct MemoryAnalysis {
40    /// Sparse format being analyzed
41    pub format: SparseFormat,
42    /// Number of non-zero elements
43    pub nnz: usize,
44    /// Estimated memory usage for sparse representation (bytes)
45    pub estimated_memory: usize,
46    /// Memory usage if stored as dense tensor (bytes)
47    pub dense_memory: usize,
48    /// Compression ratio (dense_memory / sparse_memory)
49    pub compression_ratio: f32,
50    /// Memory overhead per non-zero element (bytes)
51    pub overhead_per_nnz: f32,
52    /// Matrix dimensions (rows, columns)
53    pub matrix_dimensions: (usize, usize),
54    /// Memory breakdown by component
55    pub memory_breakdown: MemoryBreakdown,
56    /// Additional memory metrics
57    pub metrics: HashMap<String, f64>,
58}
59
60/// Detailed breakdown of memory usage by component
61#[derive(Debug, Clone)]
62pub struct MemoryBreakdown {
63    /// Memory used by value storage
64    pub values_memory: usize,
65    /// Memory used by index storage
66    pub indices_memory: usize,
67    /// Memory used by structural information (row pointers, etc.)
68    pub structure_memory: usize,
69    /// Additional metadata memory
70    pub metadata_memory: usize,
71}
72
73impl MemoryAnalysis {
74    /// Create a new memory analysis result
75    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    /// Calculate compression effectiveness score (0-1, higher is better)
90    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    /// Get sparsity ratio (percentage of zero elements)
98    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    /// Check if sparse representation is memory efficient
107    pub fn is_memory_efficient(&self) -> bool {
108        self.compression_ratio > 2.0 // At least 2x compression
109    }
110
111    /// Get memory efficiency rating (Poor, Fair, Good, Excellent)
112    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    /// Add a custom memory metric
122    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    /// Calculate total memory usage
140    pub fn total_memory(&self) -> usize {
141        self.values_memory + self.indices_memory + self.structure_memory + self.metadata_memory
142    }
143
144    /// Get memory distribution as percentages
145    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/// Cache performance analysis result
173#[derive(Debug, Clone)]
174pub struct CachePerformanceResult {
175    /// Operation being analyzed
176    pub operation: String,
177    /// Performance measurements during cache analysis
178    pub measurements: Vec<PerformanceMeasurement>,
179    /// Cache efficiency score (0-1, higher is better)
180    pub cache_efficiency_score: f64,
181    /// Optimization recommendations
182    pub recommendations: Vec<String>,
183    /// Cache miss ratio estimation
184    pub cache_miss_ratio: f64,
185    /// Memory access pattern analysis
186    pub access_pattern: MemoryAccessPattern,
187}
188
189/// Memory access pattern classification
190#[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/// Memory usage tracker for monitoring operations
214#[derive(Debug)]
215pub struct MemoryTracker {
216    /// Initial memory usage
217    baseline_memory: usize,
218    /// Peak memory usage
219    peak_memory: usize,
220    /// Current memory usage
221    current_memory: usize,
222    /// Memory samples taken during tracking
223    samples: Vec<(Instant, usize)>,
224    /// Sampling interval
225    sampling_interval: Duration,
226}
227
228impl Default for MemoryTracker {
229    fn default() -> Self {
230        Self::new()
231    }
232}
233
234impl MemoryTracker {
235    /// Create a new memory tracker
236    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    /// Track memory usage during operation execution
248    pub fn track_operation<F, R>(&mut self, operation: F) -> TorshResult<(R, MemoryUsageResult)>
249    where
250        F: FnOnce() -> TorshResult<R>,
251    {
252        // Reset tracking state
253        self.reset();
254
255        // Start tracking
256        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        // Add initial sample
263        self.add_sample(start_time, start_memory);
264
265        // Execute operation with periodic memory sampling
266        let result = operation()?;
267
268        // Final memory reading
269        let end_memory = get_current_memory_usage();
270        self.current_memory = end_memory;
271        self.add_sample(Instant::now(), end_memory);
272
273        // Analyze results
274        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    /// Reset tracking state
287    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    /// Add a memory sample
295    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    /// Get current memory growth rate (bytes per second)
303    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/// Result of memory usage tracking
323#[derive(Debug, Clone)]
324pub struct MemoryUsageResult {
325    /// Memory usage at start of operation
326    pub baseline_memory: usize,
327    /// Peak memory usage during operation
328    pub peak_memory: usize,
329    /// Memory usage at end of operation
330    pub final_memory: usize,
331    /// Net memory change (can be negative)
332    pub memory_delta: i64,
333    /// Peak memory increase from baseline
334    pub peak_delta: usize,
335    /// Time-series memory samples
336    pub samples: Vec<(Instant, usize)>,
337}
338
339impl MemoryUsageResult {
340    /// Check if there was a memory leak (final > baseline by significant amount)
341    pub fn has_potential_leak(&self, threshold_bytes: usize) -> bool {
342        self.memory_delta > threshold_bytes as i64
343    }
344
345    /// Get memory efficiency score (0-1, lower peak usage is better)
346    pub fn efficiency_score(&self) -> f64 {
347        if self.peak_delta == 0 {
348            return 1.0;
349        }
350
351        // Score based on how much peak exceeded the final delta
352        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
361/// Analyze memory characteristics of a sparse tensor
362pub 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    // Calculate memory usage based on format
370    analysis.memory_breakdown = calculate_memory_breakdown(sparse)?;
371    analysis.estimated_memory = analysis.memory_breakdown.total_memory();
372
373    // Calculate dense memory equivalent
374    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, // Default to 4 bytes
380    };
381    analysis.dense_memory = shape.dims()[0] * shape.dims()[1] * element_size;
382
383    // Calculate derived metrics
384    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    // Add format-specific metrics
394    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
406/// Calculate memory breakdown for different sparse formats
407fn 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; // Assume 32-bit indices
420
421    let mut breakdown = MemoryBreakdown::default();
422
423    match sparse.format() {
424        SparseFormat::Coo => {
425            // COO format: values + row_indices + col_indices
426            breakdown.values_memory = nnz * element_size;
427            breakdown.indices_memory = nnz * 2 * index_size; // row and col indices
428            breakdown.structure_memory = 0;
429            breakdown.metadata_memory = 32; // Shape and other metadata
430        }
431        SparseFormat::Csr => {
432            // CSR format: values + col_indices + row_ptrs
433            breakdown.values_memory = nnz * element_size;
434            breakdown.indices_memory = nnz * index_size; // Column indices
435            breakdown.structure_memory = (shape.dims()[0] + 1) * index_size; // Row pointers
436            breakdown.metadata_memory = 32;
437        }
438        SparseFormat::Csc => {
439            // CSC format: values + row_indices + col_ptrs
440            breakdown.values_memory = nnz * element_size;
441            breakdown.indices_memory = nnz * index_size; // Row indices
442            breakdown.structure_memory = (shape.dims()[1] + 1) * index_size; // Column pointers
443            breakdown.metadata_memory = 32;
444        }
445        SparseFormat::Bsr => {
446            // BSR format: blocks + block_indices + block_ptrs
447            breakdown.values_memory = nnz * element_size;
448            breakdown.indices_memory = nnz * index_size; // Block indices
449            breakdown.structure_memory = nnz * index_size; // Block pointers
450            breakdown.metadata_memory = 64; // Block size + metadata
451        }
452        SparseFormat::Dia => {
453            // DIA format: data + offsets
454            breakdown.values_memory = nnz * element_size;
455            breakdown.indices_memory = 0; // Implicit indexing
456            breakdown.structure_memory = nnz * index_size; // Diagonal offsets
457            breakdown.metadata_memory = 32;
458        }
459        SparseFormat::Dsr => {
460            // DSR format: dynamic sparse row
461            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            // ELL format: column indices + values in fixed-width format
468            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            // RLE format: run-length encoded
475            breakdown.values_memory = nnz * element_size;
476            breakdown.indices_memory = nnz * index_size;
477            breakdown.structure_memory = nnz * index_size; // Run lengths
478            breakdown.metadata_memory = 32;
479        }
480        SparseFormat::Symmetric => {
481            // Symmetric format: stores only upper or lower triangle
482            breakdown.values_memory = nnz * element_size;
483            breakdown.indices_memory = nnz * index_size;
484            breakdown.structure_memory = nnz * index_size; // Triangle structure
485            breakdown.metadata_memory = 64; // Symmetry mode + metadata
486        }
487    }
488
489    Ok(breakdown)
490}
491
492/// Benchmark cache performance for sparse operations
493pub 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    // Simulate cache analysis through repeated operations
501    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
508        simulate_cache_sensitive_operation(sparse)?;
509
510        measurement.duration = start.elapsed();
511        measurements.push(measurement);
512    }
513
514    // Analyze cache performance
515    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
530/// Simulate a cache-sensitive operation for benchmarking
531fn simulate_cache_sensitive_operation(sparse: &dyn SparseTensor) -> TorshResult<()> {
532    // This is a simplified simulation - in practice, this would perform
533    // actual sparse operations with known cache access patterns
534    let nnz = sparse.nnz();
535    let mut sum = 0.0f64;
536
537    // Simulate memory access patterns based on format
538    match sparse.format() {
539        SparseFormat::Csr => {
540            // CSR has good row-wise cache locality
541            for i in 0..nnz.min(1000) {
542                sum += (i as f64).sin(); // Simulate some computation
543            }
544        }
545        SparseFormat::Csc => {
546            // CSC has good column-wise cache locality
547            for i in 0..nnz.min(1000) {
548                sum += (i as f64).cos(); // Simulate some computation
549            }
550        }
551        SparseFormat::Coo => {
552            // COO has less predictable cache behavior
553            for i in 0..nnz.min(1000) {
554                sum += (i as f64).tan(); // Simulate some computation
555            }
556        }
557        _ => {
558            // Default case for other formats
559            for i in 0..nnz.min(1000) {
560                sum += (i as f64).sqrt(); // Simulate some computation
561            }
562        }
563    }
564
565    // Prevent optimization
566    std::hint::black_box(sum);
567    Ok(())
568}
569
570/// Calculate cache efficiency score from measurements
571fn calculate_cache_efficiency(measurements: &[PerformanceMeasurement]) -> f64 {
572    if measurements.len() < 2 {
573        return 0.5; // Default middle score
574    }
575
576    // Lower variance in execution times suggests better cache performance
577    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    // Lower coefficient of variation = higher cache efficiency
590    (1.0 - coefficient_of_variation.min(1.0)).max(0.0)
591}
592
593/// Estimate cache miss ratio based on sparse tensor characteristics
594fn 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    // Higher sparsity typically leads to more cache misses
600    match sparse.format() {
601        SparseFormat::Csr => 0.1 + sparsity * 0.3, // CSR generally has better cache behavior
602        SparseFormat::Csc => 0.1 + sparsity * 0.3,
603        SparseFormat::Coo => 0.2 + sparsity * 0.5, // COO has less predictable access
604        _ => 0.15 + sparsity * 0.4,                // Default case for other formats
605    }
606}
607
608/// Analyze memory access pattern based on sparse format and structure
609fn 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            // CSR provides sequential access within rows
616            if nnz < shape.dims()[0] * 2 {
617                MemoryAccessPattern::Random
618            } else {
619                MemoryAccessPattern::Sequential
620            }
621        }
622        SparseFormat::Csc => {
623            // CSC provides sequential access within columns
624            if nnz < shape.dims()[1] * 2 {
625                MemoryAccessPattern::Random
626            } else {
627                MemoryAccessPattern::Sequential
628            }
629        }
630        SparseFormat::Coo => {
631            // COO access pattern depends on sorting
632            MemoryAccessPattern::Random
633        }
634        _ => {
635            // Default case for other formats
636            MemoryAccessPattern::Random
637        }
638    }
639}
640
641/// Generate cache optimization recommendations
642fn 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
688/// Generate comprehensive memory optimization recommendations
689///
690/// Analyzes memory usage patterns and provides specific recommendations for optimization
691/// based on tensor characteristics, access patterns, and system constraints.
692pub 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    // Analyze compression ratios across all tensors
705    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    // Format-specific recommendations
712    let format_distribution = get_format_distribution(memory_analyses);
713
714    // Compression ratio analysis
715    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        // Specific format recommendations
721        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    // Memory overhead analysis
733    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        // Suggest specific optimizations
746        recommendations.push(
747            "For matrices with mixed sparsity patterns, consider HybridTensor format".to_string(),
748        );
749    }
750
751    // Memory budget analysis
752    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    // Operation-specific recommendations
777    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    // Advanced optimization recommendations
806    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    // Memory fragmentation recommendations
815    let total_memory_usage: usize = memory_analyses.iter().map(|a| a.estimated_memory).sum();
816    if total_memory_usage > 100 * 1024 * 1024 {
817        // > 100MB
818        recommendations.push(
819            "Large memory usage detected: Consider memory pooling for better allocation efficiency"
820                .to_string(),
821        );
822    }
823
824    recommendations
825}
826
827/// Calculate distribution of sparse formats in the analysis
828fn 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
836/// Calculate variance in sparsity levels across tensors
837fn 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
861/// Get current memory usage (platform-specific implementation)
862fn get_current_memory_usage() -> usize {
863    // This is a simplified implementation - in practice, this would use
864    // platform-specific APIs to get actual memory usage
865    #[cfg(target_os = "linux")]
866    {
867        // Could parse /proc/self/statm or use libc calls
868        64 * 1024 * 1024 // 64MB placeholder
869    }
870    #[cfg(target_os = "macos")]
871    {
872        // Could use mach_task_basic_info
873        64 * 1024 * 1024 // 64MB placeholder
874    }
875    #[cfg(target_os = "windows")]
876    {
877        // Could use GetProcessMemoryInfo
878        64 * 1024 * 1024 // 64MB placeholder
879    }
880    #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
881    {
882        64 * 1024 * 1024 // 64MB placeholder
883    }
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        // Create a larger, more sparse matrix (100x100 with only 10 non-zero elements)
894        // This will have a good compression ratio
895        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; // 1000*1000*4 bytes
918        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); // 99.99% sparse
925    }
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); // 400/1332 ≈ 30.03%
939        assert!((distribution["indices"] - 60.06).abs() < 0.1); // 800/1332 ≈ 60.06%
940    }
941
942    #[test]
943    fn test_memory_tracker() {
944        let mut tracker = MemoryTracker::new();
945
946        // Test tracking a simple operation
947        let result = tracker.track_operation(|| -> TorshResult<i32> {
948            // Simulate some work
949            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)); // 200 > 100, so leak detected
971        assert!(!result.has_potential_leak(300)); // 200 < 300, so no leak
972
973        let efficiency = result.efficiency_score();
974        assert!((efficiency - 0.4).abs() < 0.01); // 200/500 = 0.4
975    }
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        // COO format: 10 values * 4 bytes + 10*2 indices * 4 bytes + metadata
1000        assert_eq!(breakdown.values_memory, 40); // 10 * 4
1001        assert_eq!(breakdown.indices_memory, 80); // 10 * 2 * 4 (32-bit indices)
1002        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        // Add measurements with consistent timing (good cache performance)
1042        for i in 0..5 {
1043            let mut measurement = PerformanceMeasurement::new(format!("test_{}", i));
1044            measurement.duration = Duration::from_millis(100); // Consistent timing
1045            measurements.push(measurement);
1046        }
1047
1048        let efficiency = calculate_cache_efficiency(&measurements);
1049        assert!(efficiency > 0.8); // Should be high due to consistent timing
1050
1051        // Add measurement with very different timing (poor cache performance)
1052        let mut outlier = PerformanceMeasurement::new("outlier".to_string());
1053        outlier.duration = Duration::from_millis(500); // Much slower
1054        measurements.push(outlier);
1055
1056        let efficiency_with_outlier = calculate_cache_efficiency(&measurements);
1057        assert!(efficiency_with_outlier < efficiency); // Should be lower due to variance
1058    }
1059
1060    #[test]
1061    fn test_generate_cache_recommendations() {
1062        // Test recommendations for poor cache performance
1063        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        // Test recommendations for good cache performance
1071        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}