use concurrent_queue::ConcurrentQueue;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
pub struct BytesPool {
pub(crate) queue: ConcurrentQueue<Vec<u8>>,
capacity: usize,
stats: PoolStats,
}
struct PoolStats {
allocations: AtomicUsize,
pool_hits: AtomicUsize,
}
impl BytesPool {
#[must_use]
pub fn new(capacity: usize, prealloc: usize) -> Self {
let queue = ConcurrentQueue::unbounded();
for _ in 0..prealloc {
queue.push(Vec::with_capacity(capacity)).ok();
}
Self {
queue,
capacity,
stats: PoolStats {
allocations: AtomicUsize::new(0),
pool_hits: AtomicUsize::new(0),
},
}
}
#[must_use]
pub fn acquire(self: &Arc<Self>) -> PooledBuffer {
if let Ok(mut buf) = self.queue.pop() {
self.stats.pool_hits.fetch_add(1, Ordering::Relaxed);
buf.clear();
PooledBuffer::new(buf, self.clone())
} else {
self.stats.allocations.fetch_add(1, Ordering::Relaxed);
PooledBuffer::new(Vec::with_capacity(self.capacity), self.clone())
}
}
#[must_use]
pub fn acquire_with_capacity(self: &Arc<Self>, capacity: usize) -> PooledBuffer {
if let Ok(mut buf) = self.queue.pop() {
self.stats.pool_hits.fetch_add(1, Ordering::Relaxed);
buf.clear();
if buf.capacity() < capacity {
self.queue.push(buf).ok();
self.stats.allocations.fetch_add(1, Ordering::Relaxed);
PooledBuffer::new(Vec::with_capacity(capacity), self.clone())
} else {
PooledBuffer::new(buf, self.clone())
}
} else {
self.stats.allocations.fetch_add(1, Ordering::Relaxed);
PooledBuffer::new(Vec::with_capacity(capacity), self.clone())
}
}
#[must_use]
pub fn stats(&self) -> PoolStatsSnapshot {
PoolStatsSnapshot {
allocations: self.stats.allocations.load(Ordering::Relaxed),
pool_hits: self.stats.pool_hits.load(Ordering::Relaxed),
}
}
#[must_use]
pub fn capacity(&self) -> usize {
self.capacity
}
}
#[derive(Debug, Clone, Copy)]
pub struct PoolStatsSnapshot {
pub allocations: usize,
pub pool_hits: usize,
}
impl PoolStatsSnapshot {
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn hit_ratio(&self) -> f64 {
let total = self.allocations + self.pool_hits;
if total == 0 {
return 1.0;
}
self.pool_hits as f64 / total as f64
}
}
pub struct PooledBuffer {
buffer: Vec<u8>,
pool: Arc<BytesPool>,
}
impl PooledBuffer {
fn new(buffer: Vec<u8>, pool: Arc<BytesPool>) -> Self {
Self { buffer, pool }
}
#[must_use]
pub fn owned(buffer: Vec<u8>) -> Self {
let capacity = buffer.capacity();
Self {
buffer,
pool: Arc::new(BytesPool::new(capacity, 0)),
}
}
#[must_use]
pub fn pool_stats(&self) -> PoolStatsSnapshot {
self.pool.stats()
}
}
impl std::ops::Deref for PooledBuffer {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.buffer
}
}
impl std::ops::DerefMut for PooledBuffer {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buffer
}
}
impl Drop for PooledBuffer {
fn drop(&mut self) {
self.buffer.clear();
self.pool.queue.push(std::mem::take(&mut self.buffer)).ok();
}
}
impl AsRef<[u8]> for PooledBuffer {
fn as_ref(&self) -> &[u8] {
&self.buffer
}
}
impl AsMut<[u8]> for PooledBuffer {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.buffer
}
}