scirs2_sparse/adaptive_memory_compression/
stats.rs1use super::config::CompressionAlgorithm;
7
8#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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 pub fn new() -> Self {
164 Self::default()
165 }
166
167 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 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 pub fn update_decompression(&mut self, decompression_time: f64) {
190 self.decompression_time += decompression_time;
191 }
192
193 pub fn record_cache_hit(&mut self) {
195 self.cache_hits += 1;
196 }
197
198 pub fn record_cache_miss(&mut self) {
200 self.cache_misses += 1;
201 }
202
203 pub fn record_out_of_core_read(&mut self) {
205 self.out_of_core_reads += 1;
206 }
207
208 pub fn record_out_of_core_write(&mut self) {
210 self.out_of_core_writes += 1;
211 }
212
213 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 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 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 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 pub fn space_savings(&self) -> usize {
253 self.total_uncompressed_size
254 .saturating_sub(self.total_compressed_size)
255 }
256
257 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 pub fn reset(&mut self) {
268 *self = Self::default();
269 }
270
271 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 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 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 pub fn space_savings(&self) -> usize {
312 self.original_size.saturating_sub(self.compressed_size)
313 }
314
315 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 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 pub fn is_effective(&self, threshold: f64) -> bool {
335 self.compression_ratio < threshold
336 }
337}
338
339impl MemoryStats {
340 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 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 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 pub fn has_memory_pressure(&self, threshold: f64) -> bool {
378 self.memory_usage_ratio > threshold
379 }
380
381 pub fn available_memory(&self) -> usize {
383 self.total_memory_budget
384 .saturating_sub(self.current_memory_usage)
385 }
386
387 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 pub fn new() -> Self {
400 Self::default()
401 }
402
403 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 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 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 self.sequential_patterns = self.count_sequential_patterns(indptr, indices);
439
440 self.clustering_factor = self.calculate_clustering_factor(indptr, indices);
442
443 self.diagonal_dominance = self.calculate_diagonal_dominance(rows, indptr, indices);
445 }
446
447 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 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 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 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 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 } else {
541 AccessPatternType::Mixed
542 }
543 }
544}
545
546impl AccessPatternInfo {
547 pub fn new() -> Self {
549 Self::default()
550 }
551
552 pub fn update(&mut self, temporal_locality: f64, spatial_locality: f64, is_sequential: bool) {
554 self.total_accesses += 1;
555
556 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 pub fn locality_score(&self) -> f64 {
574 (self.avg_temporal_locality + self.avg_spatial_locality) / 2.0
575 }
576
577 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}