use crate::error::{GraphError, Result};
use parking_lot::RwLock;
use std::collections::VecDeque;
use std::ptr::NonNull;
use std::sync::Arc;
pub struct FixedSizePool {
free_blocks: Arc<RwLock<VecDeque<NonNull<u8>>>>,
block_size: usize,
alignment: usize,
total_blocks: Arc<RwLock<usize>>,
max_blocks: usize,
}
#[allow(unsafe_code)]
unsafe impl Send for FixedSizePool {}
#[allow(unsafe_code)]
unsafe impl Sync for FixedSizePool {}
impl FixedSizePool {
#[allow(clippy::arc_with_non_send_sync)]
pub fn new(
block_size: usize,
alignment: usize,
initial_blocks: usize,
max_blocks: usize,
) -> Result<Self> {
if block_size == 0 || alignment == 0 || !alignment.is_power_of_two() {
return Err(GraphError::Memory("Invalid pool parameters".to_string()));
}
let pool = Self {
free_blocks: Arc::new(RwLock::new(VecDeque::new())),
block_size,
alignment,
total_blocks: Arc::new(RwLock::new(0)),
max_blocks,
};
pool.expand_pool(initial_blocks)?;
Ok(pool)
}
pub fn allocate(&self) -> Result<NonNull<u8>> {
{
let mut free_blocks = self.free_blocks.write();
if let Some(block) = free_blocks.pop_front() {
return Ok(block);
}
}
let total_blocks = *self.total_blocks.read();
if total_blocks < self.max_blocks {
self.expand_pool(std::cmp::min(64, self.max_blocks - total_blocks))?;
let mut free_blocks = self.free_blocks.write();
if let Some(block) = free_blocks.pop_front() {
return Ok(block);
}
}
Err(GraphError::Memory("Pool exhausted".to_string()))
}
#[allow(unsafe_code)]
pub fn deallocate(&self, ptr: NonNull<u8>) {
unsafe {
std::ptr::write_bytes(ptr.as_ptr(), 0, self.block_size);
}
let mut free_blocks = self.free_blocks.write();
free_blocks.push_back(ptr);
}
#[allow(unsafe_code)]
fn expand_pool(&self, count: usize) -> Result<()> {
let layout = std::alloc::Layout::from_size_align(self.block_size, self.alignment)
.map_err(|e| GraphError::Memory(format!("Invalid layout: {}", e)))?;
let mut free_blocks = self.free_blocks.write();
let mut total_blocks = self.total_blocks.write();
for _ in 0..count {
if *total_blocks >= self.max_blocks {
break;
}
unsafe {
let ptr = std::alloc::alloc(layout);
if ptr.is_null() {
return Err(GraphError::Memory("Failed to allocate memory".to_string()));
}
free_blocks.push_back(NonNull::new_unchecked(ptr));
*total_blocks += 1;
}
}
Ok(())
}
pub fn stats(&self) -> PoolStats {
PoolStats {
block_size: self.block_size,
total_blocks: *self.total_blocks.read(),
free_blocks: self.free_blocks.read().len(),
allocated_blocks: *self.total_blocks.read() - self.free_blocks.read().len(),
max_blocks: self.max_blocks,
}
}
#[allow(unsafe_code)]
pub fn shrink(&self, target_free: usize) -> Result<usize> {
let layout = std::alloc::Layout::from_size_align(self.block_size, self.alignment)
.map_err(|e| GraphError::Memory(format!("Invalid layout: {}", e)))?;
let mut free_blocks = self.free_blocks.write();
let mut total_blocks = self.total_blocks.write();
let mut deallocated = 0;
while free_blocks.len() > target_free && !free_blocks.is_empty() {
if let Some(ptr) = free_blocks.pop_back() {
unsafe {
std::alloc::dealloc(ptr.as_ptr(), layout);
}
*total_blocks -= 1;
deallocated += 1;
}
}
Ok(deallocated)
}
}
impl Drop for FixedSizePool {
#[allow(unsafe_code)]
fn drop(&mut self) {
let layout = std::alloc::Layout::from_size_align(self.block_size, self.alignment).unwrap();
let mut free_blocks = self.free_blocks.write();
unsafe {
while let Some(ptr) = free_blocks.pop_front() {
std::alloc::dealloc(ptr.as_ptr(), layout);
}
}
}
}
#[derive(Debug, Clone)]
pub struct PoolStats {
pub block_size: usize,
pub total_blocks: usize,
pub free_blocks: usize,
pub allocated_blocks: usize,
pub max_blocks: usize,
}
impl PoolStats {
pub fn utilization(&self) -> f64 {
if self.total_blocks == 0 {
0.0
} else {
self.allocated_blocks as f64 / self.total_blocks as f64
}
}
pub fn is_nearly_full(&self) -> bool {
self.utilization() > 0.9
}
pub fn has_excess_free(&self) -> bool {
self.free_blocks > self.allocated_blocks && self.free_blocks > 100
}
}
pub struct MultiSizePool {
pools: Vec<FixedSizePool>,
size_classes: Vec<usize>,
}
impl MultiSizePool {
pub fn new() -> Result<Self> {
let size_classes = vec![32, 64, 128, 256, 512, 1024, 2048, 4096];
let mut pools = Vec::new();
for &size in &size_classes {
let initial_blocks = match size {
32..=128 => 1000, 129..=512 => 500, 513..=2048 => 200, _ => 50, };
let max_blocks = initial_blocks * 10; let alignment = if size >= 8 { 8 } else { size };
pools.push(FixedSizePool::new(
size,
alignment,
initial_blocks,
max_blocks,
)?);
}
Ok(Self {
pools,
size_classes,
})
}
fn find_pool_index(&self, size: usize) -> Option<usize> {
self.size_classes
.iter()
.position(|&class_size| class_size >= size)
}
pub fn allocate(&self, size: usize) -> Result<(NonNull<u8>, usize)> {
if let Some(pool_index) = self.find_pool_index(size) {
let ptr = self.pools[pool_index].allocate()?;
let actual_size = self.size_classes[pool_index];
Ok((ptr, actual_size))
} else {
Err(GraphError::Memory(format!(
"Size {} too large for pools",
size
)))
}
}
pub fn deallocate(&self, ptr: NonNull<u8>, size: usize) -> Result<()> {
if let Some(pool_index) = self.find_pool_index(size) {
self.pools[pool_index].deallocate(ptr);
Ok(())
} else {
Err(GraphError::Memory(format!(
"Invalid size {} for deallocation",
size
)))
}
}
pub fn all_stats(&self) -> Vec<PoolStats> {
self.pools.iter().map(|pool| pool.stats()).collect()
}
pub fn maintain(&self) -> Result<usize> {
let mut total_freed = 0;
for pool in &self.pools {
let stats = pool.stats();
if stats.has_excess_free() {
let target_free = std::cmp::max(10, stats.allocated_blocks / 2);
total_freed += pool.shrink(target_free)?;
}
}
Ok(total_freed)
}
}
pub struct NodePool {
small_pool: FixedSizePool,
medium_pool: FixedSizePool,
large_pool: FixedSizePool,
fallback: MultiSizePool,
}
impl NodePool {
pub fn new() -> Result<Self> {
Ok(Self {
small_pool: FixedSizePool::new(256, 8, 2000, 20000)?, medium_pool: FixedSizePool::new(1024, 8, 1000, 10000)?, large_pool: FixedSizePool::new(4096, 8, 200, 2000)?, fallback: MultiSizePool::new()?,
})
}
pub fn allocate_node(&self, data_size: usize) -> Result<(NonNull<u8>, usize)> {
if data_size <= 256 {
let ptr = self.small_pool.allocate()?;
Ok((ptr, 256))
} else if data_size <= 1024 {
let ptr = self.medium_pool.allocate()?;
Ok((ptr, 1024))
} else if data_size <= 4096 {
let ptr = self.large_pool.allocate()?;
Ok((ptr, 4096))
} else {
self.fallback.allocate(data_size)
}
}
pub fn deallocate_node(&self, ptr: NonNull<u8>, allocated_size: usize) -> Result<()> {
match allocated_size {
256 => {
self.small_pool.deallocate(ptr);
Ok(())
}
1024 => {
self.medium_pool.deallocate(ptr);
Ok(())
}
4096 => {
self.large_pool.deallocate(ptr);
Ok(())
}
_ => self.fallback.deallocate(ptr, allocated_size),
}
}
pub fn stats(&self) -> NodePoolStats {
NodePoolStats {
small: self.small_pool.stats(),
medium: self.medium_pool.stats(),
large: self.large_pool.stats(),
fallback: self.fallback.all_stats(),
}
}
}
#[derive(Debug)]
pub struct NodePoolStats {
pub small: PoolStats,
pub medium: PoolStats,
pub large: PoolStats,
pub fallback: Vec<PoolStats>,
}
impl NodePoolStats {
pub fn total_memory_bytes(&self) -> usize {
let main_memory = self.small.total_blocks * self.small.block_size
+ self.medium.total_blocks * self.medium.block_size
+ self.large.total_blocks * self.large.block_size;
let fallback_memory: usize = self
.fallback
.iter()
.map(|stats| stats.total_blocks * stats.block_size)
.sum();
main_memory + fallback_memory
}
pub fn overall_utilization(&self) -> f64 {
let total_blocks =
self.small.total_blocks + self.medium.total_blocks + self.large.total_blocks;
let allocated_blocks = self.small.allocated_blocks
+ self.medium.allocated_blocks
+ self.large.allocated_blocks;
if total_blocks == 0 {
0.0
} else {
allocated_blocks as f64 / total_blocks as f64
}
}
}
pub struct RelationshipPool {
standard_pool: FixedSizePool,
extended_pool: FixedSizePool,
}
impl RelationshipPool {
pub fn new() -> Result<Self> {
Ok(Self {
standard_pool: FixedSizePool::new(128, 8, 4000, 40000)?, extended_pool: FixedSizePool::new(512, 8, 1000, 10000)?, })
}
pub fn allocate_relationship(&self, has_properties: bool) -> Result<(NonNull<u8>, usize)> {
if has_properties {
let ptr = self.extended_pool.allocate()?;
Ok((ptr, 512))
} else {
let ptr = self.standard_pool.allocate()?;
Ok((ptr, 128))
}
}
pub fn deallocate_relationship(&self, ptr: NonNull<u8>, allocated_size: usize) {
match allocated_size {
128 => self.standard_pool.deallocate(ptr),
512 => self.extended_pool.deallocate(ptr),
_ => panic!("Invalid relationship size: {}", allocated_size),
}
}
pub fn stats(&self) -> (PoolStats, PoolStats) {
(self.standard_pool.stats(), self.extended_pool.stats())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fixed_size_pool() {
let pool = FixedSizePool::new(64, 8, 10, 100).unwrap();
let ptr1 = pool.allocate().unwrap();
let ptr2 = pool.allocate().unwrap();
let stats = pool.stats();
assert_eq!(stats.allocated_blocks, 2);
assert_eq!(stats.free_blocks, 8);
pool.deallocate(ptr1);
pool.deallocate(ptr2);
let stats = pool.stats();
assert_eq!(stats.allocated_blocks, 0);
assert_eq!(stats.free_blocks, 10);
}
#[test]
fn test_multi_size_pool() {
let pool = MultiSizePool::new().unwrap();
let (ptr1, size1) = pool.allocate(30).unwrap();
let (ptr2, size2) = pool.allocate(100).unwrap();
let (ptr3, size3) = pool.allocate(500).unwrap();
assert_eq!(size1, 32); assert_eq!(size2, 128);
assert_eq!(size3, 512);
pool.deallocate(ptr1, size1).unwrap();
pool.deallocate(ptr2, size2).unwrap();
pool.deallocate(ptr3, size3).unwrap();
}
#[test]
fn test_node_pool() {
let pool = NodePool::new().unwrap();
let (ptr1, size1) = pool.allocate_node(100).unwrap();
let (ptr2, size2) = pool.allocate_node(800).unwrap();
let (ptr3, size3) = pool.allocate_node(3000).unwrap();
assert_eq!(size1, 256);
assert_eq!(size2, 1024);
assert_eq!(size3, 4096);
pool.deallocate_node(ptr1, size1).unwrap();
pool.deallocate_node(ptr2, size2).unwrap();
pool.deallocate_node(ptr3, size3).unwrap();
let stats = pool.stats();
assert!(stats.overall_utilization() == 0.0);
}
#[test]
fn test_relationship_pool() {
let pool = RelationshipPool::new().unwrap();
let (ptr1, size1) = pool.allocate_relationship(false).unwrap();
let (ptr2, size2) = pool.allocate_relationship(true).unwrap();
assert_eq!(size1, 128); assert_eq!(size2, 512);
pool.deallocate_relationship(ptr1, size1);
pool.deallocate_relationship(ptr2, size2);
let (standard_stats, extended_stats) = pool.stats();
assert_eq!(standard_stats.allocated_blocks, 0);
assert_eq!(extended_stats.allocated_blocks, 0);
}
#[test]
fn test_pool_maintenance() {
let pool = MultiSizePool::new().unwrap();
let mut ptrs = Vec::new();
for _ in 0..100 {
let (ptr, size) = pool.allocate(64).unwrap();
ptrs.push((ptr, size));
}
for (ptr, size) in ptrs {
pool.deallocate(ptr, size).unwrap();
}
let freed = pool.maintain().unwrap();
assert!(freed > 0);
}
}