#![allow(dead_code)]
use crate::{Tensor, TensorStorage};
use std::alloc::{handle_alloc_error, Layout};
use std::collections::{HashMap, VecDeque};
use std::marker::PhantomData;
use std::mem::{ManuallyDrop, MaybeUninit};
use std::ptr::NonNull;
use std::sync::{Arc, Mutex, Weak};
use torsh_core::{device::DeviceType, dtype::TensorElement, error::Result};
use scirs2_core::memory::GlobalBufferPool;
use scirs2_core::memory::LeakDetector;
#[cfg(feature = "memory_efficient")]
use scirs2_core::memory_efficient::{AccessMode, MemoryMappedArray};
#[cfg(feature = "memory_efficient")]
fn unique_mmap_path(tag: &str) -> std::path::PathBuf {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let seq = COUNTER.fetch_add(1, Ordering::Relaxed);
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
std::env::temp_dir().join(format!(
"torsh_mmap_{tag}_{pid}_{nanos}_{seq}.bin",
pid = std::process::id()
))
}
#[cfg(feature = "memory_efficient")]
fn map_through_mmap_file<T: TensorElement>(
data: Vec<T>,
backing_path: &std::path::Path,
) -> Result<Vec<T>> {
use scirs2_core::ndarray::Array1;
let array: Array1<T> = Array1::from(data);
let mmap = MemoryMappedArray::<T>::new(Some(&array), backing_path, AccessMode::Write, 0)
.map_err(|e| {
torsh_core::error::TorshError::IoError(format!(
"memory-mapped allocation failed at {path}: {e}",
path = backing_path.display()
))
})?;
let mapped = mmap.as_slice().to_vec();
drop(mmap);
let _ = std::fs::remove_file(backing_path);
Ok(mapped)
}
static MEMORY_POOL: std::sync::OnceLock<Arc<Mutex<GlobalMemoryPool>>> = std::sync::OnceLock::new();
pub fn init_memory_pool() -> Arc<Mutex<GlobalMemoryPool>> {
let arc = MEMORY_POOL
.get_or_init(|| {
let pool = Arc::new(Mutex::new(GlobalMemoryPool::new()));
if let Ok(mut guard) = pool.lock() {
guard.self_weak = Some(Arc::downgrade(&pool));
}
pool
})
.clone();
arc
}
pub fn get_memory_pool() -> Arc<Mutex<GlobalMemoryPool>> {
init_memory_pool()
}
struct RawEntry {
ptr: NonNull<u8>,
capacity_bytes: usize,
layout: Layout,
}
unsafe impl Send for RawEntry {}
impl Drop for RawEntry {
fn drop(&mut self) {
unsafe { std::alloc::dealloc(self.ptr.as_ptr(), self.layout) };
}
}
pub struct ReusedBuffer<T: 'static> {
ptr: NonNull<T>,
capacity: usize,
layout: Layout,
pool: Weak<Mutex<GlobalMemoryPool>>,
}
unsafe impl<T: Send + 'static> Send for ReusedBuffer<T> {}
impl<T: 'static> ReusedBuffer<T> {
pub fn as_uninit_slice_mut(&mut self) -> &mut [MaybeUninit<T>] {
unsafe {
std::slice::from_raw_parts_mut(self.ptr.as_ptr() as *mut MaybeUninit<T>, self.capacity)
}
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn as_ptr_raw(&self) -> *mut T {
self.ptr.as_ptr()
}
pub fn into_vec(self, len: usize) -> Vec<T>
where
T: Copy,
{
debug_assert!(len <= self.capacity, "len must not exceed capacity");
if self.layout.align() != std::mem::align_of::<T>() {
let initialized =
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr() as *const T, len) };
let copy = initialized.to_vec();
return copy;
}
let md = ManuallyDrop::new(self);
unsafe { Vec::from_raw_parts(md.ptr.as_ptr(), len, md.capacity) }
}
pub fn release_to_pool(self) {
let md = ManuallyDrop::new(self);
let raw_entry = RawEntry {
ptr: NonNull::new(md.ptr.as_ptr() as *mut u8)
.expect("ReusedBuffer pointer is non-null by construction"),
capacity_bytes: md.capacity * std::mem::size_of::<T>(),
layout: md.layout,
};
if let Some(pool_arc) = md.pool.upgrade() {
let mut guard = pool_arc
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let type_id = std::any::TypeId::of::<T>();
let size_class = guard.find_size_class(raw_entry.capacity_bytes);
let align = raw_entry.layout.align();
let pool_key = (type_id, size_class, align);
if let Some(bucket) = guard.pools.get_mut(&pool_key) {
if bucket.available_buffers.len() < bucket.max_buffers {
bucket.available_buffers.push_back(raw_entry);
bucket.deallocations += 1;
return;
}
}
}
}
}
impl<T: 'static> Drop for ReusedBuffer<T> {
fn drop(&mut self) {
let raw_entry = RawEntry {
ptr: NonNull::new(self.ptr.as_ptr() as *mut u8)
.expect("ReusedBuffer pointer is non-null by construction"),
capacity_bytes: self.capacity * std::mem::size_of::<T>(),
layout: self.layout,
};
if let Some(pool_arc) = self.pool.upgrade() {
let mut guard = pool_arc
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let type_id = std::any::TypeId::of::<T>();
let size_class = guard.find_size_class(raw_entry.capacity_bytes);
let align = raw_entry.layout.align();
let pool_key = (type_id, size_class, align);
if let Some(bucket) = guard.pools.get_mut(&pool_key) {
if bucket.available_buffers.len() < bucket.max_buffers {
let md_entry = ManuallyDrop::new(raw_entry);
bucket
.available_buffers
.push_back(unsafe { std::ptr::read(&*md_entry as *const RawEntry) });
bucket.deallocations += 1;
return;
}
}
}
}
}
pub struct GlobalMemoryPool {
pools: HashMap<(std::any::TypeId, usize, usize), MemoryPool>,
stats: PoolStatistics,
config: PoolConfig,
scirs2_pool: GlobalBufferPool,
leak_detector: Option<LeakDetector>,
self_weak: Option<Weak<Mutex<GlobalMemoryPool>>>,
}
#[derive(Debug)]
struct MemoryPool {
available_buffers: VecDeque<RawEntry>,
#[allow(dead_code)]
size_class: usize,
max_buffers: usize,
allocations: usize,
reuses: usize,
deallocations: usize,
}
#[derive(Debug, Clone)]
pub struct PoolConfig {
pub max_buffers_per_class: usize,
pub max_total_memory: usize,
pub auto_cleanup: bool,
pub cleanup_threshold: f64,
pub size_classes: Vec<usize>,
}
#[derive(Debug, Default, Clone)]
pub struct PoolStatistics {
pub total_allocations: usize,
pub pool_hits: usize,
pub pool_misses: usize,
pub total_bytes_allocated: usize,
pub bytes_in_pools: usize,
pub peak_memory_usage: usize,
}
#[derive(Debug)]
pub struct PooledTensor<T: TensorElement + Default> {
tensor: Tensor<T>,
pool_key: Option<(std::any::TypeId, usize, usize)>,
_phantom: PhantomData<T>,
}
impl Default for PoolConfig {
fn default() -> Self {
let size_classes = (10..31) .map(|exp| 1 << exp)
.collect();
Self {
max_buffers_per_class: 16,
max_total_memory: 1024 * 1024 * 1024, auto_cleanup: true,
cleanup_threshold: 0.8,
size_classes,
}
}
}
impl Default for GlobalMemoryPool {
fn default() -> Self {
Self::new()
}
}
fn assert_valid_alignment<T>(align: usize) {
let element_align = std::mem::align_of::<T>();
assert!(
align.is_power_of_two(),
"alignment must be a power of two (got {align})"
);
assert!(
align >= element_align,
"alignment {align} must be >= align_of::<T>() ({element_align})"
);
}
impl GlobalMemoryPool {
pub fn new() -> Self {
#[cfg(feature = "profiling")]
{
}
Self {
pools: HashMap::new(),
stats: PoolStatistics::default(),
config: PoolConfig::default(),
scirs2_pool: GlobalBufferPool::new(),
leak_detector: LeakDetector::new(Default::default()).ok(),
self_weak: None,
}
}
pub fn create_large_tensor<T: TensorElement>(
&mut self,
shape: &[usize],
device: DeviceType,
) -> Result<Tensor<T>>
where
T: Clone + Default,
{
#[cfg(feature = "profiling")]
{
}
let total_elements: usize = shape.iter().product();
let total_bytes = total_elements * std::mem::size_of::<T>();
if total_bytes > 100 * 1024 * 1024 {
self.create_memory_mapped_tensor(shape, device)
} else if total_bytes > 10 * 1024 * 1024 {
self.create_chunked_tensor(shape, device)
} else if total_bytes > 1024 * 1024 {
self.create_pooled_tensor(shape, device)
} else {
Tensor::zeros(shape, device)
}
}
fn create_memory_mapped_tensor<T: TensorElement>(
&mut self,
shape: &[usize],
device: DeviceType,
) -> Result<Tensor<T>>
where
T: Clone + Default,
{
let total_elements: usize = shape.iter().product();
let data = vec![T::default(); total_elements];
#[cfg(feature = "memory_efficient")]
{
let backing_path = unique_mmap_path("tensor");
let mapped = map_through_mmap_file::<T>(data, &backing_path)?;
Tensor::from_data(mapped, shape.to_vec(), device)
}
#[cfg(not(feature = "memory_efficient"))]
{
Tensor::from_data(data, shape.to_vec(), device)
}
}
fn create_chunked_tensor<T: TensorElement>(
&mut self,
shape: &[usize],
device: DeviceType,
) -> Result<Tensor<T>>
where
T: Clone + Default,
{
let total_elements: usize = shape.iter().product();
let chunk_size = (1024 * 1024) / std::mem::size_of::<T>().max(1); let num_chunks = (total_elements + chunk_size - 1) / chunk_size;
let _ = (total_elements, num_chunks, chunk_size);
let data = vec![T::default(); total_elements];
Tensor::from_data(data, shape.to_vec(), device)
}
fn create_pooled_tensor<T: TensorElement>(
&mut self,
shape: &[usize],
device: DeviceType,
) -> Result<Tensor<T>>
where
T: Clone + Default,
{
let total_elements: usize = shape.iter().product();
let buffer_size = total_elements * std::mem::size_of::<T>();
let _ = (buffer_size, total_elements);
let data = vec![T::default(); total_elements];
self.stats.pool_hits += 1;
Tensor::from_data(data, shape.to_vec(), device)
}
pub fn create_lazy_tensor<T: TensorElement>(
&mut self,
shape: &[usize],
device: DeviceType,
) -> Result<Tensor<T>>
where
T: Clone + Default,
{
#[cfg(feature = "profiling")]
{
}
let total_elements: usize = shape.iter().product();
let data = vec![T::default(); total_elements];
Tensor::from_data(data, shape.to_vec(), device)
}
pub fn create_zero_copy_view<T: TensorElement>(
&self,
source: &Tensor<T>,
offset: usize,
shape: &[usize],
) -> Result<Tensor<T>>
where
T: Clone,
{
#[cfg(feature = "profiling")]
{
}
let source_data = source.data()?;
let view_data = source_data[offset..offset + shape.iter().product::<usize>()].to_vec();
Tensor::from_data(view_data, shape.to_vec(), source.device())
}
pub fn get_enhanced_stats(&self) -> PoolStatistics {
self.stats.clone()
}
pub fn acquire_uninit<T: 'static>(&mut self, count: usize) -> ReusedBuffer<T> {
self.acquire_uninit_aligned::<T>(count, std::mem::align_of::<T>())
}
pub fn acquire_uninit_aligned<T: 'static>(
&mut self,
count: usize,
align: usize,
) -> ReusedBuffer<T> {
assert_valid_alignment::<T>(align);
let element_size = std::mem::size_of::<T>();
let size_bytes = count * element_size;
let size_class = self.find_size_class(size_bytes);
let type_id = std::any::TypeId::of::<T>();
let pool_key = (type_id, size_class, align);
let layout =
Layout::from_size_align(size_bytes.max(1), align).expect("size and align are valid");
self.stats.total_allocations += 1;
self.stats.total_bytes_allocated += size_bytes;
if let Some(bucket) = self.pools.get_mut(&pool_key) {
let mut found_idx: Option<usize> = None;
for (i, entry) in bucket.available_buffers.iter().enumerate() {
if entry.capacity_bytes >= size_bytes && entry.layout.align() >= align {
found_idx = Some(i);
break;
}
}
if let Some(idx) = found_idx {
let raw_entry = bucket
.available_buffers
.remove(idx)
.expect("index was valid moments ago");
self.stats.pool_hits += 1;
bucket.reuses += 1;
let ptr = NonNull::new(raw_entry.ptr.as_ptr() as *mut T)
.expect("RawEntry pointer is non-null by construction");
let actual_capacity = raw_entry.capacity_bytes / element_size;
let entry_layout = raw_entry.layout;
std::mem::forget(raw_entry);
let weak = self.self_weak.clone().unwrap_or_else(Weak::new);
return ReusedBuffer {
ptr,
capacity: actual_capacity,
layout: entry_layout,
pool: weak,
};
}
}
self.stats.pool_misses += 1;
self.pools.entry(pool_key).or_insert_with(|| MemoryPool {
available_buffers: VecDeque::new(),
size_class,
max_buffers: self.config.max_buffers_per_class,
allocations: 0,
reuses: 0,
deallocations: 0,
});
if let Some(bucket) = self.pools.get_mut(&pool_key) {
bucket.allocations += 1;
}
let raw_ptr = unsafe { std::alloc::alloc(layout) };
let ptr = NonNull::new(raw_ptr as *mut T).unwrap_or_else(|| handle_alloc_error(layout));
let weak = self.self_weak.clone().unwrap_or_else(Weak::new);
ReusedBuffer {
ptr,
capacity: count,
layout,
pool: weak,
}
}
#[deprecated = "Use global_acquire_uninit instead for zero-copy buffer reuse"]
pub fn allocate<T: TensorElement + Default + 'static>(&mut self, count: usize) -> Vec<T> {
let mut buf = self.acquire_uninit::<T>(count);
for slot in buf.as_uninit_slice_mut() {
slot.write(T::default());
}
buf.into_vec(count)
}
pub fn find_size_class(&self, size_bytes: usize) -> usize {
self.config
.size_classes
.iter()
.position(|&class_size| size_bytes <= class_size)
.unwrap_or(self.config.size_classes.len() - 1)
}
pub fn deallocate<T: 'static>(&mut self, data: Vec<T>) {
drop(data);
}
pub fn clear(&mut self) {
self.pools.clear();
self.stats = PoolStatistics::default();
}
pub fn get_statistics(&self) -> &PoolStatistics {
&self.stats
}
pub fn hit_rate(&self) -> f64 {
if self.stats.total_allocations == 0 {
0.0
} else {
self.stats.pool_hits as f64 / self.stats.total_allocations as f64
}
}
pub fn cleanup(&mut self) {
if self.config.auto_cleanup {
let threshold_bytes =
(self.config.max_total_memory as f64 * self.config.cleanup_threshold) as usize;
if self.stats.total_bytes_allocated > threshold_bytes {
self.pools
.retain(|_, pool| !pool.available_buffers.is_empty());
}
}
}
}
impl std::fmt::Debug for GlobalMemoryPool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GlobalMemoryPool")
.field("pools", &self.pools)
.field("stats", &self.stats)
.field("config", &self.config)
.field("scirs2_pool", &"<GlobalBufferPool>")
.field(
"leak_detector",
&self.leak_detector.as_ref().map(|_| "<LeakDetector>"),
)
.finish()
}
}
impl std::fmt::Debug for RawEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RawEntry")
.field("capacity_bytes", &self.capacity_bytes)
.finish()
}
}
pub fn global_acquire_uninit<T: 'static>(count: usize) -> ReusedBuffer<T> {
let pool_arc = get_memory_pool();
let mut guard = pool_arc
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
guard.acquire_uninit::<T>(count)
}
pub fn global_acquire_uninit_aligned<T: 'static>(count: usize, align: usize) -> ReusedBuffer<T> {
assert_valid_alignment::<T>(align);
let pool_arc = get_memory_pool();
let mut guard = pool_arc
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
guard.acquire_uninit_aligned::<T>(count, align)
}
pub type EnhancedMemoryStats = PoolStatistics;
impl<T: TensorElement> Tensor<T> {
pub fn create_efficient(shape: &[usize], device: DeviceType) -> Result<Self>
where
T: Clone + Default,
{
let binding = get_memory_pool();
let mut pool = binding
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
pool.create_large_tensor::<T>(shape, device)
}
pub fn lazy(shape: &[usize], device: DeviceType) -> Result<Self>
where
T: Clone + Default,
{
let binding = get_memory_pool();
let mut pool = binding
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
pool.create_lazy_tensor::<T>(shape, device)
}
pub fn memory_mapped(shape: &[usize], device: DeviceType) -> Result<Self>
where
T: Clone + Default,
{
#[cfg(feature = "profiling")]
{
}
let total_elements: usize = shape.iter().product();
let data = vec![T::default(); total_elements];
Self::from_data(data, shape.to_vec(), device)
}
pub fn chunked(shape: &[usize], chunk_size: usize, device: DeviceType) -> Result<Self>
where
T: Clone + Default,
{
#[cfg(feature = "profiling")]
{
}
let total_elements: usize = shape.iter().product();
let effective_chunk_size = if chunk_size == 0 {
let default_chunk_bytes = 64 * 1024;
let element_size = std::mem::size_of::<T>();
(default_chunk_bytes / element_size.max(1)).max(1)
} else {
chunk_size
};
let cache_line_elements = 64 / std::mem::size_of::<T>().max(1);
let aligned_chunk_size = ((effective_chunk_size + cache_line_elements - 1)
/ cache_line_elements)
* cache_line_elements;
let _ = (total_elements, effective_chunk_size, aligned_chunk_size);
let data = vec![T::default(); total_elements];
Self::from_data(data, shape.to_vec(), device)
}
pub fn disk_backed(shape: &[usize], device: DeviceType, file_path: Option<&str>) -> Result<Self>
where
T: Clone + Default,
{
#[cfg(feature = "profiling")]
{
}
let total_elements: usize = shape.iter().product();
let backing_path = file_path.map(std::path::PathBuf::from);
let storage =
TensorStorage::memory_mapped_filled(total_elements, T::default(), backing_path)?;
let mut tensor = Self::from_data(Vec::new(), Vec::new(), device)?;
tensor.storage = storage;
tensor.shape = torsh_core::shape::Shape::new(shape.to_vec());
Ok(tensor)
}
pub fn process_chunked<F, R>(&self, chunk_size: usize, mut processor: F) -> Result<Vec<R>>
where
F: FnMut(&[T]) -> Result<R>,
T: Clone,
{
#[cfg(feature = "profiling")]
{
}
let data = self.data()?;
let mut results = Vec::new();
let effective_chunk_size = chunk_size;
for chunk in data.chunks(effective_chunk_size) {
results.push(processor(chunk)?);
}
Ok(results)
}
}
impl MemoryPool {
fn new(size_class: usize, max_buffers: usize) -> Self {
Self {
available_buffers: VecDeque::new(),
size_class,
max_buffers,
allocations: 0,
reuses: 0,
deallocations: 0,
}
}
}
impl<T: TensorElement + Copy + Default> PooledTensor<T> {
pub fn new(shape: &[usize], device: DeviceType) -> Result<Self> {
let numel = shape.iter().product::<usize>();
let pool = get_memory_pool();
let data = {
let mut pool_guard = pool.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
#[allow(deprecated)]
pool_guard.allocate::<T>(numel)
};
let tensor = Tensor::from_data(data, shape.to_vec(), device)?;
let type_id = std::any::TypeId::of::<T>();
let size_class = {
let pool_guard = pool.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
pool_guard.find_size_class(numel * std::mem::size_of::<T>())
};
let align = std::mem::align_of::<T>();
Ok(Self {
tensor,
pool_key: Some((type_id, size_class, align)),
_phantom: PhantomData,
})
}
pub fn zeros(shape: &[usize], device: DeviceType) -> Result<Self> {
let mut pooled = Self::new(shape, device)?;
let numel = shape.iter().product::<usize>();
let data = vec![T::default(); numel];
pooled.tensor.storage = TensorStorage::create_optimal(data)?;
Ok(pooled)
}
pub fn ones(shape: &[usize], device: DeviceType) -> Result<Self>
where
T: std::ops::Add<Output = T> + From<f32>,
{
let mut pooled = Self::new(shape, device)?;
let numel = shape.iter().product::<usize>();
let data = vec![T::from(1.0f32); numel];
pooled.tensor.storage = TensorStorage::create_optimal(data)?;
Ok(pooled)
}
pub fn tensor(&self) -> &Tensor<T> {
&self.tensor
}
pub fn tensor_mut(&mut self) -> &mut Tensor<T> {
&mut self.tensor
}
pub fn into_tensor(mut self) -> Tensor<T> {
self.pool_key = None; self.tensor.clone()
}
}
impl<T: TensorElement + std::default::Default> Drop for PooledTensor<T> {
fn drop(&mut self) {
if let Some((_type_id, _size_class, _align)) = self.pool_key {
if let Ok(data) = self.tensor.to_vec() {
let pool = get_memory_pool();
let mut pool_guard = pool.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
pool_guard.deallocate(data);
}
}
}
}
impl<T: TensorElement + Copy + Default> Tensor<T> {
pub fn pooled(shape: &[usize], device: DeviceType) -> Result<PooledTensor<T>> {
PooledTensor::new(shape, device)
}
pub fn temporary(shape: &[usize], device: DeviceType) -> Result<PooledTensor<T>> {
PooledTensor::new(shape, device)
}
}
pub fn clear_memory_pool() {
if let Some(pool) = MEMORY_POOL.get() {
pool.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.clear();
}
}
pub fn get_pool_statistics() -> PoolStatistics {
get_memory_pool()
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.get_statistics()
.clone()
}
pub fn get_pool_hit_rate() -> f64 {
get_memory_pool()
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.hit_rate()
}
pub fn cleanup_memory_pool() {
get_memory_pool()
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.cleanup();
}
#[cfg(test)]
mod tests {
use super::*;
static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn test_memory_pool_basic() {
let _guard = TEST_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
clear_memory_pool();
let pooled = PooledTensor::<f32>::zeros(&[100, 100], DeviceType::Cpu)
.expect("zeros creation should succeed");
assert_eq!(pooled.tensor().numel(), 10000);
drop(pooled);
let _pooled2 = PooledTensor::<f32>::zeros(&[100, 100], DeviceType::Cpu)
.expect("zeros creation should succeed");
let stats = get_pool_statistics();
assert!(stats.pool_hits > 0 || stats.pool_misses > 0);
}
#[test]
fn test_pool_statistics() {
let _guard = TEST_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
clear_memory_pool();
let _pooled1 = PooledTensor::<f32>::zeros(&[50, 50], DeviceType::Cpu)
.expect("zeros creation should succeed");
let _pooled2 = PooledTensor::<f32>::ones(&[50, 50], DeviceType::Cpu)
.expect("ones creation should succeed");
let stats = get_pool_statistics();
assert!(stats.total_allocations >= 2);
assert!(stats.total_bytes_allocated > 0);
}
#[test]
fn test_pool_cleanup() {
let _guard = TEST_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
clear_memory_pool();
for _ in 0..10 {
let _temp = PooledTensor::<f32>::zeros(&[100, 100], DeviceType::Cpu)
.expect("zeros creation should succeed");
}
cleanup_memory_pool();
let _stats = get_pool_statistics();
}
#[test]
fn test_pooled_tensor_conversion() {
let _guard = TEST_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let pooled = PooledTensor::<f32>::ones(&[10, 10], DeviceType::Cpu)
.expect("ones creation should succeed");
let tensor = pooled.into_tensor();
assert_eq!(tensor.numel(), 100);
}
#[test]
fn test_acquire_truly_reuses_allocation() {
let _guard = TEST_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
clear_memory_pool();
let buf1: ReusedBuffer<f32> = global_acquire_uninit::<f32>(1024);
let ptr1 = buf1.as_ptr_raw();
buf1.release_to_pool();
let buf2: ReusedBuffer<f32> = global_acquire_uninit::<f32>(1024);
let ptr2 = buf2.as_ptr_raw();
buf2.release_to_pool();
assert_eq!(
ptr1, ptr2,
"pool should return the same allocation on second acquire"
);
}
#[test]
fn test_into_vec_transfers_ownership() {
let _guard = TEST_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
clear_memory_pool();
let mut buf: ReusedBuffer<f32> = global_acquire_uninit::<f32>(64);
for slot in buf.as_uninit_slice_mut() {
slot.write(1.0_f32);
}
let vec = buf.into_vec(64);
assert_eq!(vec.len(), 64);
assert!(vec.iter().all(|&x| x == 1.0_f32));
}
#[test]
fn test_drop_returns_to_pool() {
let _guard = TEST_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
clear_memory_pool();
{
let buf: ReusedBuffer<f32> = global_acquire_uninit::<f32>(256);
drop(buf);
}
let buf2: ReusedBuffer<f32> = global_acquire_uninit::<f32>(256);
buf2.release_to_pool();
let stats = get_pool_statistics();
assert!(
stats.pool_hits >= 1,
"expected at least one pool hit after drop-return"
);
}
#[test]
fn test_acquire_capacity_and_uninit_slice() {
let _guard = TEST_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
clear_memory_pool();
let buf: ReusedBuffer<u64> = global_acquire_uninit::<u64>(32);
assert_eq!(buf.capacity(), 32);
buf.release_to_pool();
}
#[test]
fn test_acquire_aligned_returns_simd_aligned_pointer() {
let _guard = TEST_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
clear_memory_pool();
let buf: ReusedBuffer<f32> = global_acquire_uninit_aligned::<f32>(1024, 32);
assert_eq!(buf.capacity(), 1024);
let addr = buf.as_ptr_raw() as usize;
assert_eq!(
addr % 32,
0,
"buffer pointer {addr:#x} must be 32-byte aligned"
);
buf.release_to_pool();
}
#[test]
fn test_acquire_aligned_pool_hit_on_release() {
let _guard = TEST_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
clear_memory_pool();
let buf1: ReusedBuffer<f32> = global_acquire_uninit_aligned::<f32>(2048, 32);
let ptr1 = buf1.as_ptr_raw();
let cap1 = buf1.capacity();
buf1.release_to_pool();
let buf2: ReusedBuffer<f32> = global_acquire_uninit_aligned::<f32>(2048, 32);
let ptr2 = buf2.as_ptr_raw();
let cap2 = buf2.capacity();
assert_eq!(
ptr1, ptr2,
"aligned bucket should return the same allocation on second acquire"
);
assert_eq!(cap1, cap2, "capacity should match across reuse");
assert_eq!(ptr2 as usize % 32, 0, "reused buffer must remain aligned");
buf2.release_to_pool();
}
#[test]
fn test_aligned_and_natural_buckets_are_independent() {
let _guard = TEST_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
clear_memory_pool();
let buf_aligned: ReusedBuffer<f32> = global_acquire_uninit_aligned::<f32>(512, 32);
let ptr_aligned = buf_aligned.as_ptr_raw();
buf_aligned.release_to_pool();
let buf_natural: ReusedBuffer<f32> = global_acquire_uninit::<f32>(512);
let ptr_natural = buf_natural.as_ptr_raw();
assert_ne!(
ptr_aligned, ptr_natural,
"naturally-aligned bucket must be distinct from the 32-byte bucket"
);
buf_natural.release_to_pool();
}
#[test]
#[should_panic(expected = "alignment must be a power of two")]
fn test_acquire_aligned_rejects_non_power_of_two() {
let _guard = TEST_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
clear_memory_pool();
let _buf: ReusedBuffer<f32> = global_acquire_uninit_aligned::<f32>(16, 6);
}
#[cfg(feature = "memory_efficient")]
#[test]
fn test_map_through_mmap_file_roundtrips_known_data() {
let known: Vec<f32> = (0..48).map(|i| (i as f32) * 1.5 - 7.25).collect();
let backing_path = unique_mmap_path("test_helper");
assert!(
backing_path.starts_with(std::env::temp_dir()),
"backing file must live under the system temp directory"
);
let mapped = map_through_mmap_file::<f32>(known.clone(), &backing_path)
.expect("memory-mapped round-trip should succeed");
assert_eq!(
mapped, known,
"as_slice() must return exactly the data written to the memory-mapped file"
);
let _ = std::fs::remove_file(&backing_path);
}
#[cfg(feature = "memory_efficient")]
#[test]
fn test_memory_mapped_array_as_slice_direct() {
use scirs2_core::memory_efficient::{AccessMode, MemoryMappedArray};
use scirs2_core::ndarray::Array1;
let known: Vec<f64> = vec![3.5, -1.25, 42.0, 7.0, 0.5, 100.0, -8.0, 256.0];
let backing_path = unique_mmap_path("test_direct");
let array = Array1::from(known.clone());
let mmap = MemoryMappedArray::<f64>::new(Some(&array), &backing_path, AccessMode::Write, 0)
.expect("memory-mapped array creation should succeed");
let read_back = mmap.as_slice().to_vec();
drop(mmap);
let _ = std::fs::remove_file(&backing_path);
assert_eq!(
read_back, known,
"as_slice() over a Write-mode memory map must return the written data"
);
}
#[cfg(feature = "memory_efficient")]
#[test]
fn test_create_memory_mapped_tensor_uses_mmap_path() {
let mut pool = GlobalMemoryPool::new();
let shape = [4usize, 5];
let tensor = pool
.create_memory_mapped_tensor::<f32>(&shape, DeviceType::Cpu)
.expect("memory-mapped tensor creation should succeed");
assert_eq!(tensor.numel(), 20);
let dims = tensor.shape();
assert_eq!(dims.dims(), &[4, 5]);
let data = tensor.data().expect("tensor data should be readable");
assert_eq!(data.len(), 20);
assert!(data.iter().all(|&x| x == 0.0_f32));
}
}