1use crate::error::{StatsError, StatsResult};
7use scirs2_core::ndarray::{ArrayBase, Data, Ix1};
8use scirs2_core::numeric::{Float, NumCast};
9use std::collections::HashMap;
10use std::sync::{Arc, Mutex};
11use std::time::{Duration, Instant};
12
13pub struct MemoryProfiler {
15 allocations: Arc<Mutex<HashMap<String, AllocationStats>>>,
16 peak_memory: Arc<Mutex<usize>>,
17 current_memory: Arc<Mutex<usize>>,
18 enabled: bool,
19}
20
21#[derive(Debug, Clone, Default)]
23pub struct AllocationStats {
24 pub total_allocations: usize,
25 pub total_bytes: usize,
26 pub peak_bytes: usize,
27 pub averagesize: f64,
28 pub allocation_times: Vec<Duration>,
29}
30
31impl Default for MemoryProfiler {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl MemoryProfiler {
38 pub fn new() -> Self {
39 Self {
40 allocations: Arc::new(Mutex::new(HashMap::new())),
41 peak_memory: Arc::new(Mutex::new(0)),
42 current_memory: Arc::new(Mutex::new(0)),
43 enabled: true,
44 }
45 }
46
47 pub fn record_allocation(&self, category: &str, size: usize, duration: Duration) {
49 if !self.enabled {
50 return;
51 }
52
53 let mut allocations = self.allocations.lock().expect("Operation failed");
54 let stats = allocations.entry(category.to_string()).or_default();
55
56 stats.total_allocations += 1;
57 stats.total_bytes += size;
58 stats.peak_bytes = stats.peak_bytes.max(size);
59 stats.averagesize = stats.total_bytes as f64 / stats.total_allocations as f64;
60 stats.allocation_times.push(duration);
61
62 let mut current = self.current_memory.lock().expect("Operation failed");
64 *current += size;
65
66 let mut peak = self.peak_memory.lock().expect("Operation failed");
67 *peak = (*peak).max(*current);
68 }
69
70 pub fn record_deallocation(&self, size: usize) {
72 if !self.enabled {
73 return;
74 }
75
76 let mut current = self.current_memory.lock().expect("Operation failed");
77 *current = current.saturating_sub(size);
78 }
79
80 pub fn get_report(&self) -> MemoryReport {
82 let allocations = self.allocations.lock().expect("Operation failed").clone();
83 let peak_memory = *self.peak_memory.lock().expect("Operation failed");
84 let current_memory = *self.current_memory.lock().expect("Operation failed");
85
86 let recommendations = self.generate_recommendations(&allocations);
87 MemoryReport {
88 allocations,
89 peak_memory,
90 current_memory,
91 recommendations,
92 }
93 }
94
95 fn generate_recommendations(
97 &self,
98 allocations: &HashMap<String, AllocationStats>,
99 ) -> Vec<String> {
100 let mut recommendations = Vec::new();
101
102 for (category, stats) in allocations {
103 if stats.total_allocations > 1000 && stats.averagesize < 1024.0 {
105 recommendations.push(format!(
106 "Consider memory pooling for '{}' category (many small allocations: {} allocations, avg size: {:.1} bytes)",
107 category, stats.total_allocations, stats.averagesize
108 ));
109 }
110
111 if stats.peak_bytes > 10 * 1024 * 1024 {
113 recommendations.push(format!(
115 "Consider streaming processing for '{}' category (large allocation: {:.1} MB)",
116 category,
117 stats.peak_bytes as f64 / 1024.0 / 1024.0
118 ));
119 }
120
121 if let Some(&max_time) = stats.allocation_times.iter().max() {
123 if max_time > Duration::from_millis(10) {
124 recommendations.push(format!(
125 "Consider pre-allocation for '{}' category (slow allocation: {:?})",
126 category, max_time
127 ));
128 }
129 }
130 }
131
132 recommendations
133 }
134
135 pub fn set_enabled(&mut self, enabled: bool) {
137 self.enabled = enabled;
138 }
139
140 pub fn reset(&self) {
142 self.allocations.lock().expect("Operation failed").clear();
143 *self.peak_memory.lock().expect("Operation failed") = 0;
144 *self.current_memory.lock().expect("Operation failed") = 0;
145 }
146}
147
148#[derive(Debug)]
150pub struct MemoryReport {
151 pub allocations: HashMap<String, AllocationStats>,
152 pub peak_memory: usize,
153 pub current_memory: usize,
154 pub recommendations: Vec<String>,
155}
156
157impl MemoryReport {
158 pub fn print_report(&self) {
160 println!("=== Memory Usage Report ===");
161 println!(
162 "Peak Memory Usage: {:.2} MB",
163 self.peak_memory as f64 / 1024.0 / 1024.0
164 );
165 println!(
166 "Current Memory Usage: {:.2} MB",
167 self.current_memory as f64 / 1024.0 / 1024.0
168 );
169 println!();
170
171 println!("Allocation Statistics by Category:");
172 for (category, stats) in &self.allocations {
173 println!(" {}:", category);
174 println!(" Total Allocations: {}", stats.total_allocations);
175 println!(
176 " Total Bytes: {:.2} MB",
177 stats.total_bytes as f64 / 1024.0 / 1024.0
178 );
179 println!(
180 " Peak Allocation: {:.2} KB",
181 stats.peak_bytes as f64 / 1024.0
182 );
183 println!(" Average Size: {:.1} bytes", stats.averagesize);
184
185 if !stats.allocation_times.is_empty() {
186 let avg_time = stats.allocation_times.iter().sum::<Duration>().as_micros() as f64
187 / stats.allocation_times.len() as f64;
188 println!(" Average Allocation Time: {:.1} µs", avg_time);
189 }
190 println!();
191 }
192
193 if !self.recommendations.is_empty() {
194 println!("Optimization Recommendations:");
195 for (i, rec) in self.recommendations.iter().enumerate() {
196 println!(" {}. {}", i + 1, rec);
197 }
198 }
199 }
200}
201
202pub struct StatisticsCache<F> {
204 cache: HashMap<String, CachedResult<F>>,
205 max_entries: usize,
206 max_memory: usize,
207 current_memory: usize,
208 profiler: Option<Arc<MemoryProfiler>>,
209}
210
211#[derive(Clone)]
212struct CachedResult<F> {
213 value: F,
214 timestamp: Instant,
215 memorysize: usize,
216 access_count: usize,
217}
218
219impl<F: Float + Clone + std::fmt::Display> StatisticsCache<F> {
220 pub fn new(_max_entries: usize, maxmemory: usize) -> Self {
221 Self {
222 cache: HashMap::new(),
223 max_entries: _max_entries,
224 max_memory: maxmemory,
225 current_memory: 0,
226 profiler: None,
227 }
228 }
229
230 pub fn with_profiler(mut self, profiler: Arc<MemoryProfiler>) -> Self {
231 self.profiler = Some(profiler);
232 self
233 }
234
235 pub fn put(&mut self, key: String, value: F) {
237 let memorysize = std::mem::size_of::<F>() + key.len();
238
239 self.maybe_evict(memorysize);
241
242 let cached_result = CachedResult {
243 value,
244 timestamp: Instant::now(),
245 memorysize,
246 access_count: 0,
247 };
248
249 if let Some(old_result) = self.cache.insert(key.clone(), cached_result) {
250 self.current_memory -= old_result.memorysize;
251 }
252
253 self.current_memory += memorysize;
254
255 if let Some(profiler) = &self.profiler {
256 profiler.record_allocation("statistics_cache", memorysize, Duration::from_nanos(0));
257 }
258 }
259
260 pub fn get(&mut self, key: &str) -> Option<F> {
262 if let Some(entry) = self.cache.get_mut(key) {
263 entry.access_count += 1;
264 Some(entry.value)
265 } else {
266 None
267 }
268 }
269
270 fn maybe_evict(&mut self, neededsize: usize) {
272 while self.current_memory + neededsize > self.max_memory && !self.cache.is_empty() {
274 self.evict_lru();
275 }
276
277 while self.cache.len() >= self.max_entries && !self.cache.is_empty() {
279 self.evict_lru();
280 }
281 }
282
283 fn evict_lru(&mut self) {
285 if let Some((key_to_remove, entry_to_remove)) = self
286 .cache
287 .iter()
288 .min_by_key(|(_, entry)| (entry.access_count, entry.timestamp))
289 .map(|(k, v)| (k.clone(), v.clone()))
290 {
291 self.cache.remove(&key_to_remove);
292 self.current_memory -= entry_to_remove.memorysize;
293
294 if let Some(profiler) = &self.profiler {
295 profiler.record_deallocation(entry_to_remove.memorysize);
296 }
297 }
298 }
299
300 pub fn get_stats(&self) -> CacheStats {
302 CacheStats {
303 entries: self.cache.len(),
304 memory_usage: self.current_memory,
305 hit_rate: self.calculate_hit_rate(),
306 }
307 }
308
309 fn calculate_hit_rate(&self) -> f64 {
310 let total_accesses: usize = self.cache.values().map(|entry| entry.access_count).sum();
311 if total_accesses == 0 {
312 0.0
313 } else {
314 total_accesses as f64 / (total_accesses + self.cache.len()) as f64
315 }
316 }
317
318 pub fn clear(&mut self) {
320 if let Some(profiler) = &self.profiler {
321 for entry in self.cache.values() {
322 profiler.record_deallocation(entry.memorysize);
323 }
324 }
325
326 self.cache.clear();
327 self.current_memory = 0;
328 }
329}
330
331#[derive(Debug)]
332pub struct CacheStats {
333 pub entries: usize,
334 pub memory_usage: usize,
335 pub hit_rate: f64,
336}
337
338pub struct AdaptiveMemoryManager {
340 memory_threshold_low: usize,
341 memory_threshold_high: usize,
342 profiler: Arc<MemoryProfiler>,
343}
344
345impl AdaptiveMemoryManager {
346 pub fn new(profiler: Arc<MemoryProfiler>) -> Self {
347 Self {
348 memory_threshold_low: 100 * 1024 * 1024, memory_threshold_high: 1024 * 1024 * 1024, profiler,
351 }
352 }
353
354 pub fn choose_algorithm(&self, datasize: usize) -> AlgorithmChoice {
356 let current_memory = *self
357 .profiler
358 .current_memory
359 .lock()
360 .expect("Operation failed");
361
362 if current_memory > self.memory_threshold_high {
363 if datasize > 1_000_000 {
365 AlgorithmChoice::Streaming
366 } else {
367 AlgorithmChoice::InPlace
368 }
369 } else if current_memory > self.memory_threshold_low {
370 if datasize > 100_000 {
372 AlgorithmChoice::Chunked
373 } else {
374 AlgorithmChoice::Standard
375 }
376 } else {
377 if datasize > 10_000 {
379 AlgorithmChoice::Parallel
380 } else {
381 AlgorithmChoice::Standard
382 }
383 }
384 }
385
386 pub fn suggest_chunksize(&self, datasize: usize, elementsize: usize) -> usize {
388 let current_memory = *self
389 .profiler
390 .current_memory
391 .lock()
392 .expect("Operation failed");
393 let available_memory = self.memory_threshold_high.saturating_sub(current_memory);
394
395 let max_chunk_memory = available_memory / 10;
397 let max_chunk_elements = max_chunk_memory / elementsize;
398
399 max_chunk_elements.clamp(1000, datasize / 4)
401 }
402}
403
404#[derive(Debug, Clone, Copy, PartialEq)]
405pub enum AlgorithmChoice {
406 Standard, InPlace, Chunked, Streaming, Parallel, }
412
413pub struct ProfiledStatistics<F> {
415 profiler: Arc<MemoryProfiler>,
416 cache: StatisticsCache<F>,
417 adaptive_manager: AdaptiveMemoryManager,
418}
419
420impl<F> ProfiledStatistics<F>
421where
422 F: Float + NumCast + Clone + Send + Sync + std::fmt::Display,
423{
424 pub fn new(profiler: Arc<MemoryProfiler>) -> Self {
425 let cache = StatisticsCache::new(1000, 50 * 1024 * 1024) .with_profiler(profiler.clone());
427 let adaptive_manager = AdaptiveMemoryManager::new(profiler.clone());
428
429 Self {
430 profiler: profiler.clone(),
431 cache,
432 adaptive_manager,
433 }
434 }
435
436 pub fn mean_profiled<D>(&mut self, data: &ArrayBase<D, Ix1>) -> StatsResult<F>
438 where
439 D: Data<Elem = F>,
440 {
441 let start_time = Instant::now();
442
443 let cache_key = format!("mean_{}", data.len());
445
446 if let Some(cached_result) = self.cache.get(&cache_key) {
448 return Ok(cached_result);
449 }
450
451 let algorithm = self.adaptive_manager.choose_algorithm(data.len());
453
454 let result = match algorithm {
455 AlgorithmChoice::Streaming => self.compute_mean_streaming(data),
456 AlgorithmChoice::Chunked => self.compute_mean_chunked(data),
457 _ => self.compute_mean_standard(data),
458 }?;
459
460 let duration = start_time.elapsed();
462 self.profiler.record_allocation(
463 "mean_computation",
464 data.len() * std::mem::size_of::<F>(),
465 duration,
466 );
467
468 self.cache.put(cache_key, result);
470
471 Ok(result)
472 }
473
474 fn compute_mean_streaming<D>(&self, data: &ArrayBase<D, Ix1>) -> StatsResult<F>
475 where
476 D: Data<Elem = F>,
477 {
478 let mut sum = F::zero();
480 let mut count = 0;
481
482 for &value in data.iter() {
483 sum = sum + value;
484 count += 1;
485 }
486
487 if count == 0 {
488 return Err(StatsError::invalid_argument(
489 "Cannot compute mean of empty array",
490 ));
491 }
492
493 Ok(sum / F::from(count).expect("Failed to convert to float"))
494 }
495
496 fn compute_mean_chunked<D>(&self, data: &ArrayBase<D, Ix1>) -> StatsResult<F>
497 where
498 D: Data<Elem = F>,
499 {
500 let chunksize = self
502 .adaptive_manager
503 .suggest_chunksize(data.len(), std::mem::size_of::<F>());
504 let mut total_sum = F::zero();
505 let mut total_count = 0;
506
507 for chunk_start in (0..data.len()).step_by(chunksize) {
508 let chunk_end = (chunk_start + chunksize).min(data.len());
509 let chunk = data.slice(scirs2_core::ndarray::s![chunk_start..chunk_end]);
510
511 let chunk_sum = chunk.iter().fold(F::zero(), |acc, &x| acc + x);
512 total_sum = total_sum + chunk_sum;
513 total_count += chunk.len();
514 }
515
516 if total_count == 0 {
517 return Err(StatsError::invalid_argument(
518 "Cannot compute mean of empty array",
519 ));
520 }
521
522 Ok(total_sum / F::from(total_count).expect("Failed to convert to float"))
523 }
524
525 fn compute_mean_standard<D>(&self, data: &ArrayBase<D, Ix1>) -> StatsResult<F>
526 where
527 D: Data<Elem = F>,
528 {
529 let sum = data.iter().fold(F::zero(), |acc, &x| acc + x);
531 let count = data.len();
532
533 if count == 0 {
534 return Err(StatsError::invalid_argument(
535 "Cannot compute mean of empty array",
536 ));
537 }
538
539 Ok(sum / F::from(count).expect("Failed to convert to float"))
540 }
541
542 pub fn get_memory_report(&self) -> MemoryReport {
544 self.profiler.get_report()
545 }
546
547 pub fn get_cache_stats(&self) -> CacheStats {
549 self.cache.get_stats()
550 }
551}
552
553#[cfg(test)]
554mod tests {
555 use super::*;
556 use approx::assert_relative_eq;
557 use scirs2_core::ndarray::array;
558
559 #[test]
560 fn test_memory_profiler() {
561 let profiler = MemoryProfiler::new();
562
563 profiler.record_allocation("test", 1024, Duration::from_millis(5));
564 profiler.record_allocation("test", 2048, Duration::from_millis(10));
565
566 let report = profiler.get_report();
567
568 assert_eq!(report.allocations["test"].total_allocations, 2);
569 assert_eq!(report.allocations["test"].total_bytes, 3072);
570 assert_eq!(report.allocations["test"].peak_bytes, 2048);
571 }
572
573 #[test]
574 fn test_statistics_cache() {
575 let mut cache = StatisticsCache::new(2, 1024);
576
577 cache.put("key1".to_string(), 42.0);
578 cache.put("key2".to_string(), 24.0);
579
580 assert_eq!(cache.get("key1"), Some(42.0));
581 assert_eq!(cache.get("key2"), Some(24.0));
582 assert_eq!(cache.get("key3"), None);
583
584 cache.put("key3".to_string(), 12.0);
586 assert_eq!(cache.cache.len(), 2); }
588
589 #[test]
590 fn test_profiled_statistics() {
591 let profiler = Arc::new(MemoryProfiler::new());
592 let mut stats = ProfiledStatistics::new(profiler.clone());
593
594 let data = array![1.0, 2.0, 3.0, 4.0, 5.0];
595 let mean = stats.mean_profiled(&data.view()).expect("Operation failed");
596
597 assert_relative_eq!(mean, 3.0, epsilon = 1e-10);
598
599 let mean2 = stats.mean_profiled(&data.view()).expect("Operation failed");
601 assert_relative_eq!(mean2, 3.0, epsilon = 1e-10);
602
603 let report = stats.get_memory_report();
604 assert!(!report.allocations.is_empty());
605 }
606
607 #[test]
608 fn test_adaptive_memory_manager() {
609 let profiler = Arc::new(MemoryProfiler::new());
610 let manager = AdaptiveMemoryManager::new(profiler);
611
612 let choice_small = manager.choose_algorithm(1000);
614 let choice_large = manager.choose_algorithm(1_000_000);
615
616 assert_ne!(choice_small, choice_large);
618
619 let chunksize = manager.suggest_chunksize(100_000, 8);
621 assert!(chunksize > 0);
622 assert!(chunksize <= 25_000); }
624}