mod concurrent;
#[cfg(test)]
mod tests;
pub use concurrent::{ConcurrentMemoryPool, ConcurrentPoolHandle};
use std::collections::HashSet;
use std::mem::MaybeUninit;
pub(crate) const DEFAULT_CHUNK_SIZE: usize = 1024;
pub struct MemoryPool<T> {
chunks: Vec<Box<[MaybeUninit<T>]>>,
free_indices: Vec<usize>,
free_lookup: HashSet<usize>,
initialized: HashSet<usize>,
chunk_size: usize,
total_allocated: usize,
}
impl<T> MemoryPool<T> {
#[must_use]
pub fn new(chunk_size: usize) -> Self {
Self {
chunks: Vec::new(),
free_indices: Vec::new(),
free_lookup: HashSet::new(),
initialized: HashSet::new(),
chunk_size: chunk_size.max(1),
total_allocated: 0,
}
}
#[must_use]
pub fn with_defaults() -> Self {
Self::new(DEFAULT_CHUNK_SIZE)
}
pub fn allocate(&mut self) -> PoolIndex {
if let Some(index) = self.free_indices.pop() {
self.free_lookup.remove(&index);
return PoolIndex(index);
}
self.grow();
let index = self.total_allocated - 1;
PoolIndex(index)
}
pub fn store(&mut self, index: PoolIndex, value: T) {
let (chunk_idx, slot_idx) = self.index_to_chunk_slot(index.0);
if self.initialized.contains(&index.0) {
unsafe {
std::ptr::drop_in_place(self.chunks[chunk_idx][slot_idx].as_mut_ptr());
}
}
self.chunks[chunk_idx][slot_idx].write(value);
self.initialized.insert(index.0);
}
#[must_use]
pub fn get(&self, index: PoolIndex) -> Option<&T> {
let (chunk_idx, slot_idx) = self.index_to_chunk_slot(index.0);
if chunk_idx < self.chunks.len() && self.initialized.contains(&index.0) {
Some(unsafe { self.chunks[chunk_idx][slot_idx].assume_init_ref() })
} else {
None
}
}
pub fn deallocate(&mut self, index: PoolIndex) {
let (chunk_idx, slot_idx) = self.index_to_chunk_slot(index.0);
if chunk_idx < self.chunks.len() {
if self.initialized.remove(&index.0) {
unsafe {
std::ptr::drop_in_place(self.chunks[chunk_idx][slot_idx].as_mut_ptr());
}
}
if self.free_lookup.insert(index.0) {
self.free_indices.push(index.0);
}
}
}
#[must_use]
pub fn allocated_count(&self) -> usize {
self.total_allocated.saturating_sub(self.free_indices.len())
}
#[must_use]
pub fn capacity(&self) -> usize {
self.total_allocated
}
pub fn allocate_batch(&mut self, count: usize) -> Vec<PoolIndex> {
if count == 0 {
return Vec::new();
}
let mut result = Vec::with_capacity(count);
let from_free = count.min(self.free_indices.len());
for _ in 0..from_free {
if let Some(idx) = self.free_indices.pop() {
self.free_lookup.remove(&idx);
result.push(PoolIndex(idx));
}
}
let remaining = count - from_free;
if remaining > 0 {
let chunks_needed = remaining.div_ceil(self.chunk_size);
for _ in 0..chunks_needed {
self.grow_for_batch();
}
for _ in 0..remaining {
if let Some(idx) = self.free_indices.pop() {
self.free_lookup.remove(&idx);
result.push(PoolIndex(idx));
}
}
}
result
}
#[inline]
pub fn prefetch(&self, index: PoolIndex) {
let (chunk_idx, slot_idx) = self.index_to_chunk_slot(index.0);
if chunk_idx < self.chunks.len() {
let ptr = self.chunks[chunk_idx][slot_idx].as_ptr();
#[cfg(target_arch = "x86_64")]
unsafe {
std::arch::x86_64::_mm_prefetch(ptr.cast::<i8>(), std::arch::x86_64::_MM_HINT_T0);
}
#[cfg(target_arch = "aarch64")]
{
crate::simd_neon_prefetch::prefetch_read_l1(ptr.cast::<u8>());
}
}
}
fn grow(&mut self) {
let mut chunk: Vec<MaybeUninit<T>> = Vec::with_capacity(self.chunk_size);
unsafe {
chunk.set_len(self.chunk_size);
}
self.chunks.push(chunk.into_boxed_slice());
self.total_allocated += self.chunk_size;
let start = self.total_allocated - self.chunk_size;
for i in start..(self.total_allocated - 1) {
self.free_indices.push(i);
self.free_lookup.insert(i);
}
}
fn grow_for_batch(&mut self) {
let mut chunk: Vec<MaybeUninit<T>> = Vec::with_capacity(self.chunk_size);
unsafe {
chunk.set_len(self.chunk_size);
}
self.chunks.push(chunk.into_boxed_slice());
self.total_allocated += self.chunk_size;
let start = self.total_allocated - self.chunk_size;
for i in start..self.total_allocated {
self.free_indices.push(i);
self.free_lookup.insert(i);
}
}
#[inline]
fn index_to_chunk_slot(&self, index: usize) -> (usize, usize) {
(index / self.chunk_size, index % self.chunk_size)
}
}
impl<T> Default for MemoryPool<T> {
fn default() -> Self {
Self::with_defaults()
}
}
impl<T> Drop for MemoryPool<T> {
fn drop(&mut self) {
for &idx in &self.initialized {
let (chunk_idx, slot_idx) = self.index_to_chunk_slot(idx);
if chunk_idx < self.chunks.len() {
unsafe {
std::ptr::drop_in_place(self.chunks[chunk_idx][slot_idx].as_mut_ptr());
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PoolIndex(usize);
impl PoolIndex {
#[must_use]
pub fn as_usize(self) -> usize {
self.0
}
}