use std::cell::{Cell, RefCell};
use std::ptr::NonNull;
const MIN_POOL_ALLOCATION_SIZE: usize = 64;
const MAX_POOL_ALLOCATION_SIZE: usize = 4096;
const DEFAULT_BLOCK_SIZE: usize = 16 * 1024;
thread_local! {
static THREAD_LOCAL_POOL: RefCell<Option<FastMemoryPool>> = const { RefCell::new(None) };
}
pub struct OptimizedMemoryPool {
pooling_enabled: Cell<bool>,
stats: PoolStatistics,
use_thread_local: bool,
}
struct FastMemoryPool {
current_pos: usize,
current_block: Vec<u8>,
free_blocks: Vec<Vec<u8>>,
block_size: usize,
}
#[derive(Default)]
struct PoolStatistics {
allocations: Cell<usize>,
pooled_allocations: Cell<usize>,
total_allocated: Cell<usize>,
avg_allocation_size: Cell<usize>,
}
impl OptimizedMemoryPool {
pub fn new() -> Self {
OptimizedMemoryPool {
pooling_enabled: Cell::new(true),
stats: PoolStatistics::default(),
use_thread_local: true,
}
}
pub fn with_config(use_thread_local: bool, initial_enabled: bool) -> Self {
OptimizedMemoryPool {
pooling_enabled: Cell::new(initial_enabled),
stats: PoolStatistics::default(),
use_thread_local,
}
}
pub fn allocate(&self, size: usize) -> Option<NonNull<u8>> {
self.stats.allocations.set(self.stats.allocations.get() + 1);
self.stats
.total_allocated
.set(self.stats.total_allocated.get() + size);
let total_allocs = self.stats.allocations.get();
let total_bytes = self.stats.total_allocated.get();
if total_allocs > 0 {
self.stats
.avg_allocation_size
.set(total_bytes / total_allocs);
}
if !self.should_use_pool(size) {
let layout = std::alloc::Layout::from_size_align(size, 1).ok()?;
let ptr = unsafe { std::alloc::alloc(layout) };
NonNull::new(ptr)
} else {
self.stats
.pooled_allocations
.set(self.stats.pooled_allocations.get() + 1);
if self.use_thread_local {
THREAD_LOCAL_POOL.with(|pool| {
let mut pool_ref = pool.borrow_mut();
if pool_ref.is_none() {
*pool_ref = Some(FastMemoryPool::new(DEFAULT_BLOCK_SIZE));
}
pool_ref.as_mut().unwrap().allocate(size)
})
} else {
let layout = std::alloc::Layout::from_size_align(size, 1).ok()?;
let ptr = unsafe { std::alloc::alloc(layout) };
NonNull::new(ptr)
}
}
}
pub fn allocate_str<'a>(&self, s: &str) -> Option<&'a str> {
if s.is_empty() {
return Some("");
}
let bytes = s.as_bytes();
let size = bytes.len();
self.stats.allocations.set(self.stats.allocations.get() + 1);
self.stats
.total_allocated
.set(self.stats.total_allocated.get() + size);
if size < MIN_POOL_ALLOCATION_SIZE {
let boxed = s.to_string().into_boxed_str();
let leaked = Box::leak(boxed);
Some(leaked)
} else {
self.stats.allocations.set(self.stats.allocations.get() - 1);
self.stats
.total_allocated
.set(self.stats.total_allocated.get() - size);
let ptr = self.allocate(size)?;
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.as_ptr(), size);
Some(std::str::from_utf8_unchecked(std::slice::from_raw_parts(
ptr.as_ptr(),
size,
)))
}
}
}
fn should_use_pool(&self, size: usize) -> bool {
if !self.pooling_enabled.get() {
return false;
}
(MIN_POOL_ALLOCATION_SIZE..=MAX_POOL_ALLOCATION_SIZE).contains(&size)
}
pub fn set_pooling_enabled(&self, enabled: bool) {
self.pooling_enabled.set(enabled);
}
pub fn stats(&self) -> PoolStats {
PoolStats {
total_allocations: self.stats.allocations.get(),
pooled_allocations: self.stats.pooled_allocations.get(),
total_bytes: self.stats.total_allocated.get(),
avg_allocation_size: self.stats.avg_allocation_size.get(),
pooling_enabled: self.pooling_enabled.get(),
}
}
pub fn reset(&self) {
if self.use_thread_local {
THREAD_LOCAL_POOL.with(|pool| {
if let Some(ref mut p) = *pool.borrow_mut() {
p.reset();
}
});
}
}
}
impl FastMemoryPool {
fn new(block_size: usize) -> Self {
FastMemoryPool {
current_pos: 0,
current_block: Vec::with_capacity(block_size),
free_blocks: Vec::new(),
block_size,
}
}
fn allocate(&mut self, size: usize) -> Option<NonNull<u8>> {
let align = if size >= 8 { 8 } else { 1 };
let aligned_size = (size + align - 1) & !(align - 1);
if self.current_pos + aligned_size > self.current_block.capacity() {
self.allocate_new_block();
}
if self.current_pos + aligned_size <= self.current_block.capacity() {
let ptr = unsafe { self.current_block.as_mut_ptr().add(self.current_pos) };
self.current_pos += aligned_size;
NonNull::new(ptr)
} else {
None
}
}
fn allocate_new_block(&mut self) {
if self.current_pos > 0 {
let mut old_block =
std::mem::replace(&mut self.current_block, Vec::with_capacity(self.block_size));
unsafe {
old_block.set_len(self.current_pos);
}
self.free_blocks.push(old_block);
}
if let Some(mut block) = self.free_blocks.pop() {
block.clear();
self.current_block = block;
} else {
self.current_block = Vec::with_capacity(self.block_size);
}
self.current_pos = 0;
}
fn reset(&mut self) {
self.current_pos = 0;
}
}
#[derive(Debug, Clone)]
pub struct PoolStats {
pub total_allocations: usize,
pub pooled_allocations: usize,
pub total_bytes: usize,
pub avg_allocation_size: usize,
pub pooling_enabled: bool,
}
pub struct ScopedOptimizedPool<'a> {
pool: &'a OptimizedMemoryPool,
}
impl<'a> ScopedOptimizedPool<'a> {
pub fn new(pool: &'a OptimizedMemoryPool) -> Self {
ScopedOptimizedPool { pool }
}
pub fn allocate_str(&self, s: &str) -> Option<&'a str> {
self.pool.allocate_str(s)
}
}
impl<'a> Drop for ScopedOptimizedPool<'a> {
fn drop(&mut self) {
self.pool.reset();
}
}
impl Default for OptimizedMemoryPool {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_small_allocation_bypass() {
let pool = OptimizedMemoryPool::new();
let small = "hi";
let allocated = pool.allocate_str(small).unwrap();
assert_eq!(allocated, small);
let stats = pool.stats();
assert_eq!(stats.pooled_allocations, 0); }
#[test]
fn test_medium_allocation_pooled() {
let pool = OptimizedMemoryPool::new();
let medium = "a".repeat(100);
let allocated = pool.allocate_str(&medium).unwrap();
assert_eq!(allocated, medium);
let stats = pool.stats();
assert_eq!(stats.pooled_allocations, 1); }
#[test]
fn test_adaptive_pooling() {
let pool = OptimizedMemoryPool::new();
for i in 0..100 {
let s = format!("{i}");
pool.allocate_str(&s);
}
let stats = pool.stats();
assert!(stats.avg_allocation_size < MIN_POOL_ALLOCATION_SIZE);
pool.set_pooling_enabled(false);
let large = "x".repeat(200);
pool.allocate_str(&large);
let new_stats = pool.stats();
assert_eq!(new_stats.pooled_allocations, stats.pooled_allocations);
}
#[test]
fn test_scoped_pool() {
let pool = OptimizedMemoryPool::new();
{
let scoped = ScopedOptimizedPool::new(&pool);
let s = "test string";
let allocated = scoped.allocate_str(s).unwrap();
assert_eq!(allocated, s);
}
pool.reset();
let stats = pool.stats();
assert!(stats.total_allocations > 0); }
}