1#![allow(dead_code)]
3use crate::{Tensor, TensorStorage};
6use std::alloc::{handle_alloc_error, Layout};
7use std::collections::{HashMap, VecDeque};
8use std::marker::PhantomData;
9use std::mem::{ManuallyDrop, MaybeUninit};
10use std::ptr::NonNull;
11use std::sync::{Arc, Mutex, Weak};
12use torsh_core::{device::DeviceType, dtype::TensorElement, error::Result};
13
14use scirs2_core::memory::GlobalBufferPool;
16use scirs2_core::memory::LeakDetector;
17#[cfg(feature = "memory_efficient")]
20use scirs2_core::memory_efficient::{AccessMode, MemoryMappedArray};
21
22#[cfg(feature = "memory_efficient")]
25fn unique_mmap_path(tag: &str) -> std::path::PathBuf {
26 use std::sync::atomic::{AtomicU64, Ordering};
27 static COUNTER: AtomicU64 = AtomicU64::new(0);
28 let seq = COUNTER.fetch_add(1, Ordering::Relaxed);
29 let nanos = std::time::SystemTime::now()
30 .duration_since(std::time::UNIX_EPOCH)
31 .unwrap_or_default()
32 .as_nanos();
33 std::env::temp_dir().join(format!(
34 "torsh_mmap_{tag}_{pid}_{nanos}_{seq}.bin",
35 pid = std::process::id()
36 ))
37}
38
39#[cfg(feature = "memory_efficient")]
46fn map_through_mmap_file<T: TensorElement>(
47 data: Vec<T>,
48 backing_path: &std::path::Path,
49) -> Result<Vec<T>> {
50 use scirs2_core::ndarray::Array1;
51
52 let array: Array1<T> = Array1::from(data);
54 let mmap = MemoryMappedArray::<T>::new(Some(&array), backing_path, AccessMode::Write, 0)
55 .map_err(|e| {
56 torsh_core::error::TorshError::IoError(format!(
57 "memory-mapped allocation failed at {path}: {e}",
58 path = backing_path.display()
59 ))
60 })?;
61
62 let mapped = mmap.as_slice().to_vec();
64
65 drop(mmap);
67 let _ = std::fs::remove_file(backing_path);
68
69 Ok(mapped)
70}
71
72static MEMORY_POOL: std::sync::OnceLock<Arc<Mutex<GlobalMemoryPool>>> = std::sync::OnceLock::new();
78
79pub fn init_memory_pool() -> Arc<Mutex<GlobalMemoryPool>> {
81 let arc = MEMORY_POOL
82 .get_or_init(|| {
83 let pool = Arc::new(Mutex::new(GlobalMemoryPool::new()));
84 if let Ok(mut guard) = pool.lock() {
86 guard.self_weak = Some(Arc::downgrade(&pool));
87 }
88 pool
89 })
90 .clone();
91 arc
92}
93
94pub fn get_memory_pool() -> Arc<Mutex<GlobalMemoryPool>> {
96 init_memory_pool()
97}
98
99struct RawEntry {
104 ptr: NonNull<u8>,
105 capacity_bytes: usize,
106 layout: Layout,
107}
108
109unsafe impl Send for RawEntry {}
111
112impl Drop for RawEntry {
113 fn drop(&mut self) {
114 unsafe { std::alloc::dealloc(self.ptr.as_ptr(), self.layout) };
116 }
117}
118
119pub struct ReusedBuffer<T: 'static> {
126 ptr: NonNull<T>,
127 capacity: usize,
128 layout: Layout,
129 pool: Weak<Mutex<GlobalMemoryPool>>,
130}
131
132unsafe impl<T: Send + 'static> Send for ReusedBuffer<T> {}
135
136impl<T: 'static> ReusedBuffer<T> {
137 pub fn as_uninit_slice_mut(&mut self) -> &mut [MaybeUninit<T>] {
139 unsafe {
141 std::slice::from_raw_parts_mut(self.ptr.as_ptr() as *mut MaybeUninit<T>, self.capacity)
142 }
143 }
144
145 pub fn capacity(&self) -> usize {
147 self.capacity
148 }
149
150 pub fn as_ptr_raw(&self) -> *mut T {
152 self.ptr.as_ptr()
153 }
154
155 pub fn into_vec(self, len: usize) -> Vec<T>
172 where
173 T: Copy,
174 {
175 debug_assert!(len <= self.capacity, "len must not exceed capacity");
176
177 if self.layout.align() != std::mem::align_of::<T>() {
178 let initialized =
181 unsafe { std::slice::from_raw_parts(self.ptr.as_ptr() as *const T, len) };
182 let copy = initialized.to_vec();
183 return copy;
186 }
187
188 let md = ManuallyDrop::new(self);
190 unsafe { Vec::from_raw_parts(md.ptr.as_ptr(), len, md.capacity) }
194 }
195
196 pub fn release_to_pool(self) {
200 let md = ManuallyDrop::new(self);
202 let raw_entry = RawEntry {
203 ptr: NonNull::new(md.ptr.as_ptr() as *mut u8)
204 .expect("ReusedBuffer pointer is non-null by construction"),
205 capacity_bytes: md.capacity * std::mem::size_of::<T>(),
206 layout: md.layout,
207 };
208 if let Some(pool_arc) = md.pool.upgrade() {
209 let mut guard = pool_arc
216 .lock()
217 .unwrap_or_else(|poisoned| poisoned.into_inner());
218 let type_id = std::any::TypeId::of::<T>();
219 let size_class = guard.find_size_class(raw_entry.capacity_bytes);
220 let align = raw_entry.layout.align();
221 let pool_key = (type_id, size_class, align);
222 if let Some(bucket) = guard.pools.get_mut(&pool_key) {
223 if bucket.available_buffers.len() < bucket.max_buffers {
224 bucket.available_buffers.push_back(raw_entry);
225 bucket.deallocations += 1;
226 return;
228 }
229 }
230 }
231 }
233}
234
235impl<T: 'static> Drop for ReusedBuffer<T> {
236 fn drop(&mut self) {
237 let raw_entry = RawEntry {
240 ptr: NonNull::new(self.ptr.as_ptr() as *mut u8)
241 .expect("ReusedBuffer pointer is non-null by construction"),
242 capacity_bytes: self.capacity * std::mem::size_of::<T>(),
243 layout: self.layout,
244 };
245 if let Some(pool_arc) = self.pool.upgrade() {
246 let mut guard = pool_arc
249 .lock()
250 .unwrap_or_else(|poisoned| poisoned.into_inner());
251 let type_id = std::any::TypeId::of::<T>();
252 let size_class = guard.find_size_class(raw_entry.capacity_bytes);
253 let align = raw_entry.layout.align();
254 let pool_key = (type_id, size_class, align);
255 if let Some(bucket) = guard.pools.get_mut(&pool_key) {
256 if bucket.available_buffers.len() < bucket.max_buffers {
257 let md_entry = ManuallyDrop::new(raw_entry);
260 bucket
263 .available_buffers
264 .push_back(unsafe { std::ptr::read(&*md_entry as *const RawEntry) });
265 bucket.deallocations += 1;
266 return;
267 }
268 }
269 }
270 }
272}
273
274pub struct GlobalMemoryPool {
278 pools: HashMap<(std::any::TypeId, usize, usize), MemoryPool>,
284 stats: PoolStatistics,
286 config: PoolConfig,
288 scirs2_pool: GlobalBufferPool,
290 leak_detector: Option<LeakDetector>,
296 self_weak: Option<Weak<Mutex<GlobalMemoryPool>>>,
298 }
303
304#[derive(Debug)]
306struct MemoryPool {
307 available_buffers: VecDeque<RawEntry>,
309 #[allow(dead_code)]
311 size_class: usize,
312 max_buffers: usize,
314 allocations: usize,
316 reuses: usize,
317 deallocations: usize,
318}
319
320#[derive(Debug, Clone)]
322pub struct PoolConfig {
323 pub max_buffers_per_class: usize,
325 pub max_total_memory: usize,
327 pub auto_cleanup: bool,
329 pub cleanup_threshold: f64,
331 pub size_classes: Vec<usize>,
333}
334
335#[derive(Debug, Default, Clone)]
337pub struct PoolStatistics {
338 pub total_allocations: usize,
340 pub pool_hits: usize,
342 pub pool_misses: usize,
344 pub total_bytes_allocated: usize,
346 pub bytes_in_pools: usize,
348 pub peak_memory_usage: usize,
350}
351
352#[derive(Debug)]
354pub struct PooledTensor<T: TensorElement + Default> {
355 tensor: Tensor<T>,
356 pool_key: Option<(std::any::TypeId, usize, usize)>,
357 _phantom: PhantomData<T>,
358}
359
360impl Default for PoolConfig {
361 fn default() -> Self {
362 let size_classes = (10..31) .map(|exp| 1 << exp)
365 .collect();
366
367 Self {
368 max_buffers_per_class: 16,
369 max_total_memory: 1024 * 1024 * 1024, auto_cleanup: true,
371 cleanup_threshold: 0.8,
372 size_classes,
373 }
374 }
375}
376
377impl Default for GlobalMemoryPool {
378 fn default() -> Self {
379 Self::new()
380 }
381}
382
383fn assert_valid_alignment<T>(align: usize) {
395 let element_align = std::mem::align_of::<T>();
396 assert!(
397 align.is_power_of_two(),
398 "alignment must be a power of two (got {align})"
399 );
400 assert!(
401 align >= element_align,
402 "alignment {align} must be >= align_of::<T>() ({element_align})"
403 );
404}
405
406impl GlobalMemoryPool {
407 pub fn new() -> Self {
409 #[cfg(feature = "profiling")]
410 {
411 }
413 Self {
414 pools: HashMap::new(),
415 stats: PoolStatistics::default(),
416 config: PoolConfig::default(),
417 scirs2_pool: GlobalBufferPool::new(),
419 leak_detector: LeakDetector::new(Default::default()).ok(),
422 self_weak: None,
423 }
426 }
427
428 pub fn create_large_tensor<T: TensorElement>(
430 &mut self,
431 shape: &[usize],
432 device: DeviceType,
433 ) -> Result<Tensor<T>>
434 where
435 T: Clone + Default,
436 {
437 #[cfg(feature = "profiling")]
438 {
439 }
441 let total_elements: usize = shape.iter().product();
442 let total_bytes = total_elements * std::mem::size_of::<T>();
443
444 if total_bytes > 100 * 1024 * 1024 {
446 self.create_memory_mapped_tensor(shape, device)
448 } else if total_bytes > 10 * 1024 * 1024 {
449 self.create_chunked_tensor(shape, device)
451 } else if total_bytes > 1024 * 1024 {
452 self.create_pooled_tensor(shape, device)
454 } else {
455 Tensor::zeros(shape, device)
457 }
458 }
459
460 fn create_memory_mapped_tensor<T: TensorElement>(
467 &mut self,
468 shape: &[usize],
469 device: DeviceType,
470 ) -> Result<Tensor<T>>
471 where
472 T: Clone + Default,
473 {
474 let total_elements: usize = shape.iter().product();
475
476 let data = vec![T::default(); total_elements];
478
479 #[cfg(feature = "memory_efficient")]
480 {
481 let backing_path = unique_mmap_path("tensor");
484 let mapped = map_through_mmap_file::<T>(data, &backing_path)?;
485 Tensor::from_data(mapped, shape.to_vec(), device)
486 }
487
488 #[cfg(not(feature = "memory_efficient"))]
489 {
490 Tensor::from_data(data, shape.to_vec(), device)
492 }
493 }
494
495 fn create_chunked_tensor<T: TensorElement>(
497 &mut self,
498 shape: &[usize],
499 device: DeviceType,
500 ) -> Result<Tensor<T>>
501 where
502 T: Clone + Default,
503 {
504 let total_elements: usize = shape.iter().product();
505
506 let chunk_size = (1024 * 1024) / std::mem::size_of::<T>().max(1); let num_chunks = (total_elements + chunk_size - 1) / chunk_size;
509
510 let _ = (total_elements, num_chunks, chunk_size); let data = vec![T::default(); total_elements];
515
516 Tensor::from_data(data, shape.to_vec(), device)
521 }
522
523 fn create_pooled_tensor<T: TensorElement>(
525 &mut self,
526 shape: &[usize],
527 device: DeviceType,
528 ) -> Result<Tensor<T>>
529 where
530 T: Clone + Default,
531 {
532 let total_elements: usize = shape.iter().product();
533 let buffer_size = total_elements * std::mem::size_of::<T>();
534
535 let _ = (buffer_size, total_elements); let data = vec![T::default(); total_elements];
540
541 self.stats.pool_hits += 1;
543 Tensor::from_data(data, shape.to_vec(), device)
547 }
548
549 pub fn create_lazy_tensor<T: TensorElement>(
551 &mut self,
552 shape: &[usize],
553 device: DeviceType,
554 ) -> Result<Tensor<T>>
555 where
556 T: Clone + Default,
557 {
558 #[cfg(feature = "profiling")]
559 {
560 }
562 let total_elements: usize = shape.iter().product();
563
564 let data = vec![T::default(); total_elements];
566
567 Tensor::from_data(data, shape.to_vec(), device)
571 }
572
573 pub fn create_zero_copy_view<T: TensorElement>(
575 &self,
576 source: &Tensor<T>,
577 offset: usize,
578 shape: &[usize],
579 ) -> Result<Tensor<T>>
580 where
581 T: Clone,
582 {
583 #[cfg(feature = "profiling")]
584 {
585 }
587
588 let source_data = source.data()?;
590 let view_data = source_data[offset..offset + shape.iter().product::<usize>()].to_vec();
591
592 Tensor::from_data(view_data, shape.to_vec(), source.device())
593 }
594
595 pub fn get_enhanced_stats(&self) -> PoolStatistics {
597 self.stats.clone()
599 }
600
601 pub fn acquire_uninit<T: 'static>(&mut self, count: usize) -> ReusedBuffer<T> {
612 self.acquire_uninit_aligned::<T>(count, std::mem::align_of::<T>())
613 }
614
615 pub fn acquire_uninit_aligned<T: 'static>(
626 &mut self,
627 count: usize,
628 align: usize,
629 ) -> ReusedBuffer<T> {
630 assert_valid_alignment::<T>(align);
631 let element_size = std::mem::size_of::<T>();
632 let size_bytes = count * element_size;
633 let size_class = self.find_size_class(size_bytes);
634 let type_id = std::any::TypeId::of::<T>();
635 let pool_key = (type_id, size_class, align);
636
637 let layout =
638 Layout::from_size_align(size_bytes.max(1), align).expect("size and align are valid");
639
640 self.stats.total_allocations += 1;
642 self.stats.total_bytes_allocated += size_bytes;
643
644 if let Some(bucket) = self.pools.get_mut(&pool_key) {
646 let mut found_idx: Option<usize> = None;
648 for (i, entry) in bucket.available_buffers.iter().enumerate() {
649 if entry.capacity_bytes >= size_bytes && entry.layout.align() >= align {
650 found_idx = Some(i);
651 break;
652 }
653 }
654 if let Some(idx) = found_idx {
655 let raw_entry = bucket
656 .available_buffers
657 .remove(idx)
658 .expect("index was valid moments ago");
659 self.stats.pool_hits += 1;
660 bucket.reuses += 1;
661
662 let ptr = NonNull::new(raw_entry.ptr.as_ptr() as *mut T)
663 .expect("RawEntry pointer is non-null by construction");
664 let actual_capacity = raw_entry.capacity_bytes / element_size;
666 let entry_layout = raw_entry.layout;
667 std::mem::forget(raw_entry);
668
669 let weak = self.self_weak.clone().unwrap_or_else(Weak::new);
670 return ReusedBuffer {
671 ptr,
672 capacity: actual_capacity,
673 layout: entry_layout,
674 pool: weak,
675 };
676 }
677 }
678
679 self.stats.pool_misses += 1;
681
682 self.pools.entry(pool_key).or_insert_with(|| MemoryPool {
684 available_buffers: VecDeque::new(),
685 size_class,
686 max_buffers: self.config.max_buffers_per_class,
687 allocations: 0,
688 reuses: 0,
689 deallocations: 0,
690 });
691
692 if let Some(bucket) = self.pools.get_mut(&pool_key) {
693 bucket.allocations += 1;
694 }
695
696 let raw_ptr = unsafe { std::alloc::alloc(layout) };
698 let ptr = NonNull::new(raw_ptr as *mut T).unwrap_or_else(|| handle_alloc_error(layout));
699
700 let weak = self.self_weak.clone().unwrap_or_else(Weak::new);
701 ReusedBuffer {
702 ptr,
703 capacity: count,
704 layout,
705 pool: weak,
706 }
707 }
708
709 #[deprecated = "Use global_acquire_uninit instead for zero-copy buffer reuse"]
716 pub fn allocate<T: TensorElement + Default + 'static>(&mut self, count: usize) -> Vec<T> {
717 let mut buf = self.acquire_uninit::<T>(count);
718 for slot in buf.as_uninit_slice_mut() {
720 slot.write(T::default());
721 }
722 buf.into_vec(count)
723 }
724
725 pub fn find_size_class(&self, size_bytes: usize) -> usize {
727 self.config
728 .size_classes
729 .iter()
730 .position(|&class_size| size_bytes <= class_size)
731 .unwrap_or(self.config.size_classes.len() - 1)
732 }
733
734 pub fn deallocate<T: 'static>(&mut self, data: Vec<T>) {
741 drop(data);
743 }
744
745 pub fn clear(&mut self) {
747 self.pools.clear();
748 self.stats = PoolStatistics::default();
749 }
750
751 pub fn get_statistics(&self) -> &PoolStatistics {
753 &self.stats
754 }
755
756 pub fn hit_rate(&self) -> f64 {
758 if self.stats.total_allocations == 0 {
759 0.0
760 } else {
761 self.stats.pool_hits as f64 / self.stats.total_allocations as f64
762 }
763 }
764
765 pub fn cleanup(&mut self) {
767 if self.config.auto_cleanup {
768 let threshold_bytes =
769 (self.config.max_total_memory as f64 * self.config.cleanup_threshold) as usize;
770 if self.stats.total_bytes_allocated > threshold_bytes {
771 self.pools
772 .retain(|_, pool| !pool.available_buffers.is_empty());
773 }
774 }
775 }
776}
777
778impl std::fmt::Debug for GlobalMemoryPool {
779 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
780 f.debug_struct("GlobalMemoryPool")
781 .field("pools", &self.pools)
782 .field("stats", &self.stats)
783 .field("config", &self.config)
784 .field("scirs2_pool", &"<GlobalBufferPool>")
785 .field(
786 "leak_detector",
787 &self.leak_detector.as_ref().map(|_| "<LeakDetector>"),
788 )
789 .finish()
790 }
791}
792
793impl std::fmt::Debug for RawEntry {
796 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
797 f.debug_struct("RawEntry")
798 .field("capacity_bytes", &self.capacity_bytes)
799 .finish()
800 }
801}
802
803pub fn global_acquire_uninit<T: 'static>(count: usize) -> ReusedBuffer<T> {
816 let pool_arc = get_memory_pool();
817 let mut guard = pool_arc
818 .lock()
819 .unwrap_or_else(|poisoned| poisoned.into_inner());
820 guard.acquire_uninit::<T>(count)
821}
822
823pub fn global_acquire_uninit_aligned<T: 'static>(count: usize, align: usize) -> ReusedBuffer<T> {
835 assert_valid_alignment::<T>(align);
840
841 let pool_arc = get_memory_pool();
842 let mut guard = pool_arc
843 .lock()
844 .unwrap_or_else(|poisoned| poisoned.into_inner());
845 guard.acquire_uninit_aligned::<T>(count, align)
846}
847
848pub type EnhancedMemoryStats = PoolStatistics;
852
853impl<T: TensorElement> Tensor<T> {
855 pub fn create_efficient(shape: &[usize], device: DeviceType) -> Result<Self>
857 where
858 T: Clone + Default,
859 {
860 let binding = get_memory_pool();
861 let mut pool = binding
862 .lock()
863 .unwrap_or_else(|poisoned| poisoned.into_inner());
864 pool.create_large_tensor::<T>(shape, device)
865 }
866
867 pub fn lazy(shape: &[usize], device: DeviceType) -> Result<Self>
869 where
870 T: Clone + Default,
871 {
872 let binding = get_memory_pool();
873 let mut pool = binding
874 .lock()
875 .unwrap_or_else(|poisoned| poisoned.into_inner());
876 pool.create_lazy_tensor::<T>(shape, device)
877 }
878
879 pub fn memory_mapped(shape: &[usize], device: DeviceType) -> Result<Self>
890 where
891 T: Clone + Default,
892 {
893 #[cfg(feature = "profiling")]
894 {
895 }
897
898 let total_elements: usize = shape.iter().product();
900 let data = vec![T::default(); total_elements];
901 Self::from_data(data, shape.to_vec(), device)
902 }
903
904 pub fn chunked(shape: &[usize], chunk_size: usize, device: DeviceType) -> Result<Self>
914 where
915 T: Clone + Default,
916 {
917 #[cfg(feature = "profiling")]
918 {
919 }
921 let total_elements: usize = shape.iter().product();
922
923 let effective_chunk_size = if chunk_size == 0 {
925 let default_chunk_bytes = 64 * 1024;
927 let element_size = std::mem::size_of::<T>();
928 (default_chunk_bytes / element_size.max(1)).max(1)
929 } else {
930 chunk_size
931 };
932
933 let cache_line_elements = 64 / std::mem::size_of::<T>().max(1);
935 let aligned_chunk_size = ((effective_chunk_size + cache_line_elements - 1)
936 / cache_line_elements)
937 * cache_line_elements;
938
939 let _ = (total_elements, effective_chunk_size, aligned_chunk_size); let data = vec![T::default(); total_elements];
944
945 Self::from_data(data, shape.to_vec(), device)
948 }
949
950 pub fn disk_backed(shape: &[usize], device: DeviceType, file_path: Option<&str>) -> Result<Self>
968 where
969 T: Clone + Default,
970 {
971 #[cfg(feature = "profiling")]
972 {
973 }
975 let total_elements: usize = shape.iter().product();
976
977 let backing_path = file_path.map(std::path::PathBuf::from);
980
981 let storage =
982 TensorStorage::memory_mapped_filled(total_elements, T::default(), backing_path)?;
983
984 let mut tensor = Self::from_data(Vec::new(), Vec::new(), device)?;
985 tensor.storage = storage;
986 tensor.shape = torsh_core::shape::Shape::new(shape.to_vec());
987 Ok(tensor)
988 }
989
990 pub fn process_chunked<F, R>(&self, chunk_size: usize, mut processor: F) -> Result<Vec<R>>
992 where
993 F: FnMut(&[T]) -> Result<R>,
994 T: Clone,
995 {
996 #[cfg(feature = "profiling")]
997 {
998 }
1000 let data = self.data()?;
1001 let mut results = Vec::new();
1002
1003 let effective_chunk_size = chunk_size;
1005
1006 for chunk in data.chunks(effective_chunk_size) {
1007 results.push(processor(chunk)?);
1008 }
1009
1010 Ok(results)
1011 }
1012}
1013
1014impl MemoryPool {
1015 fn new(size_class: usize, max_buffers: usize) -> Self {
1016 Self {
1017 available_buffers: VecDeque::new(),
1018 size_class,
1019 max_buffers,
1020 allocations: 0,
1021 reuses: 0,
1022 deallocations: 0,
1023 }
1024 }
1025}
1026
1027impl<T: TensorElement + Copy + Default> PooledTensor<T> {
1028 pub fn new(shape: &[usize], device: DeviceType) -> Result<Self> {
1030 let numel = shape.iter().product::<usize>();
1031
1032 let pool = get_memory_pool();
1034 let data = {
1035 let mut pool_guard = pool.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
1036 #[allow(deprecated)]
1037 pool_guard.allocate::<T>(numel)
1038 };
1039
1040 let tensor = Tensor::from_data(data, shape.to_vec(), device)?;
1041 let type_id = std::any::TypeId::of::<T>();
1042 let size_class = {
1043 let pool_guard = pool.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
1044 pool_guard.find_size_class(numel * std::mem::size_of::<T>())
1045 };
1046 let align = std::mem::align_of::<T>();
1047
1048 Ok(Self {
1049 tensor,
1050 pool_key: Some((type_id, size_class, align)),
1051 _phantom: PhantomData,
1052 })
1053 }
1054
1055 pub fn zeros(shape: &[usize], device: DeviceType) -> Result<Self> {
1057 let mut pooled = Self::new(shape, device)?;
1058 let numel = shape.iter().product::<usize>();
1060 let data = vec![T::default(); numel];
1061 pooled.tensor.storage = TensorStorage::create_optimal(data)?;
1062 Ok(pooled)
1063 }
1064
1065 pub fn ones(shape: &[usize], device: DeviceType) -> Result<Self>
1067 where
1068 T: std::ops::Add<Output = T> + From<f32>,
1069 {
1070 let mut pooled = Self::new(shape, device)?;
1071 let numel = shape.iter().product::<usize>();
1073 let data = vec![T::from(1.0f32); numel];
1074 pooled.tensor.storage = TensorStorage::create_optimal(data)?;
1075 Ok(pooled)
1076 }
1077
1078 pub fn tensor(&self) -> &Tensor<T> {
1080 &self.tensor
1081 }
1082
1083 pub fn tensor_mut(&mut self) -> &mut Tensor<T> {
1085 &mut self.tensor
1086 }
1087
1088 pub fn into_tensor(mut self) -> Tensor<T> {
1090 self.pool_key = None; self.tensor.clone()
1092 }
1093}
1094
1095impl<T: TensorElement + std::default::Default> Drop for PooledTensor<T> {
1096 fn drop(&mut self) {
1097 if let Some((_type_id, _size_class, _align)) = self.pool_key {
1098 if let Ok(data) = self.tensor.to_vec() {
1100 let pool = get_memory_pool();
1101 let mut pool_guard = pool.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
1102 pool_guard.deallocate(data);
1103 }
1104 }
1105 }
1106}
1107
1108impl<T: TensorElement + Copy + Default> Tensor<T> {
1110 pub fn pooled(shape: &[usize], device: DeviceType) -> Result<PooledTensor<T>> {
1112 PooledTensor::new(shape, device)
1113 }
1114
1115 pub fn temporary(shape: &[usize], device: DeviceType) -> Result<PooledTensor<T>> {
1117 PooledTensor::new(shape, device)
1118 }
1119}
1120
1121pub fn clear_memory_pool() {
1123 if let Some(pool) = MEMORY_POOL.get() {
1124 pool.lock()
1125 .unwrap_or_else(|poisoned| poisoned.into_inner())
1126 .clear();
1127 }
1128}
1129
1130pub fn get_pool_statistics() -> PoolStatistics {
1131 get_memory_pool()
1132 .lock()
1133 .unwrap_or_else(|poisoned| poisoned.into_inner())
1134 .get_statistics()
1135 .clone()
1136}
1137
1138pub fn get_pool_hit_rate() -> f64 {
1139 get_memory_pool()
1140 .lock()
1141 .unwrap_or_else(|poisoned| poisoned.into_inner())
1142 .hit_rate()
1143}
1144
1145pub fn cleanup_memory_pool() {
1146 get_memory_pool()
1147 .lock()
1148 .unwrap_or_else(|poisoned| poisoned.into_inner())
1149 .cleanup();
1150}
1151
1152#[cfg(test)]
1153mod tests {
1154 use super::*;
1155
1156 static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
1183
1184 #[test]
1185 fn test_memory_pool_basic() {
1186 let _guard = TEST_LOCK
1187 .lock()
1188 .unwrap_or_else(|poisoned| poisoned.into_inner());
1189 clear_memory_pool();
1190
1191 let pooled = PooledTensor::<f32>::zeros(&[100, 100], DeviceType::Cpu)
1193 .expect("zeros creation should succeed");
1194 assert_eq!(pooled.tensor().numel(), 10000);
1195
1196 drop(pooled);
1198
1199 let _pooled2 = PooledTensor::<f32>::zeros(&[100, 100], DeviceType::Cpu)
1201 .expect("zeros creation should succeed");
1202
1203 let stats = get_pool_statistics();
1204 assert!(stats.pool_hits > 0 || stats.pool_misses > 0);
1205 }
1206
1207 #[test]
1208 fn test_pool_statistics() {
1209 let _guard = TEST_LOCK
1210 .lock()
1211 .unwrap_or_else(|poisoned| poisoned.into_inner());
1212 clear_memory_pool();
1213
1214 let _pooled1 = PooledTensor::<f32>::zeros(&[50, 50], DeviceType::Cpu)
1215 .expect("zeros creation should succeed");
1216 let _pooled2 = PooledTensor::<f32>::ones(&[50, 50], DeviceType::Cpu)
1217 .expect("ones creation should succeed");
1218
1219 let stats = get_pool_statistics();
1220 assert!(stats.total_allocations >= 2);
1221 assert!(stats.total_bytes_allocated > 0);
1222 }
1223
1224 #[test]
1225 fn test_pool_cleanup() {
1226 let _guard = TEST_LOCK
1227 .lock()
1228 .unwrap_or_else(|poisoned| poisoned.into_inner());
1229 clear_memory_pool();
1230
1231 for _ in 0..10 {
1233 let _temp = PooledTensor::<f32>::zeros(&[100, 100], DeviceType::Cpu)
1234 .expect("zeros creation should succeed");
1235 }
1236
1237 cleanup_memory_pool();
1238 let _stats = get_pool_statistics();
1239 }
1241
1242 #[test]
1243 fn test_pooled_tensor_conversion() {
1244 let _guard = TEST_LOCK
1247 .lock()
1248 .unwrap_or_else(|poisoned| poisoned.into_inner());
1249 let pooled = PooledTensor::<f32>::ones(&[10, 10], DeviceType::Cpu)
1250 .expect("ones creation should succeed");
1251 let tensor = pooled.into_tensor();
1252 assert_eq!(tensor.numel(), 100);
1253 }
1254
1255 #[test]
1258 fn test_acquire_truly_reuses_allocation() {
1259 let _guard = TEST_LOCK
1260 .lock()
1261 .unwrap_or_else(|poisoned| poisoned.into_inner());
1262 clear_memory_pool();
1263
1264 let buf1: ReusedBuffer<f32> = global_acquire_uninit::<f32>(1024);
1265 let ptr1 = buf1.as_ptr_raw();
1266 buf1.release_to_pool();
1267
1268 let buf2: ReusedBuffer<f32> = global_acquire_uninit::<f32>(1024);
1269 let ptr2 = buf2.as_ptr_raw();
1270 buf2.release_to_pool();
1271
1272 assert_eq!(
1273 ptr1, ptr2,
1274 "pool should return the same allocation on second acquire"
1275 );
1276 }
1277
1278 #[test]
1279 fn test_into_vec_transfers_ownership() {
1280 let _guard = TEST_LOCK
1281 .lock()
1282 .unwrap_or_else(|poisoned| poisoned.into_inner());
1283 clear_memory_pool();
1284
1285 let mut buf: ReusedBuffer<f32> = global_acquire_uninit::<f32>(64);
1286 for slot in buf.as_uninit_slice_mut() {
1288 slot.write(1.0_f32);
1289 }
1290 let vec = buf.into_vec(64);
1291 assert_eq!(vec.len(), 64);
1292 assert!(vec.iter().all(|&x| x == 1.0_f32));
1293 }
1294
1295 #[test]
1296 fn test_drop_returns_to_pool() {
1297 let _guard = TEST_LOCK
1298 .lock()
1299 .unwrap_or_else(|poisoned| poisoned.into_inner());
1300 clear_memory_pool();
1301
1302 {
1303 let buf: ReusedBuffer<f32> = global_acquire_uninit::<f32>(256);
1304 drop(buf);
1306 }
1307
1308 let buf2: ReusedBuffer<f32> = global_acquire_uninit::<f32>(256);
1310 buf2.release_to_pool();
1311
1312 let stats = get_pool_statistics();
1313 assert!(
1314 stats.pool_hits >= 1,
1315 "expected at least one pool hit after drop-return"
1316 );
1317 }
1318
1319 #[test]
1320 fn test_acquire_capacity_and_uninit_slice() {
1321 let _guard = TEST_LOCK
1322 .lock()
1323 .unwrap_or_else(|poisoned| poisoned.into_inner());
1324 clear_memory_pool();
1325
1326 let buf: ReusedBuffer<u64> = global_acquire_uninit::<u64>(32);
1327 assert_eq!(buf.capacity(), 32);
1328 buf.release_to_pool();
1329 }
1330
1331 #[test]
1334 fn test_acquire_aligned_returns_simd_aligned_pointer() {
1335 let _guard = TEST_LOCK
1336 .lock()
1337 .unwrap_or_else(|poisoned| poisoned.into_inner());
1338 clear_memory_pool();
1339
1340 let buf: ReusedBuffer<f32> = global_acquire_uninit_aligned::<f32>(1024, 32);
1342 assert_eq!(buf.capacity(), 1024);
1343 let addr = buf.as_ptr_raw() as usize;
1344 assert_eq!(
1345 addr % 32,
1346 0,
1347 "buffer pointer {addr:#x} must be 32-byte aligned"
1348 );
1349 buf.release_to_pool();
1350 }
1351
1352 #[test]
1353 fn test_acquire_aligned_pool_hit_on_release() {
1354 let _guard = TEST_LOCK
1355 .lock()
1356 .unwrap_or_else(|poisoned| poisoned.into_inner());
1357 clear_memory_pool();
1358
1359 let buf1: ReusedBuffer<f32> = global_acquire_uninit_aligned::<f32>(2048, 32);
1360 let ptr1 = buf1.as_ptr_raw();
1361 let cap1 = buf1.capacity();
1362 buf1.release_to_pool();
1363
1364 let buf2: ReusedBuffer<f32> = global_acquire_uninit_aligned::<f32>(2048, 32);
1365 let ptr2 = buf2.as_ptr_raw();
1366 let cap2 = buf2.capacity();
1367 assert_eq!(
1368 ptr1, ptr2,
1369 "aligned bucket should return the same allocation on second acquire"
1370 );
1371 assert_eq!(cap1, cap2, "capacity should match across reuse");
1372 assert_eq!(ptr2 as usize % 32, 0, "reused buffer must remain aligned");
1374 buf2.release_to_pool();
1375 }
1376
1377 #[test]
1378 fn test_aligned_and_natural_buckets_are_independent() {
1379 let _guard = TEST_LOCK
1380 .lock()
1381 .unwrap_or_else(|poisoned| poisoned.into_inner());
1382 clear_memory_pool();
1383
1384 let buf_aligned: ReusedBuffer<f32> = global_acquire_uninit_aligned::<f32>(512, 32);
1386 let ptr_aligned = buf_aligned.as_ptr_raw();
1387 buf_aligned.release_to_pool();
1388
1389 let buf_natural: ReusedBuffer<f32> = global_acquire_uninit::<f32>(512);
1392 let ptr_natural = buf_natural.as_ptr_raw();
1393 assert_ne!(
1394 ptr_aligned, ptr_natural,
1395 "naturally-aligned bucket must be distinct from the 32-byte bucket"
1396 );
1397 buf_natural.release_to_pool();
1398 }
1399
1400 #[test]
1401 #[should_panic(expected = "alignment must be a power of two")]
1402 fn test_acquire_aligned_rejects_non_power_of_two() {
1403 let _guard = TEST_LOCK
1404 .lock()
1405 .unwrap_or_else(|poisoned| poisoned.into_inner());
1406 clear_memory_pool();
1407 let _buf: ReusedBuffer<f32> = global_acquire_uninit_aligned::<f32>(16, 6);
1408 }
1409
1410 #[cfg(feature = "memory_efficient")]
1419 #[test]
1420 fn test_map_through_mmap_file_roundtrips_known_data() {
1421 let known: Vec<f32> = (0..48).map(|i| (i as f32) * 1.5 - 7.25).collect();
1423
1424 let backing_path = unique_mmap_path("test_helper");
1425 assert!(
1426 backing_path.starts_with(std::env::temp_dir()),
1427 "backing file must live under the system temp directory"
1428 );
1429
1430 let mapped = map_through_mmap_file::<f32>(known.clone(), &backing_path)
1431 .expect("memory-mapped round-trip should succeed");
1432
1433 assert_eq!(
1434 mapped, known,
1435 "as_slice() must return exactly the data written to the memory-mapped file"
1436 );
1437
1438 let _ = std::fs::remove_file(&backing_path);
1440 }
1441
1442 #[cfg(feature = "memory_efficient")]
1445 #[test]
1446 fn test_memory_mapped_array_as_slice_direct() {
1447 use scirs2_core::memory_efficient::{AccessMode, MemoryMappedArray};
1448 use scirs2_core::ndarray::Array1;
1449
1450 let known: Vec<f64> = vec![3.5, -1.25, 42.0, 7.0, 0.5, 100.0, -8.0, 256.0];
1451 let backing_path = unique_mmap_path("test_direct");
1452
1453 let array = Array1::from(known.clone());
1454 let mmap = MemoryMappedArray::<f64>::new(Some(&array), &backing_path, AccessMode::Write, 0)
1455 .expect("memory-mapped array creation should succeed");
1456
1457 let read_back = mmap.as_slice().to_vec();
1458 drop(mmap);
1459 let _ = std::fs::remove_file(&backing_path);
1460
1461 assert_eq!(
1462 read_back, known,
1463 "as_slice() over a Write-mode memory map must return the written data"
1464 );
1465 }
1466
1467 #[cfg(feature = "memory_efficient")]
1470 #[test]
1471 fn test_create_memory_mapped_tensor_uses_mmap_path() {
1472 let mut pool = GlobalMemoryPool::new();
1473 let shape = [4usize, 5];
1474 let tensor = pool
1475 .create_memory_mapped_tensor::<f32>(&shape, DeviceType::Cpu)
1476 .expect("memory-mapped tensor creation should succeed");
1477
1478 assert_eq!(tensor.numel(), 20);
1479 let dims = tensor.shape();
1480 assert_eq!(dims.dims(), &[4, 5]);
1481
1482 let data = tensor.data().expect("tensor data should be readable");
1485 assert_eq!(data.len(), 20);
1486 assert!(data.iter().all(|&x| x == 0.0_f32));
1487 }
1488}