scirs2_sparse/adaptive_memory_compression/
stats.rs

1//! Statistics and metadata tracking for adaptive memory compression
2//!
3//! This module contains structures for tracking compression performance,
4//! memory usage, and access patterns to guide optimization decisions.
5
6use super::config::CompressionAlgorithm;
7
8/// Statistics for compression operations
9#[derive(Debug, Default, Clone)]
10pub struct CompressionStats {
11    pub total_blocks: usize,
12    pub compressed_blocks: usize,
13    pub total_uncompressed_size: usize,
14    pub total_compressed_size: usize,
15    pub compression_ratio: f64,
16    pub compression_time: f64,
17    pub decompression_time: f64,
18    pub cache_hits: usize,
19    pub cache_misses: usize,
20    pub out_of_core_reads: usize,
21    pub out_of_core_writes: usize,
22}
23
24/// Compression metadata
25#[derive(Debug, Clone)]
26pub struct CompressionMetadata {
27    pub original_size: usize,
28    pub compressed_size: usize,
29    pub compression_ratio: f64,
30    pub compression_time: f64,
31}
32
33/// Memory usage statistics
34#[derive(Debug)]
35pub struct MemoryStats {
36    pub total_memory_budget: usize,
37    pub current_memory_usage: usize,
38    pub memory_usage_ratio: f64,
39    pub compression_stats: CompressionStats,
40    pub cache_hits: usize,
41    pub cache_misses: usize,
42    pub cache_hit_ratio: f64,
43    pub out_of_core_enabled: bool,
44}
45
46/// Cache statistics
47#[derive(Debug, Clone)]
48pub(crate) struct CacheStats {
49    pub hits: usize,
50    pub misses: usize,
51    pub hit_ratio: f64,
52    pub total_size_bytes: usize,
53    pub max_size_bytes: usize,
54    pub utilization: f64,
55}
56
57/// Compression strategy performance tracking
58#[derive(Debug)]
59pub(crate) struct CompressionStrategy {
60    pub algorithm: CompressionAlgorithm,
61    pub block_size: usize,
62    pub hierarchical: bool,
63    pub predicted_ratio: f64,
64    pub actual_ratio: f64,
65    pub compression_speed: f64,
66    pub decompression_speed: f64,
67}
68
69/// Sparsity pattern analysis
70#[derive(Debug, Default, Clone)]
71pub(crate) struct SparsityPatternAnalysis {
72    pub avg_nnz_per_row: f64,
73    pub max_nnz_per_row: usize,
74    pub min_nnz_per_row: usize,
75    pub sequential_patterns: usize,
76    pub clustering_factor: f64,
77    pub bandwidth: usize,
78    pub diagonal_dominance: f64,
79    pub fill_ratio: f64,
80}
81
82/// Access pattern information
83#[derive(Debug, Default, Clone)]
84pub(crate) struct AccessPatternInfo {
85    pub total_accesses: usize,
86    pub avg_temporal_locality: f64,
87    pub avg_spatial_locality: f64,
88    pub pattern_count: usize,
89    pub sequential_access_ratio: f64,
90    pub random_access_ratio: f64,
91}
92
93/// Performance metrics for different compression algorithms
94#[derive(Debug, Clone)]
95pub struct AlgorithmPerformanceMetrics {
96    pub algorithm: CompressionAlgorithm,
97    pub avg_compression_ratio: f64,
98    pub avg_compression_time: f64,
99    pub avg_decompression_time: f64,
100    pub success_rate: f64,
101    pub memory_overhead: f64,
102    pub sample_count: usize,
103}
104
105/// Comprehensive performance summary
106#[derive(Debug)]
107pub struct PerformanceSummary {
108    pub total_operations: usize,
109    pub total_compressed_bytes: usize,
110    pub total_uncompressed_bytes: usize,
111    pub overall_compression_ratio: f64,
112    pub cache_performance: CachePerformance,
113    pub algorithm_metrics: Vec<AlgorithmPerformanceMetrics>,
114    pub memory_efficiency: MemoryEfficiency,
115    pub access_patterns: AccessPatternSummary,
116}
117
118/// Cache performance metrics
119#[derive(Debug, Clone)]
120pub struct CachePerformance {
121    pub total_requests: usize,
122    pub cache_hits: usize,
123    pub cache_misses: usize,
124    pub hit_rate: f64,
125    pub avg_response_time_us: f64,
126    pub eviction_count: usize,
127    pub prefetch_accuracy: f64,
128}
129
130/// Memory efficiency metrics
131#[derive(Debug, Clone)]
132pub struct MemoryEfficiency {
133    pub memory_budget_utilization: f64,
134    pub compression_space_savings: f64,
135    pub fragmentation_ratio: f64,
136    pub out_of_core_usage: f64,
137    pub memory_pressure_events: usize,
138}
139
140/// Access pattern summary
141#[derive(Debug, Clone)]
142pub struct AccessPatternSummary {
143    pub predominant_pattern: AccessPatternType,
144    pub temporal_locality_score: f64,
145    pub spatial_locality_score: f64,
146    pub predictability_score: f64,
147    pub hotspot_concentration: f64,
148}
149
150/// Type of access pattern
151#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
152pub enum AccessPatternType {
153    Sequential,
154    Random,
155    Clustered,
156    Mixed,
157    #[default]
158    Unknown,
159}
160
161impl CompressionStats {
162    /// Create new compression statistics
163    pub fn new() -> Self {
164        Self::default()
165    }
166
167    /// Update statistics with a new compression operation
168    pub fn update_compression(
169        &mut self,
170        original_size: usize,
171        compressed_size: usize,
172        compression_time: f64,
173    ) {
174        self.total_blocks += 1;
175        self.compressed_blocks += 1;
176        self.total_uncompressed_size += original_size;
177        self.total_compressed_size += compressed_size;
178        self.compression_time += compression_time;
179
180        // Update compression ratio
181        self.compression_ratio = if self.total_uncompressed_size > 0 {
182            self.total_compressed_size as f64 / self.total_uncompressed_size as f64
183        } else {
184            1.0
185        };
186    }
187
188    /// Update decompression statistics
189    pub fn update_decompression(&mut self, decompression_time: f64) {
190        self.decompression_time += decompression_time;
191    }
192
193    /// Record cache hit
194    pub fn record_cache_hit(&mut self) {
195        self.cache_hits += 1;
196    }
197
198    /// Record cache miss
199    pub fn record_cache_miss(&mut self) {
200        self.cache_misses += 1;
201    }
202
203    /// Record out-of-core read
204    pub fn record_out_of_core_read(&mut self) {
205        self.out_of_core_reads += 1;
206    }
207
208    /// Record out-of-core write
209    pub fn record_out_of_core_write(&mut self) {
210        self.out_of_core_writes += 1;
211    }
212
213    /// Get cache hit ratio
214    pub fn cache_hit_ratio(&self) -> f64 {
215        let total_accesses = self.cache_hits + self.cache_misses;
216        if total_accesses > 0 {
217            self.cache_hits as f64 / total_accesses as f64
218        } else {
219            0.0
220        }
221    }
222
223    /// Get average compression time per block
224    pub fn avg_compression_time(&self) -> f64 {
225        if self.compressed_blocks > 0 {
226            self.compression_time / self.compressed_blocks as f64
227        } else {
228            0.0
229        }
230    }
231
232    /// Get average decompression time per block
233    pub fn avg_decompression_time(&self) -> f64 {
234        if self.compressed_blocks > 0 {
235            self.decompression_time / self.compressed_blocks as f64
236        } else {
237            0.0
238        }
239    }
240
241    /// Get compression efficiency (inverse of time)
242    pub fn compression_efficiency(&self) -> f64 {
243        let avg_time = self.avg_compression_time();
244        if avg_time > 0.0 {
245            1.0 / avg_time
246        } else {
247            0.0
248        }
249    }
250
251    /// Get space savings in bytes
252    pub fn space_savings(&self) -> usize {
253        self.total_uncompressed_size
254            .saturating_sub(self.total_compressed_size)
255    }
256
257    /// Get space savings ratio
258    pub fn space_savings_ratio(&self) -> f64 {
259        if self.total_uncompressed_size > 0 {
260            self.space_savings() as f64 / self.total_uncompressed_size as f64
261        } else {
262            0.0
263        }
264    }
265
266    /// Reset statistics
267    pub fn reset(&mut self) {
268        *self = Self::default();
269    }
270
271    /// Merge with another stats instance
272    pub fn merge(&mut self, other: &CompressionStats) {
273        self.total_blocks += other.total_blocks;
274        self.compressed_blocks += other.compressed_blocks;
275        self.total_uncompressed_size += other.total_uncompressed_size;
276        self.total_compressed_size += other.total_compressed_size;
277        self.compression_time += other.compression_time;
278        self.decompression_time += other.decompression_time;
279        self.cache_hits += other.cache_hits;
280        self.cache_misses += other.cache_misses;
281        self.out_of_core_reads += other.out_of_core_reads;
282        self.out_of_core_writes += other.out_of_core_writes;
283
284        // Recalculate compression ratio
285        self.compression_ratio = if self.total_uncompressed_size > 0 {
286            self.total_compressed_size as f64 / self.total_uncompressed_size as f64
287        } else {
288            1.0
289        };
290    }
291}
292
293impl CompressionMetadata {
294    /// Create new compression metadata
295    pub fn new(original_size: usize, compressed_size: usize, compression_time: f64) -> Self {
296        let compression_ratio = if original_size > 0 {
297            compressed_size as f64 / original_size as f64
298        } else {
299            1.0
300        };
301
302        Self {
303            original_size,
304            compressed_size,
305            compression_ratio,
306            compression_time,
307        }
308    }
309
310    /// Get space savings in bytes
311    pub fn space_savings(&self) -> usize {
312        self.original_size.saturating_sub(self.compressed_size)
313    }
314
315    /// Get space savings ratio
316    pub fn space_savings_ratio(&self) -> f64 {
317        if self.original_size > 0 {
318            self.space_savings() as f64 / self.original_size as f64
319        } else {
320            0.0
321        }
322    }
323
324    /// Get compression speed (bytes per second)
325    pub fn compression_speed(&self) -> f64 {
326        if self.compression_time > 0.0 {
327            self.original_size as f64 / self.compression_time
328        } else {
329            0.0
330        }
331    }
332
333    /// Check if compression was effective
334    pub fn is_effective(&self, threshold: f64) -> bool {
335        self.compression_ratio < threshold
336    }
337}
338
339impl MemoryStats {
340    /// Create new memory statistics
341    pub fn new(memory_budget: usize, out_of_core_enabled: bool) -> Self {
342        Self {
343            total_memory_budget: memory_budget,
344            current_memory_usage: 0,
345            memory_usage_ratio: 0.0,
346            compression_stats: CompressionStats::default(),
347            cache_hits: 0,
348            cache_misses: 0,
349            cache_hit_ratio: 0.0,
350            out_of_core_enabled,
351        }
352    }
353
354    /// Update memory usage
355    pub fn update_memory_usage(&mut self, current_usage: usize) {
356        self.current_memory_usage = current_usage;
357        self.memory_usage_ratio = if self.total_memory_budget > 0 {
358            current_usage as f64 / self.total_memory_budget as f64
359        } else {
360            0.0
361        };
362    }
363
364    /// Update cache statistics
365    pub fn update_cache_stats(&mut self, hits: usize, misses: usize) {
366        self.cache_hits = hits;
367        self.cache_misses = misses;
368        let total = hits + misses;
369        self.cache_hit_ratio = if total > 0 {
370            hits as f64 / total as f64
371        } else {
372            0.0
373        };
374    }
375
376    /// Check if memory pressure exists
377    pub fn has_memory_pressure(&self, threshold: f64) -> bool {
378        self.memory_usage_ratio > threshold
379    }
380
381    /// Get available memory
382    pub fn available_memory(&self) -> usize {
383        self.total_memory_budget
384            .saturating_sub(self.current_memory_usage)
385    }
386
387    /// Get memory efficiency score
388    pub fn memory_efficiency_score(&self) -> f64 {
389        let compression_benefit = self.compression_stats.space_savings_ratio();
390        let cache_benefit = self.cache_hit_ratio;
391        let usage_efficiency = 1.0 - self.memory_usage_ratio.min(1.0);
392
393        (compression_benefit + cache_benefit + usage_efficiency) / 3.0
394    }
395}
396
397impl SparsityPatternAnalysis {
398    /// Create a new sparsity pattern analysis
399    pub fn new() -> Self {
400        Self::default()
401    }
402
403    /// Analyze sparsity pattern from matrix data
404    pub fn analyze_pattern(
405        &mut self,
406        rows: usize,
407        cols: usize,
408        indptr: &[usize],
409        indices: &[usize],
410    ) {
411        let nnz = indices.len();
412        self.fill_ratio = nnz as f64 / (rows * cols) as f64;
413
414        // Calculate per-row statistics
415        let mut nnz_per_row = Vec::new();
416        for i in 0..rows {
417            if i + 1 < indptr.len() {
418                let row_nnz = indptr[i + 1] - indptr[i];
419                nnz_per_row.push(row_nnz);
420            }
421        }
422
423        if !nnz_per_row.is_empty() {
424            self.avg_nnz_per_row =
425                nnz_per_row.iter().sum::<usize>() as f64 / nnz_per_row.len() as f64;
426            self.max_nnz_per_row = *nnz_per_row.iter().max().unwrap();
427            self.min_nnz_per_row = *nnz_per_row.iter().min().unwrap();
428        }
429
430        // Calculate bandwidth (simplified)
431        if !indices.is_empty() {
432            let min_col = *indices.iter().min().unwrap();
433            let max_col = *indices.iter().max().unwrap();
434            self.bandwidth = max_col - min_col + 1;
435        }
436
437        // Analyze sequential patterns
438        self.sequential_patterns = self.count_sequential_patterns(indptr, indices);
439
440        // Calculate clustering factor (simplified)
441        self.clustering_factor = self.calculate_clustering_factor(indptr, indices);
442
443        // Calculate diagonal dominance
444        self.diagonal_dominance = self.calculate_diagonal_dominance(rows, indptr, indices);
445    }
446
447    /// Count sequential access patterns
448    fn count_sequential_patterns(&self, indptr: &[usize], indices: &[usize]) -> usize {
449        let mut sequential_count = 0;
450
451        for i in 0..indptr.len().saturating_sub(1) {
452            let start = indptr[i];
453            let end = indptr[i + 1];
454
455            if end > start + 1 {
456                let row_indices = &indices[start..end];
457                let mut is_sequential = true;
458
459                for j in 1..row_indices.len() {
460                    if row_indices[j] != row_indices[j - 1] + 1 {
461                        is_sequential = false;
462                        break;
463                    }
464                }
465
466                if is_sequential {
467                    sequential_count += 1;
468                }
469            }
470        }
471
472        sequential_count
473    }
474
475    /// Calculate clustering factor
476    fn calculate_clustering_factor(&self, indptr: &[usize], indices: &[usize]) -> f64 {
477        if indices.is_empty() {
478            return 0.0;
479        }
480
481        let mut cluster_score = 0.0;
482        let mut total_comparisons = 0;
483
484        for i in 0..indptr.len().saturating_sub(1) {
485            let start = indptr[i];
486            let end = indptr[i + 1];
487
488            if end > start + 1 {
489                let row_indices = &indices[start..end];
490                for j in 1..row_indices.len() {
491                    let distance = row_indices[j].saturating_sub(row_indices[j - 1]);
492                    if distance <= 5 {
493                        // Consider elements within 5 columns as clustered
494                        cluster_score += 1.0;
495                    }
496                    total_comparisons += 1;
497                }
498            }
499        }
500
501        if total_comparisons > 0 {
502            cluster_score / total_comparisons as f64
503        } else {
504            0.0
505        }
506    }
507
508    /// Calculate diagonal dominance
509    fn calculate_diagonal_dominance(
510        &self,
511        rows: usize,
512        indptr: &[usize],
513        indices: &[usize],
514    ) -> f64 {
515        let mut diagonal_elements = 0;
516
517        for i in 0..rows.min(indptr.len().saturating_sub(1)) {
518            let start = indptr[i];
519            let end = indptr[i + 1];
520
521            for j in start..end {
522                if j < indices.len() && indices[j] == i {
523                    diagonal_elements += 1;
524                    break;
525                }
526            }
527        }
528
529        diagonal_elements as f64 / rows as f64
530    }
531
532    /// Get pattern classification
533    pub fn pattern_type(&self) -> AccessPatternType {
534        if self.sequential_patterns as f64 / (self.avg_nnz_per_row + 1.0) > 0.7 {
535            AccessPatternType::Sequential
536        } else if self.clustering_factor > 0.6 {
537            AccessPatternType::Clustered
538        } else if self.diagonal_dominance > 0.8 {
539            AccessPatternType::Sequential // Diagonal patterns are often sequential
540        } else {
541            AccessPatternType::Mixed
542        }
543    }
544}
545
546impl AccessPatternInfo {
547    /// Create new access pattern info
548    pub fn new() -> Self {
549        Self::default()
550    }
551
552    /// Update with new access information
553    pub fn update(&mut self, temporal_locality: f64, spatial_locality: f64, is_sequential: bool) {
554        self.total_accesses += 1;
555
556        // Update running averages
557        let n = self.total_accesses as f64;
558        self.avg_temporal_locality =
559            ((n - 1.0) * self.avg_temporal_locality + temporal_locality) / n;
560        self.avg_spatial_locality = ((n - 1.0) * self.avg_spatial_locality + spatial_locality) / n;
561
562        if is_sequential {
563            self.sequential_access_ratio = ((n - 1.0) * self.sequential_access_ratio + 1.0) / n;
564        } else {
565            self.sequential_access_ratio = ((n - 1.0) * self.sequential_access_ratio) / n;
566            self.random_access_ratio = 1.0 - self.sequential_access_ratio;
567        }
568
569        self.pattern_count += 1;
570    }
571
572    /// Get overall locality score
573    pub fn locality_score(&self) -> f64 {
574        (self.avg_temporal_locality + self.avg_spatial_locality) / 2.0
575    }
576
577    /// Determine predominant access pattern
578    pub fn predominant_pattern(&self) -> AccessPatternType {
579        if self.sequential_access_ratio > 0.7 {
580            AccessPatternType::Sequential
581        } else if self.random_access_ratio > 0.7 {
582            AccessPatternType::Random
583        } else if self.locality_score() > 0.6 {
584            AccessPatternType::Clustered
585        } else {
586            AccessPatternType::Mixed
587        }
588    }
589}
590
591impl std::fmt::Display for AccessPatternType {
592    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
593        match self {
594            AccessPatternType::Sequential => write!(f, "Sequential"),
595            AccessPatternType::Random => write!(f, "Random"),
596            AccessPatternType::Clustered => write!(f, "Clustered"),
597            AccessPatternType::Mixed => write!(f, "Mixed"),
598            AccessPatternType::Unknown => write!(f, "Unknown"),
599        }
600    }
601}