use crate::core::error::{Error, Result};
use crate::storage::unified_memory::*;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::mem;
use std::ops::{Deref, Range};
use std::ptr::NonNull;
use std::slice;
use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
pub const CACHE_LINE_SIZE: usize = 64;
pub const PAGE_SIZE: usize = 4096;
mod sealed {
pub trait Sealed {}
}
pub unsafe trait ZeroCopyPod: sealed::Sealed + Copy + Sized + 'static {}
macro_rules! impl_zero_copy_pod {
($($ty:ty),* $(,)?) => {
$(
impl sealed::Sealed for $ty {}
unsafe impl ZeroCopyPod for $ty {}
)*
};
}
impl_zero_copy_pod!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize, f32, f64);
#[derive(Debug)]
struct ViewPtr<T>(NonNull<T>);
unsafe impl<T: Send> Send for ViewPtr<T> {}
unsafe impl<T: Sync> Sync for ViewPtr<T> {}
impl<T> ViewPtr<T> {
fn as_ptr(&self) -> *mut T {
self.0.as_ptr()
}
}
#[derive(Debug)]
enum ViewOwner {
Empty,
Pool(PoolAllocation),
Storage(Arc<StorageHandle>),
}
impl ViewOwner {
fn backing_bytes(&self) -> usize {
match self {
ViewOwner::Empty => 0,
ViewOwner::Pool(allocation) => allocation.size(),
ViewOwner::Storage(handle) => handle.metadata.size,
}
}
}
#[derive(Debug)]
pub struct ZeroCopyView<T> {
data: ViewPtr<T>,
len: usize,
capacity: usize,
layout: MemoryLayout,
owner: Arc<ViewOwner>,
_phantom: PhantomData<T>,
}
impl<T> ZeroCopyView<T> {
pub unsafe fn new(
data: NonNull<T>,
len: usize,
capacity: usize,
layout: MemoryLayout,
storage_handle: Arc<StorageHandle>,
) -> Self {
Self {
data: ViewPtr(data),
len,
capacity,
layout,
owner: Arc::new(ViewOwner::Storage(storage_handle)),
_phantom: PhantomData,
}
}
unsafe fn from_pool(
data: NonNull<T>,
len: usize,
capacity: usize,
layout: MemoryLayout,
allocation: PoolAllocation,
) -> Self {
Self {
data: ViewPtr(data),
len,
capacity,
layout,
owner: Arc::new(ViewOwner::Pool(allocation)),
_phantom: PhantomData,
}
}
fn empty(layout: MemoryLayout) -> Self {
Self {
data: ViewPtr(NonNull::dangling()),
len: 0,
capacity: 0,
layout,
owner: Arc::new(ViewOwner::Empty),
_phantom: PhantomData,
}
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn layout(&self) -> &MemoryLayout {
&self.layout
}
pub fn as_slice(&self) -> &[T] {
unsafe { slice::from_raw_parts(self.data.as_ptr(), self.len) }
}
pub unsafe fn as_mut_slice(&mut self) -> &mut [T] {
slice::from_raw_parts_mut(self.data.as_ptr(), self.len)
}
pub fn subview(&self, range: Range<usize>) -> Result<ZeroCopyView<T>> {
if range.start > self.len || range.end > self.len || range.start > range.end {
return Err(Error::InvalidOperation(
"Invalid range for subview".to_string(),
));
}
let new_len = range.end - range.start;
let new_data = unsafe { NonNull::new_unchecked(self.data.as_ptr().add(range.start)) };
let mut layout = self.layout.clone();
layout.start_address = new_data.as_ptr() as usize;
layout.cache_aligned = layout.start_address % CACHE_LINE_SIZE == 0;
Ok(ZeroCopyView {
data: ViewPtr(new_data),
len: new_len,
capacity: self.capacity - range.start,
layout,
owner: Arc::clone(&self.owner),
_phantom: PhantomData,
})
}
pub fn as_ptr(&self) -> *const T {
self.data.as_ptr()
}
pub fn is_cache_aligned(&self) -> bool {
self.data.as_ptr() as usize % CACHE_LINE_SIZE == 0
}
pub fn memory_address(&self) -> usize {
self.data.as_ptr() as usize
}
pub fn backing_bytes(&self) -> usize {
self.owner.backing_bytes()
}
}
impl<T> Deref for ZeroCopyView<T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
#[derive(Debug, Clone)]
pub struct MemoryLayout {
pub start_address: usize,
pub element_size: usize,
pub stride: usize,
pub alignment: usize,
pub cache_aligned: bool,
pub numa_node: Option<u32>,
}
impl MemoryLayout {
pub fn new<T>() -> Self {
Self {
start_address: 0,
element_size: mem::size_of::<T>(),
stride: mem::size_of::<T>(),
alignment: mem::align_of::<T>(),
cache_aligned: false,
numa_node: None,
}
}
pub fn with_cache_alignment(mut self) -> Self {
self.cache_aligned = true;
self.alignment = self.alignment.max(CACHE_LINE_SIZE);
self
}
pub fn with_numa_node(mut self, node: u32) -> Self {
self.numa_node = Some(node);
self
}
}
pub struct CacheAwareAllocator {
cache_topology: CacheTopology,
memory_pools: HashMap<CacheLevel, MemoryPool>,
stats: AllocationStats,
}
impl CacheAwareAllocator {
pub fn new() -> Result<Self> {
let cache_topology = CacheTopology::detect()?;
let mut memory_pools = HashMap::new();
memory_pools.insert(CacheLevel::L1, MemoryPool::new(64 * 1024)?); memory_pools.insert(CacheLevel::L2, MemoryPool::new(512 * 1024)?); memory_pools.insert(CacheLevel::L3, MemoryPool::new(4 * 1024 * 1024)?); memory_pools.insert(CacheLevel::Memory, MemoryPool::new(64 * 1024 * 1024)?);
Ok(Self {
cache_topology,
memory_pools,
stats: AllocationStats::new(),
})
}
pub fn allocate_aligned<T: ZeroCopyPod>(
&mut self,
count: usize,
cache_level: CacheLevel,
) -> Result<ZeroCopyView<T>> {
let view = unsafe { self.allocate_uninit::<T>(count, cache_level)? };
unsafe { std::ptr::write_bytes(view.data.as_ptr(), 0u8, count) };
Ok(view)
}
pub unsafe fn allocate_uninit<T>(
&mut self,
count: usize,
cache_level: CacheLevel,
) -> Result<ZeroCopyView<T>> {
let element_size = mem::size_of::<T>();
if element_size == 0 {
return Err(Error::InvalidInput(
"Zero-sized types cannot be allocated from a memory pool".to_string(),
));
}
let alignment = CACHE_LINE_SIZE.max(mem::align_of::<T>());
let size = count.checked_mul(element_size).ok_or_else(|| {
Error::InvalidInput(format!(
"Allocation size overflow: {count} x {element_size} bytes"
))
})?;
let mut layout = MemoryLayout {
start_address: 0,
element_size,
stride: element_size,
alignment,
cache_aligned: true,
numa_node: self.cache_topology.numa_node,
};
if size == 0 {
return Ok(ZeroCopyView::empty(layout));
}
let allocation = {
let pool = self
.memory_pools
.get(&cache_level)
.ok_or_else(|| Error::InvalidOperation("Cache level not supported".to_string()))?;
pool.allocate_aligned(size, alignment)?
};
layout.start_address = allocation.address();
let data = NonNull::new(allocation.address() as *mut T)
.ok_or_else(|| Error::InvalidOperation("Null pointer allocation".to_string()))?;
self.stats.record_allocation(size);
let live = self.bytes_in_use();
self.stats.current_usage = live;
self.stats.peak_usage = self.stats.peak_usage.max(live);
Ok(ZeroCopyView::from_pool(
data, count, count, layout, allocation,
))
}
pub fn bytes_in_use(&self) -> usize {
self.memory_pools
.values()
.map(|pool| pool.bytes_in_use())
.sum()
}
pub fn stats(&self) -> AllocationStats {
let mut stats = self.stats.clone();
stats.current_usage = self.bytes_in_use();
stats.peak_usage = stats.peak_usage.max(stats.current_usage);
stats
}
pub fn cache_topology(&self) -> &CacheTopology {
&self.cache_topology
}
}
#[derive(Debug, Clone)]
pub struct CacheTopology {
pub l1_cache_size: usize,
pub l2_cache_size: usize,
pub l3_cache_size: usize,
pub cache_line_size: usize,
pub cpu_cores: usize,
pub numa_node: Option<u32>,
pub probed: bool,
}
static CACHE_TOPOLOGY: OnceLock<CacheTopology> = OnceLock::new();
impl CacheTopology {
pub fn detect() -> Result<Self> {
Ok(CACHE_TOPOLOGY
.get_or_init(|| Self::probe().unwrap_or_else(Self::defaults))
.clone())
}
pub fn defaults() -> Self {
Self {
l1_cache_size: 32 * 1024,
l2_cache_size: 256 * 1024,
l3_cache_size: 8 * 1024 * 1024,
cache_line_size: CACHE_LINE_SIZE,
cpu_cores: num_cpus::get(),
numa_node: None,
probed: false,
}
}
#[cfg(target_os = "linux")]
fn probe() -> Option<Self> {
let mut topology = Self::defaults();
let mut found_any = false;
for index in 0..16 {
let dir = format!("/sys/devices/system/cpu/cpu0/cache/index{index}");
let level = match read_sysfs_usize(&format!("{dir}/level")) {
Some(level) => level,
None => break,
};
let cache_type = read_sysfs_string(&format!("{dir}/type")).unwrap_or_default();
let size = match read_sysfs_string(&format!("{dir}/size"))
.as_deref()
.and_then(parse_cache_size)
{
Some(size) => size,
None => continue,
};
match (level, cache_type.as_str()) {
(1, "Data") | (1, "Unified") => {
topology.l1_cache_size = size;
found_any = true;
}
(2, _) => {
topology.l2_cache_size = size;
found_any = true;
}
(3, _) => {
topology.l3_cache_size = size;
found_any = true;
}
_ => {}
}
if let Some(line) = read_sysfs_usize(&format!("{dir}/coherency_line_size")) {
if line > 0 {
topology.cache_line_size = line;
}
}
}
if found_any {
topology.probed = true;
Some(topology)
} else {
None
}
}
#[cfg(not(target_os = "linux"))]
fn probe() -> Option<Self> {
None
}
pub fn optimal_cache_level(&self, size: usize) -> CacheLevel {
if size <= self.l1_cache_size / 2 {
CacheLevel::L1
} else if size <= self.l2_cache_size / 2 {
CacheLevel::L2
} else if size <= self.l3_cache_size / 2 {
CacheLevel::L3
} else {
CacheLevel::Memory
}
}
}
#[cfg(target_os = "linux")]
fn read_sysfs_string(path: &str) -> Option<String> {
std::fs::read_to_string(path)
.ok()
.map(|value| value.trim().to_string())
}
#[cfg(target_os = "linux")]
fn read_sysfs_usize(path: &str) -> Option<usize> {
read_sysfs_string(path).and_then(|value| value.parse::<usize>().ok())
}
#[cfg(target_os = "linux")]
fn parse_cache_size(raw: &str) -> Option<usize> {
let raw = raw.trim();
let (digits, multiplier) = match raw.chars().last()? {
'K' | 'k' => (&raw[..raw.len() - 1], 1024usize),
'M' | 'm' => (&raw[..raw.len() - 1], 1024 * 1024),
'G' | 'g' => (&raw[..raw.len() - 1], 1024 * 1024 * 1024),
_ => (raw, 1usize),
};
digits
.trim()
.parse::<usize>()
.ok()
.and_then(|value| value.checked_mul(multiplier))
.filter(|size| *size > 0)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CacheLevel {
L1,
L2,
L3,
Memory,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct FreeRegion {
start: usize,
size: usize,
}
struct PoolBase(NonNull<u8>);
unsafe impl Send for PoolBase {}
unsafe impl Sync for PoolBase {}
impl std::fmt::Debug for PoolBase {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "PoolBase({:p})", self.0.as_ptr())
}
}
#[derive(Debug)]
struct MemoryPoolInner {
size: usize,
layout: std::alloc::Layout,
base: PoolBase,
free: Mutex<Vec<FreeRegion>>,
}
impl MemoryPoolInner {
fn lock_free(&self) -> MutexGuard<'_, Vec<FreeRegion>> {
self.free
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
fn base_address(&self) -> usize {
self.base.0.as_ptr() as usize
}
fn allocate(self: &Arc<Self>, size: usize, alignment: usize) -> Result<PoolAllocation> {
if size == 0 {
return Err(Error::InvalidInput(
"Cannot allocate a zero-sized block from a memory pool".to_string(),
));
}
if !alignment.is_power_of_two() {
return Err(Error::InvalidInput(format!(
"Alignment must be a power of two, got {alignment}"
)));
}
let base = self.base_address();
let mut free = self.lock_free();
for index in 0..free.len() {
let region = free[index];
let region_addr = base + region.start;
let aligned_addr = match region_addr.checked_add(alignment - 1) {
Some(value) => value & !(alignment - 1),
None => continue,
};
let head = aligned_addr - region_addr;
if head > region.size || region.size - head < size {
continue;
}
let alloc_start = region.start + head;
let tail_start = alloc_start + size;
let tail_size = region.size - head - size;
if head > 0 {
free[index] = FreeRegion {
start: region.start,
size: head,
};
if tail_size > 0 {
free.insert(
index + 1,
FreeRegion {
start: tail_start,
size: tail_size,
},
);
}
} else if tail_size > 0 {
free[index] = FreeRegion {
start: tail_start,
size: tail_size,
};
} else {
free.remove(index);
}
drop(free);
return Ok(PoolAllocation {
pool: Arc::clone(self),
offset: alloc_start,
size,
alignment,
});
}
Err(Error::InvalidOperation(format!(
"Not enough memory in pool: requested {size} bytes (alignment {alignment}), {} bytes free",
free.iter().map(|region| region.size).sum::<usize>()
)))
}
fn deallocate(&self, offset: usize, size: usize) {
if size == 0 {
return;
}
let mut free = self.lock_free();
let index = free
.iter()
.position(|region| region.start > offset)
.unwrap_or(free.len());
free.insert(
index,
FreeRegion {
start: offset,
size,
},
);
if index + 1 < free.len() && free[index].start + free[index].size == free[index + 1].start {
free[index].size += free[index + 1].size;
free.remove(index + 1);
}
if index > 0 && free[index - 1].start + free[index - 1].size == free[index].start {
free[index - 1].size += free[index].size;
free.remove(index);
}
}
fn free_bytes(&self) -> usize {
self.lock_free().iter().map(|region| region.size).sum()
}
}
impl Drop for MemoryPoolInner {
fn drop(&mut self) {
unsafe {
std::alloc::dealloc(self.base.0.as_ptr(), self.layout);
}
}
}
#[derive(Debug)]
pub struct PoolAllocation {
pool: Arc<MemoryPoolInner>,
offset: usize,
size: usize,
alignment: usize,
}
impl PoolAllocation {
pub fn address(&self) -> usize {
self.pool.base_address() + self.offset
}
pub fn size(&self) -> usize {
self.size
}
pub fn alignment(&self) -> usize {
self.alignment
}
pub fn block(&self) -> MemoryBlock {
MemoryBlock {
ptr: self.address() as *mut u8,
size: self.size,
alignment: self.alignment,
}
}
}
impl Drop for PoolAllocation {
fn drop(&mut self) {
self.pool.deallocate(self.offset, self.size);
}
}
#[derive(Debug, Clone)]
pub struct MemoryPool {
inner: Arc<MemoryPoolInner>,
}
impl MemoryPool {
pub fn new(size: usize) -> Result<Self> {
if size == 0 {
return Err(Error::InvalidInput(
"Memory pool size must be greater than zero".to_string(),
));
}
let layout = std::alloc::Layout::from_size_align(size, PAGE_SIZE)
.map_err(|_| Error::InvalidOperation("Invalid memory layout".to_string()))?;
let ptr = unsafe { std::alloc::alloc(layout) };
let base = NonNull::new(ptr)
.ok_or_else(|| Error::InvalidOperation("Memory allocation failed".to_string()))?;
Ok(Self {
inner: Arc::new(MemoryPoolInner {
size,
layout,
base: PoolBase(base),
free: Mutex::new(vec![FreeRegion { start: 0, size }]),
}),
})
}
pub fn allocate_aligned(&self, size: usize, alignment: usize) -> Result<PoolAllocation> {
self.inner.allocate(size, alignment)
}
pub fn size(&self) -> usize {
self.inner.size
}
pub fn free_bytes(&self) -> usize {
self.inner.free_bytes()
}
pub fn bytes_in_use(&self) -> usize {
self.inner.size - self.inner.free_bytes()
}
pub fn free_region_count(&self) -> usize {
self.inner.lock_free().len()
}
}
#[derive(Debug, Clone, Copy)]
pub struct MemoryBlock {
pub ptr: *mut u8,
pub size: usize,
pub alignment: usize,
}
unsafe impl Send for MemoryBlock {}
unsafe impl Sync for MemoryBlock {}
#[derive(Debug, Clone, Default)]
pub struct AllocationStats {
pub total_allocated: usize,
pub allocation_count: usize,
pub peak_usage: usize,
pub current_usage: usize,
}
impl AllocationStats {
pub fn new() -> Self {
Self::default()
}
pub fn record_allocation(&mut self, size: usize) {
self.total_allocated += size;
self.allocation_count += 1;
}
}
pub struct MemoryMappedView<T> {
mmap: memmap2::Mmap,
len: usize,
layout: MemoryLayout,
_phantom: PhantomData<T>,
}
impl<T> MemoryMappedView<T> {
pub fn from_file(file: std::fs::File, len: usize) -> Result<Self> {
let mmap = unsafe {
memmap2::Mmap::map(&file)
.map_err(|e| Error::InvalidOperation(format!("Memory mapping failed: {}", e)))?
};
let element_size = mem::size_of::<T>();
let required = len.checked_mul(element_size).ok_or_else(|| {
Error::InvalidInput(format!(
"Memory-mapped view size overflow: {len} x {element_size} bytes"
))
})?;
if required > mmap.len() {
return Err(Error::InvalidInput(format!(
"Memory-mapped view requests {required} bytes but the file maps only {} bytes",
mmap.len()
)));
}
if mmap.as_ptr() as usize % mem::align_of::<T>() != 0 {
return Err(Error::InvalidOperation(format!(
"Memory mapping is not aligned for {}",
std::any::type_name::<T>()
)));
}
let layout = MemoryLayout {
start_address: mmap.as_ptr() as usize,
element_size,
stride: element_size,
alignment: mem::align_of::<T>(),
cache_aligned: mmap.as_ptr() as usize % CACHE_LINE_SIZE == 0,
numa_node: None,
};
Ok(Self {
mmap,
len,
layout,
_phantom: PhantomData,
})
}
pub fn layout(&self) -> &MemoryLayout {
&self.layout
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn mapped_bytes(&self) -> usize {
self.mmap.len()
}
}
impl<T: ZeroCopyPod> MemoryMappedView<T> {
pub fn as_slice(&self) -> &[T] {
unsafe { slice::from_raw_parts(self.mmap.as_ptr() as *const T, self.len) }
}
}
impl<T: ZeroCopyPod> Deref for MemoryMappedView<T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
pub trait CacheAwareOps<T> {
fn linear_scan<F>(&self, predicate: F) -> Vec<usize>
where
F: Fn(&T) -> bool;
fn blocked_operation<U, F>(&self, other: &[U], block_size: usize, op: F) -> Vec<T>
where
F: Fn(&T, &U) -> T,
T: Clone,
U: Clone;
fn prefetch(&self, indices: &[usize]);
fn optimal_block_size(&self) -> usize;
}
impl<T> CacheAwareOps<T> for ZeroCopyView<T> {
fn linear_scan<F>(&self, predicate: F) -> Vec<usize>
where
F: Fn(&T) -> bool,
{
let mut results = Vec::new();
let slice = self.as_slice();
let block_size = self.optimal_block_size();
for (block_start, chunk) in slice.chunks(block_size).enumerate() {
for (i, item) in chunk.iter().enumerate() {
if predicate(item) {
results.push(block_start * block_size + i);
}
}
}
results
}
fn blocked_operation<U, F>(&self, other: &[U], block_size: usize, op: F) -> Vec<T>
where
F: Fn(&T, &U) -> T,
T: Clone,
U: Clone,
{
let slice = self.as_slice();
let len = slice.len().min(other.len());
let block_size = block_size.max(1);
let mut result = Vec::with_capacity(len);
let mut start = 0;
while start < len {
let end = (start + block_size).min(len);
for index in start..end {
result.push(op(&slice[index], &other[index]));
}
start = end;
}
result
}
fn prefetch(&self, indices: &[usize]) {
let slice = self.as_slice();
for &index in indices {
if index < slice.len() {
unsafe {
let ptr = slice.as_ptr().add(index);
#[cfg(target_arch = "x86_64")]
{
std::arch::x86_64::_mm_prefetch(
ptr as *const i8,
std::arch::x86_64::_MM_HINT_T0,
);
}
#[cfg(target_arch = "aarch64")]
{
std::arch::asm!(
"prfm pldl1keep, [{ptr}]",
ptr = in(reg) ptr,
options(nostack, preserves_flags)
);
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
{
let _ = ptr;
}
}
}
}
}
fn optimal_block_size(&self) -> usize {
let cache_size = 32 * 1024; let element_size = mem::size_of::<T>().max(1);
(cache_size / element_size).max(64)
}
}
pub struct ZeroCopyManager {
allocator: Mutex<CacheAwareAllocator>,
stats: Mutex<ZeroCopyStats>,
}
impl ZeroCopyManager {
pub fn new() -> Result<Self> {
Ok(Self {
allocator: Mutex::new(CacheAwareAllocator::new()?),
stats: Mutex::new(ZeroCopyStats::new()),
})
}
pub fn create_view<T: Copy>(&self, data: Vec<T>) -> Result<ZeroCopyView<T>> {
let len = data.len();
let size = len
.checked_mul(mem::size_of::<T>())
.ok_or_else(|| Error::InvalidInput("Zero-copy view size overflow".to_string()))?;
let mut allocator = self
.allocator
.lock()
.map_err(|_| Error::InvalidOperation("Failed to acquire allocator lock".to_string()))?;
let cache_level = allocator.cache_topology().optimal_cache_level(size);
let view = unsafe {
let view = allocator.allocate_uninit::<T>(len, cache_level)?;
std::ptr::copy_nonoverlapping(data.as_ptr(), view.data.as_ptr(), len);
view
};
drop(allocator);
self.stats
.lock()
.map_err(|_| Error::InvalidOperation("Failed to acquire stats lock".to_string()))?
.record_view_creation(size);
Ok(view)
}
pub fn create_mmap_view<T: ZeroCopyPod>(
&self,
file_path: &str,
len: usize,
) -> Result<MemoryMappedView<T>> {
let file = std::fs::File::open(file_path)
.map_err(|e| Error::InvalidOperation(format!("Failed to open file: {}", e)))?;
let view = MemoryMappedView::from_file(file, len)?;
self.stats
.lock()
.map_err(|_| Error::InvalidOperation("Failed to acquire stats lock".to_string()))?
.record_mmap_creation(len * mem::size_of::<T>());
Ok(view)
}
pub fn bytes_in_use(&self) -> Result<usize> {
self.allocator
.lock()
.map(|allocator| allocator.bytes_in_use())
.map_err(|_| Error::InvalidOperation("Failed to acquire allocator lock".to_string()))
}
pub fn stats(&self) -> Result<ZeroCopyStats> {
self.stats
.lock()
.map(|stats| stats.clone())
.map_err(|_| Error::InvalidOperation("Failed to acquire stats lock".to_string()))
}
}
#[derive(Debug, Clone, Default)]
pub struct ZeroCopyStats {
pub views_created: usize,
pub mmap_views_created: usize,
pub total_memory: usize,
}
impl ZeroCopyStats {
pub fn new() -> Self {
Self::default()
}
pub fn record_view_creation(&mut self, size: usize) {
self.views_created += 1;
self.total_memory += size;
}
pub fn record_mmap_creation(&mut self, size: usize) {
self.mmap_views_created += 1;
self.total_memory += size;
}
}
const _: () = {
const fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<StorageHandle>();
assert_send_sync::<MemoryPoolInner>();
assert_send_sync::<PoolAllocation>();
assert_send_sync::<MemoryPool>();
assert_send_sync::<ZeroCopyView<u64>>();
assert_send_sync::<MemoryMappedView<f64>>();
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_topology_detection() {
let topology = CacheTopology::detect().expect("operation should succeed");
assert!(topology.l1_cache_size > 0);
assert!(topology.l2_cache_size > 0);
assert!(topology.l3_cache_size > 0);
assert!(topology.cache_line_size > 0);
assert!(topology.cpu_cores > 0);
}
#[test]
fn test_memory_layout() {
let layout = MemoryLayout::new::<i64>().with_cache_alignment();
assert_eq!(layout.element_size, 8);
assert!(layout.cache_aligned);
assert!(layout.alignment >= CACHE_LINE_SIZE);
}
#[test]
fn test_zero_copy_manager() {
let manager = ZeroCopyManager::new().expect("operation should succeed");
let data = vec![1i32, 2, 3, 4, 5];
let view = manager.create_view(data).expect("operation should succeed");
assert_eq!(view.len(), 5);
assert_eq!(view.as_slice(), &[1, 2, 3, 4, 5]);
let stats = manager.stats().expect("operation should succeed");
assert_eq!(stats.views_created, 1);
}
#[test]
fn test_cache_aware_operations() {
let manager = ZeroCopyManager::new().expect("operation should succeed");
let data = (0..1000).collect::<Vec<i32>>();
let view = manager.create_view(data).expect("operation should succeed");
let evens = view.linear_scan(|&x| x % 2 == 0);
assert_eq!(evens.len(), 500);
let block_size = view.optimal_block_size();
assert!(block_size > 0);
}
#[test]
fn test_subview_creation() {
let manager = ZeroCopyManager::new().expect("operation should succeed");
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let view = manager.create_view(data).expect("operation should succeed");
let subview = view.subview(2..7).expect("operation should succeed");
assert_eq!(subview.len(), 5);
assert_eq!(subview.as_slice(), &[3, 4, 5, 6, 7]);
}
#[test]
fn test_subview_outlives_parent() {
let manager = ZeroCopyManager::new().expect("operation should succeed");
let view = manager
.create_view((0..64u64).collect::<Vec<_>>())
.expect("operation should succeed");
let subview = view.subview(8..16).expect("operation should succeed");
drop(view);
assert_eq!(subview.as_slice(), &[8, 9, 10, 11, 12, 13, 14, 15]);
}
#[test]
fn test_view_outlives_manager() {
let view = {
let manager = ZeroCopyManager::new().expect("operation should succeed");
let view = manager
.create_view(vec![7.5f64; 32])
.expect("operation should succeed");
drop(manager);
view
};
assert_eq!(view.len(), 32);
assert!(view.as_slice().iter().all(|value| *value == 7.5));
}
#[test]
fn test_empty_view() {
let manager = ZeroCopyManager::new().expect("operation should succeed");
let view = manager
.create_view(Vec::<u32>::new())
.expect("operation should succeed");
assert!(view.is_empty());
assert!(view.as_slice().is_empty());
}
#[test]
fn test_pool_reuses_released_blocks() {
let pool = MemoryPool::new(64 * 1024).expect("pool creation should succeed");
assert_eq!(pool.free_bytes(), pool.size());
for _ in 0..1000 {
let allocation = pool
.allocate_aligned(48 * 1024, CACHE_LINE_SIZE)
.expect("allocation should succeed");
assert_eq!(allocation.address() % CACHE_LINE_SIZE, 0);
}
assert_eq!(pool.free_bytes(), pool.size());
assert_eq!(pool.free_region_count(), 1);
}
#[test]
fn test_pool_rejects_zero_size() {
assert!(MemoryPool::new(0).is_err());
let pool = MemoryPool::new(4096).expect("pool creation should succeed");
assert!(pool.allocate_aligned(0, 64).is_err());
assert!(pool.allocate_aligned(64, 3).is_err());
}
#[test]
fn test_pool_aligns_block_start() {
let pool = MemoryPool::new(1024 * 1024).expect("pool creation should succeed");
let first = pool
.allocate_aligned(1, 8)
.expect("allocation should succeed");
let aligned = pool
.allocate_aligned(256, 4096)
.expect("allocation should succeed");
assert_eq!(aligned.address() % 4096, 0);
drop(first);
drop(aligned);
assert_eq!(pool.free_bytes(), pool.size());
}
#[test]
fn test_allocate_aligned_is_zeroed() {
let mut allocator = CacheAwareAllocator::new().expect("allocator creation should succeed");
let view: ZeroCopyView<u64> = allocator
.allocate_aligned(16, CacheLevel::L1)
.expect("allocation should succeed");
assert_eq!(view.as_slice(), &[0u64; 16]);
assert!(allocator.bytes_in_use() >= 16 * 8);
drop(view);
assert_eq!(allocator.bytes_in_use(), 0);
}
#[test]
fn test_blocked_operation_handles_zero_block_size() {
let manager = ZeroCopyManager::new().expect("operation should succeed");
let view = manager
.create_view((0..10i32).collect::<Vec<_>>())
.expect("operation should succeed");
let other = vec![2i32; 10];
let blocked = view.blocked_operation(&other, 0, |a, b| a * b);
assert_eq!(blocked, (0..10i32).map(|v| v * 2).collect::<Vec<_>>());
let blocked = view.blocked_operation(&other, 3, |a, b| a * b);
assert_eq!(blocked, (0..10i32).map(|v| v * 2).collect::<Vec<_>>());
}
#[test]
fn test_mmap_view_rejects_oversized_len() {
use std::io::Write;
let path = std::env::temp_dir().join("pandrs_zero_copy_mmap_len_check.bin");
{
let mut file = std::fs::File::create(&path).expect("file creation should succeed");
for value in 0..16u64 {
file.write_all(&value.to_le_bytes())
.expect("write should succeed");
}
file.flush().expect("flush should succeed");
}
let file = std::fs::File::open(&path).expect("file open should succeed");
let too_long = MemoryMappedView::<u64>::from_file(file, 17);
assert!(too_long.is_err(), "len beyond the mapping must be rejected");
let file = std::fs::File::open(&path).expect("file open should succeed");
let view = MemoryMappedView::<u64>::from_file(file, 16).expect("mapping should succeed");
assert_eq!(view.len(), 16);
assert_eq!(view.as_slice().len(), view.len());
assert_eq!(view.as_slice()[15], 15);
let _ = std::fs::remove_file(&path);
}
}