#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BufferSize {
Small4K,
Medium64K,
Large1M,
Huge16M,
}
impl BufferSize {
#[must_use]
pub fn bytes(self) -> usize {
match self {
Self::Small4K => 4 * 1024,
Self::Medium64K => 64 * 1024,
Self::Large1M => 1024 * 1024,
Self::Huge16M => 16 * 1024 * 1024,
}
}
}
impl std::fmt::Display for BufferSize {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Small4K => write!(f, "4K"),
Self::Medium64K => write!(f, "64K"),
Self::Large1M => write!(f, "1M"),
Self::Huge16M => write!(f, "16M"),
}
}
}
pub struct PooledBuffer {
pub data: Vec<u8>,
pub size_class: BufferSize,
pub allocated_at_idx: usize,
}
impl PooledBuffer {
fn new(size_class: BufferSize) -> Self {
Self {
data: vec![0u8; size_class.bytes()],
size_class,
allocated_at_idx: 0,
}
}
#[must_use]
pub fn capacity(&self) -> usize {
self.data.capacity()
}
#[must_use]
pub fn as_slice(&self) -> &[u8] {
&self.data
}
pub fn as_mut_slice(&mut self) -> &mut [u8] {
&mut self.data
}
}
#[derive(Debug, Clone, Default)]
pub struct PoolStats {
pub total_acquired: u64,
pub pool_hits: u64,
pub allocations: u64,
pub hit_rate: f32,
}
impl PoolStats {
#[allow(clippy::cast_precision_loss)]
fn update_hit_rate(&mut self) {
if self.total_acquired == 0 {
self.hit_rate = 0.0;
} else {
self.hit_rate = self.pool_hits as f32 / self.total_acquired as f32;
}
}
}
pub struct BufferPool {
pub free_small: Vec<Vec<u8>>,
pub free_medium: Vec<Vec<u8>>,
pub free_large: Vec<Vec<u8>>,
pub max_pool_size: usize,
stats: PoolStats,
}
impl BufferPool {
#[must_use]
pub fn new(max_pool_size: usize) -> Self {
Self {
free_small: Vec::new(),
free_medium: Vec::new(),
free_large: Vec::new(),
max_pool_size,
stats: PoolStats::default(),
}
}
pub fn acquire(&mut self, size: BufferSize) -> Vec<u8> {
self.stats.total_acquired += 1;
let pool = self.pool_for_size(size);
if let Some(mut buf) = pool.pop() {
let cap = size.bytes();
buf.resize(cap, 0);
self.stats.pool_hits += 1;
self.stats.update_hit_rate();
buf
} else {
self.stats.allocations += 1;
self.stats.update_hit_rate();
vec![0u8; size.bytes()]
}
}
pub fn release(&mut self, buf: Vec<u8>, size: BufferSize) {
if size == BufferSize::Huge16M {
return;
}
let max = self.max_pool_size;
let pool = self.pool_for_size(size);
if pool.len() < max {
pool.push(buf);
}
}
#[must_use]
pub fn stats(&self) -> &PoolStats {
&self.stats
}
#[must_use]
pub fn pooled_count(&self) -> usize {
self.free_small.len() + self.free_medium.len() + self.free_large.len()
}
fn pool_for_size(&mut self, size: BufferSize) -> &mut Vec<Vec<u8>> {
match size {
BufferSize::Small4K => &mut self.free_small,
BufferSize::Medium64K => &mut self.free_medium,
BufferSize::Large1M | BufferSize::Huge16M => &mut self.free_large,
}
}
}
impl Default for BufferPool {
fn default() -> Self {
Self::new(16)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_buffer_size_bytes() {
assert_eq!(BufferSize::Small4K.bytes(), 4 * 1024);
assert_eq!(BufferSize::Medium64K.bytes(), 64 * 1024);
assert_eq!(BufferSize::Large1M.bytes(), 1024 * 1024);
assert_eq!(BufferSize::Huge16M.bytes(), 16 * 1024 * 1024);
}
#[test]
fn test_buffer_size_display() {
assert_eq!(BufferSize::Small4K.to_string(), "4K");
assert_eq!(BufferSize::Medium64K.to_string(), "64K");
assert_eq!(BufferSize::Large1M.to_string(), "1M");
assert_eq!(BufferSize::Huge16M.to_string(), "16M");
}
#[test]
fn test_pooled_buffer_capacity() {
let buf = PooledBuffer::new(BufferSize::Medium64K);
assert_eq!(buf.capacity(), BufferSize::Medium64K.bytes());
}
#[test]
fn test_pooled_buffer_slice_mut() {
let mut buf = PooledBuffer::new(BufferSize::Small4K);
buf.as_mut_slice()[0] = 42;
assert_eq!(buf.as_slice()[0], 42);
}
#[test]
fn test_pool_acquire_fresh_allocation() {
let mut pool = BufferPool::new(4);
let buf = pool.acquire(BufferSize::Small4K);
assert_eq!(buf.len(), BufferSize::Small4K.bytes());
assert_eq!(pool.stats().allocations, 1);
assert_eq!(pool.stats().pool_hits, 0);
}
#[test]
fn test_pool_release_and_reuse() {
let mut pool = BufferPool::new(4);
let buf = pool.acquire(BufferSize::Medium64K);
pool.release(buf, BufferSize::Medium64K);
assert_eq!(pool.pooled_count(), 1);
let _buf2 = pool.acquire(BufferSize::Medium64K);
assert_eq!(pool.stats().pool_hits, 1);
assert_eq!(pool.pooled_count(), 0);
}
#[test]
fn test_pool_huge_never_pooled() {
let mut pool = BufferPool::new(4);
let buf = pool.acquire(BufferSize::Huge16M);
pool.release(buf, BufferSize::Huge16M);
assert_eq!(pool.free_large.len(), 0);
}
#[test]
fn test_pool_max_size_enforced() {
let mut pool = BufferPool::new(2);
for _ in 0..5 {
let buf = pool.acquire(BufferSize::Small4K);
pool.release(buf, BufferSize::Small4K);
}
assert!(pool.free_small.len() <= 2);
}
#[test]
fn test_pool_stats_hit_rate() {
let mut pool = BufferPool::new(4);
let b1 = pool.acquire(BufferSize::Small4K); pool.release(b1, BufferSize::Small4K);
let _b2 = pool.acquire(BufferSize::Small4K); let stats = pool.stats();
assert_eq!(stats.total_acquired, 2);
assert_eq!(stats.pool_hits, 1);
assert!((stats.hit_rate - 0.5).abs() < f32::EPSILON);
}
#[test]
fn test_pool_default_construction() {
let pool = BufferPool::default();
assert_eq!(pool.max_pool_size, 16);
assert_eq!(pool.pooled_count(), 0);
}
#[test]
fn test_pool_acquire_zeroes_buffer() {
let mut pool = BufferPool::new(4);
let mut buf = pool.acquire(BufferSize::Small4K);
buf[0] = 0xFF;
pool.release(buf, BufferSize::Small4K);
let buf2 = pool.acquire(BufferSize::Small4K);
assert_eq!(buf2.len(), BufferSize::Small4K.bytes());
}
#[test]
fn test_pool_multiple_size_classes_independent() {
let mut pool = BufferPool::new(4);
let s = pool.acquire(BufferSize::Small4K);
let m = pool.acquire(BufferSize::Medium64K);
pool.release(s, BufferSize::Small4K);
pool.release(m, BufferSize::Medium64K);
assert_eq!(pool.free_small.len(), 1);
assert_eq!(pool.free_medium.len(), 1);
}
}