use crate::error::{GraphError, Result};
use bumpalo::Bump;
use parking_lot::RwLock;
use std::alloc::Layout;
use std::ptr::NonNull;
use std::sync::Arc;
pub struct PoolAllocator {
small_pool: Arc<RwLock<Vec<NonNull<u8>>>>,
medium_pool: Arc<RwLock<Vec<NonNull<u8>>>>,
large_pool: Arc<RwLock<Vec<NonNull<u8>>>>,
small_layout: Layout,
medium_layout: Layout,
large_layout: Layout,
}
#[allow(unsafe_code)]
unsafe impl Send for PoolAllocator {}
#[allow(unsafe_code)]
unsafe impl Sync for PoolAllocator {}
impl PoolAllocator {
#[allow(clippy::arc_with_non_send_sync)]
pub fn new() -> Result<Self> {
let small_layout = Layout::from_size_align(64, 8)
.map_err(|e| GraphError::Memory(format!("Invalid small layout: {}", e)))?;
let medium_layout = Layout::from_size_align(512, 8)
.map_err(|e| GraphError::Memory(format!("Invalid medium layout: {}", e)))?;
let large_layout = Layout::from_size_align(4096, 8)
.map_err(|e| GraphError::Memory(format!("Invalid large layout: {}", e)))?;
let allocator = Self {
small_pool: Arc::new(RwLock::new(Vec::new())),
medium_pool: Arc::new(RwLock::new(Vec::new())),
large_pool: Arc::new(RwLock::new(Vec::new())),
small_layout,
medium_layout,
large_layout,
};
allocator.fill_pools()?;
Ok(allocator)
}
#[allow(unsafe_code)]
fn fill_pools(&self) -> Result<()> {
unsafe {
let mut small_pool = self.small_pool.write();
for _ in 0..1000 {
let ptr = std::alloc::alloc(self.small_layout);
if !ptr.is_null() {
small_pool.push(NonNull::new_unchecked(ptr));
}
}
let mut medium_pool = self.medium_pool.write();
for _ in 0..500 {
let ptr = std::alloc::alloc(self.medium_layout);
if !ptr.is_null() {
medium_pool.push(NonNull::new_unchecked(ptr));
}
}
let mut large_pool = self.large_pool.write();
for _ in 0..100 {
let ptr = std::alloc::alloc(self.large_layout);
if !ptr.is_null() {
large_pool.push(NonNull::new_unchecked(ptr));
}
}
}
Ok(())
}
pub fn allocate(&self, size: usize) -> Option<NonNull<u8>> {
if size <= 64 {
self.small_pool.write().pop()
} else if size <= 512 {
self.medium_pool.write().pop()
} else if size <= 4096 {
self.large_pool.write().pop()
} else {
None
}
}
pub fn deallocate(&self, ptr: NonNull<u8>, size: usize) {
if size <= 64 {
self.small_pool.write().push(ptr);
} else if size <= 512 {
self.medium_pool.write().push(ptr);
} else if size <= 4096 {
self.large_pool.write().push(ptr);
}
}
pub fn pool_stats(&self) -> PoolStats {
PoolStats {
small_available: self.small_pool.read().len(),
medium_available: self.medium_pool.read().len(),
large_available: self.large_pool.read().len(),
}
}
}
impl Drop for PoolAllocator {
#[allow(unsafe_code)]
fn drop(&mut self) {
unsafe {
for ptr in self.small_pool.write().drain(..) {
std::alloc::dealloc(ptr.as_ptr(), self.small_layout);
}
for ptr in self.medium_pool.write().drain(..) {
std::alloc::dealloc(ptr.as_ptr(), self.medium_layout);
}
for ptr in self.large_pool.write().drain(..) {
std::alloc::dealloc(ptr.as_ptr(), self.large_layout);
}
}
}
}
#[derive(Debug, Clone)]
pub struct PoolStats {
pub small_available: usize,
pub medium_available: usize,
pub large_available: usize,
}
pub struct GraphArenaAllocator {
current_arena: Arc<RwLock<Bump>>,
backup_arena: Arc<RwLock<Bump>>,
arena_size: usize,
}
impl GraphArenaAllocator {
#[allow(clippy::arc_with_non_send_sync)]
pub fn new(arena_size: usize) -> Self {
Self {
current_arena: Arc::new(RwLock::new(Bump::with_capacity(arena_size))),
backup_arena: Arc::new(RwLock::new(Bump::with_capacity(arena_size))),
arena_size,
}
}
#[allow(unsafe_code, clippy::mut_from_ref)]
pub fn alloc<T>(&self, value: T) -> &mut T {
let arena = self.current_arena.read();
let ptr = arena.alloc(value) as *mut T;
unsafe { &mut *ptr }
}
#[allow(unsafe_code, clippy::mut_from_ref)]
pub fn alloc_slice<T: Clone>(&self, slice: &[T]) -> &mut [T] {
let arena = self.current_arena.read();
let allocated = arena.alloc_slice_clone(slice);
let ptr = allocated.as_mut_ptr();
let len = allocated.len();
unsafe { std::slice::from_raw_parts_mut(ptr, len) }
}
pub fn reset_and_swap(&self) {
let mut current = self.current_arena.write();
let mut backup = self.backup_arena.write();
current.reset();
std::mem::swap(&mut *current, &mut *backup);
}
pub fn memory_usage(&self) -> usize {
let current = self.current_arena.read();
current.allocated_bytes()
}
pub fn needs_reset(&self) -> bool {
let current = self.current_arena.read();
current.allocated_bytes() > (self.arena_size * 3) / 4 }
pub fn scoped<F, R>(&self, f: F) -> R
where
F: FnOnce() -> R,
{
let result = f();
self.reset_and_swap();
result
}
}
pub struct TopologyAllocator {
pool: PoolAllocator,
temp_arena: GraphArenaAllocator,
}
impl TopologyAllocator {
pub fn new() -> Result<Self> {
Ok(Self {
pool: PoolAllocator::new()?,
temp_arena: GraphArenaAllocator::new(4 * 1024 * 1024), })
}
pub fn alloc_topology_element(&self, size: usize) -> Option<NonNull<u8>> {
self.pool.allocate(size)
}
pub fn dealloc_topology_element(&self, ptr: NonNull<u8>, size: usize) {
self.pool.deallocate(ptr, size);
}
pub fn alloc_temp<T>(&self, value: T) -> &mut T {
self.temp_arena.alloc(value)
}
pub fn reset_temp(&self) {
self.temp_arena.reset_and_swap();
}
pub fn stats(&self) -> (PoolStats, usize) {
(self.pool.pool_stats(), self.temp_arena.memory_usage())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pool_allocator() {
let allocator = PoolAllocator::new().unwrap();
let ptr = allocator.allocate(32);
assert!(ptr.is_some());
if let Some(ptr) = ptr {
allocator.deallocate(ptr, 32);
}
let stats = allocator.pool_stats();
assert!(stats.small_available > 0);
}
#[test]
fn test_arena_allocator() {
let allocator = GraphArenaAllocator::new(1024);
let value = allocator.alloc(42u64);
assert_eq!(*value, 42);
let initial_usage = allocator.memory_usage();
assert!(initial_usage > 0);
allocator.reset_and_swap();
let after_reset = allocator.memory_usage();
assert!(
after_reset == 0 || after_reset <= initial_usage,
"Expected after_reset ({}) to be 0 or <= initial_usage ({})",
after_reset,
initial_usage
);
}
#[test]
fn test_scoped_allocation() {
let allocator = GraphArenaAllocator::new(1024);
let result = allocator.scoped(|| {
let _val1 = allocator.alloc(1u32);
let _val2 = allocator.alloc(2u32);
42
});
assert_eq!(result, 42);
}
#[test]
fn test_topology_allocator() {
let allocator = TopologyAllocator::new().unwrap();
let ptr = allocator.alloc_topology_element(64);
assert!(ptr.is_some());
let temp = allocator.alloc_temp(123u32);
assert_eq!(*temp, 123);
let (_, before_reset) = allocator.stats();
allocator.reset_temp();
let (pool_stats, temp_usage) = allocator.stats();
assert!(pool_stats.small_available > 0);
assert!(
temp_usage <= before_reset,
"Expected temp_usage ({}) <= before_reset ({})",
temp_usage,
before_reset
);
}
}