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().unwrap();
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().unwrap();
64 *current += size;
65
66 let mut peak = self.peak_memory.lock().unwrap();
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().unwrap();
77 *current = current.saturating_sub(size);
78 }
79
80 pub fn get_report(&self) -> MemoryReport {
82 let allocations = self.allocations.lock().unwrap().clone();
83 let peak_memory = *self.peak_memory.lock().unwrap();
84 let current_memory = *self.current_memory.lock().unwrap();
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().unwrap().clear();
143 *self.peak_memory.lock().unwrap() = 0;
144 *self.current_memory.lock().unwrap() = 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.profiler.current_memory.lock().unwrap();
357
358 if current_memory > self.memory_threshold_high {
359 if datasize > 1_000_000 {
361 AlgorithmChoice::Streaming
362 } else {
363 AlgorithmChoice::InPlace
364 }
365 } else if current_memory > self.memory_threshold_low {
366 if datasize > 100_000 {
368 AlgorithmChoice::Chunked
369 } else {
370 AlgorithmChoice::Standard
371 }
372 } else {
373 if datasize > 10_000 {
375 AlgorithmChoice::Parallel
376 } else {
377 AlgorithmChoice::Standard
378 }
379 }
380 }
381
382 pub fn suggest_chunksize(&self, datasize: usize, elementsize: usize) -> usize {
384 let current_memory = *self.profiler.current_memory.lock().unwrap();
385 let available_memory = self.memory_threshold_high.saturating_sub(current_memory);
386
387 let max_chunk_memory = available_memory / 10;
389 let max_chunk_elements = max_chunk_memory / elementsize;
390
391 max_chunk_elements.clamp(1000, datasize / 4)
393 }
394}
395
396#[derive(Debug, Clone, Copy, PartialEq)]
397pub enum AlgorithmChoice {
398 Standard, InPlace, Chunked, Streaming, Parallel, }
404
405pub struct ProfiledStatistics<F> {
407 profiler: Arc<MemoryProfiler>,
408 cache: StatisticsCache<F>,
409 adaptive_manager: AdaptiveMemoryManager,
410}
411
412impl<F> ProfiledStatistics<F>
413where
414 F: Float + NumCast + Clone + Send + Sync + std::fmt::Display,
415{
416 pub fn new(profiler: Arc<MemoryProfiler>) -> Self {
417 let cache = StatisticsCache::new(1000, 50 * 1024 * 1024) .with_profiler(profiler.clone());
419 let adaptive_manager = AdaptiveMemoryManager::new(profiler.clone());
420
421 Self {
422 profiler: profiler.clone(),
423 cache,
424 adaptive_manager,
425 }
426 }
427
428 pub fn mean_profiled<D>(&mut self, data: &ArrayBase<D, Ix1>) -> StatsResult<F>
430 where
431 D: Data<Elem = F>,
432 {
433 let start_time = Instant::now();
434
435 let cache_key = format!("mean_{}", data.len());
437
438 if let Some(cached_result) = self.cache.get(&cache_key) {
440 return Ok(cached_result);
441 }
442
443 let algorithm = self.adaptive_manager.choose_algorithm(data.len());
445
446 let result = match algorithm {
447 AlgorithmChoice::Streaming => self.compute_mean_streaming(data),
448 AlgorithmChoice::Chunked => self.compute_mean_chunked(data),
449 _ => self.compute_mean_standard(data),
450 }?;
451
452 let duration = start_time.elapsed();
454 self.profiler.record_allocation(
455 "mean_computation",
456 data.len() * std::mem::size_of::<F>(),
457 duration,
458 );
459
460 self.cache.put(cache_key, result);
462
463 Ok(result)
464 }
465
466 fn compute_mean_streaming<D>(&self, data: &ArrayBase<D, Ix1>) -> StatsResult<F>
467 where
468 D: Data<Elem = F>,
469 {
470 let mut sum = F::zero();
472 let mut count = 0;
473
474 for &value in data.iter() {
475 sum = sum + value;
476 count += 1;
477 }
478
479 if count == 0 {
480 return Err(StatsError::invalid_argument(
481 "Cannot compute mean of empty array",
482 ));
483 }
484
485 Ok(sum / F::from(count).unwrap())
486 }
487
488 fn compute_mean_chunked<D>(&self, data: &ArrayBase<D, Ix1>) -> StatsResult<F>
489 where
490 D: Data<Elem = F>,
491 {
492 let chunksize = self
494 .adaptive_manager
495 .suggest_chunksize(data.len(), std::mem::size_of::<F>());
496 let mut total_sum = F::zero();
497 let mut total_count = 0;
498
499 for chunk_start in (0..data.len()).step_by(chunksize) {
500 let chunk_end = (chunk_start + chunksize).min(data.len());
501 let chunk = data.slice(scirs2_core::ndarray::s![chunk_start..chunk_end]);
502
503 let chunk_sum = chunk.iter().fold(F::zero(), |acc, &x| acc + x);
504 total_sum = total_sum + chunk_sum;
505 total_count += chunk.len();
506 }
507
508 if total_count == 0 {
509 return Err(StatsError::invalid_argument(
510 "Cannot compute mean of empty array",
511 ));
512 }
513
514 Ok(total_sum / F::from(total_count).unwrap())
515 }
516
517 fn compute_mean_standard<D>(&self, data: &ArrayBase<D, Ix1>) -> StatsResult<F>
518 where
519 D: Data<Elem = F>,
520 {
521 let sum = data.iter().fold(F::zero(), |acc, &x| acc + x);
523 let count = data.len();
524
525 if count == 0 {
526 return Err(StatsError::invalid_argument(
527 "Cannot compute mean of empty array",
528 ));
529 }
530
531 Ok(sum / F::from(count).unwrap())
532 }
533
534 pub fn get_memory_report(&self) -> MemoryReport {
536 self.profiler.get_report()
537 }
538
539 pub fn get_cache_stats(&self) -> CacheStats {
541 self.cache.get_stats()
542 }
543}
544
545#[cfg(test)]
546mod tests {
547 use super::*;
548 use approx::assert_relative_eq;
549 use scirs2_core::ndarray::array;
550
551 #[test]
552 fn test_memory_profiler() {
553 let profiler = MemoryProfiler::new();
554
555 profiler.record_allocation("test", 1024, Duration::from_millis(5));
556 profiler.record_allocation("test", 2048, Duration::from_millis(10));
557
558 let report = profiler.get_report();
559
560 assert_eq!(report.allocations["test"].total_allocations, 2);
561 assert_eq!(report.allocations["test"].total_bytes, 3072);
562 assert_eq!(report.allocations["test"].peak_bytes, 2048);
563 }
564
565 #[test]
566 fn test_statistics_cache() {
567 let mut cache = StatisticsCache::new(2, 1024);
568
569 cache.put("key1".to_string(), 42.0);
570 cache.put("key2".to_string(), 24.0);
571
572 assert_eq!(cache.get("key1"), Some(42.0));
573 assert_eq!(cache.get("key2"), Some(24.0));
574 assert_eq!(cache.get("key3"), None);
575
576 cache.put("key3".to_string(), 12.0);
578 assert_eq!(cache.cache.len(), 2); }
580
581 #[test]
582 fn test_profiled_statistics() {
583 let profiler = Arc::new(MemoryProfiler::new());
584 let mut stats = ProfiledStatistics::new(profiler.clone());
585
586 let data = array![1.0, 2.0, 3.0, 4.0, 5.0];
587 let mean = stats.mean_profiled(&data.view()).unwrap();
588
589 assert_relative_eq!(mean, 3.0, epsilon = 1e-10);
590
591 let mean2 = stats.mean_profiled(&data.view()).unwrap();
593 assert_relative_eq!(mean2, 3.0, epsilon = 1e-10);
594
595 let report = stats.get_memory_report();
596 assert!(!report.allocations.is_empty());
597 }
598
599 #[test]
600 fn test_adaptive_memory_manager() {
601 let profiler = Arc::new(MemoryProfiler::new());
602 let manager = AdaptiveMemoryManager::new(profiler);
603
604 let choice_small = manager.choose_algorithm(1000);
606 let choice_large = manager.choose_algorithm(1_000_000);
607
608 assert_ne!(choice_small, choice_large);
610
611 let chunksize = manager.suggest_chunksize(100_000, 8);
613 assert!(chunksize > 0);
614 assert!(chunksize <= 25_000); }
616}