pub const DEFAULT_CLASSES: [usize; 3] = [2048, 16 * 1024, 64 * 1024];
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct PoolStats {
pub allocated: u64,
pub reused: u64,
pub returned: u64,
pub freed: u64,
pub cached: usize,
pub cached_bytes: usize,
}
#[derive(Debug)]
pub struct BufPool {
classes: [usize; 3],
free: [Vec<Vec<u8>>; 3],
max_cached: [usize; 3],
stats: PoolStats,
}
impl BufPool {
pub fn new(classes: [usize; 3], max_cached: usize) -> BufPool {
assert!(classes[0] > 0 && classes[0] < classes[1] && classes[1] < classes[2], "class sizes must ascend");
BufPool {
classes,
free: [Vec::new(), Vec::new(), Vec::new()],
max_cached: [max_cached, (max_cached / 4).max(1), (max_cached / 16).max(1)],
stats: PoolStats::default(),
}
}
pub fn classes(&self) -> [usize; 3] {
self.classes
}
#[inline]
pub fn get(&mut self, min: usize) -> Vec<u8> {
for (i, &size) in self.classes.iter().enumerate() {
if min <= size {
if let Some(b) = self.free[i].pop() {
self.stats.reused += 1;
self.stats.cached -= 1;
self.stats.cached_bytes -= size;
return b;
}
self.stats.allocated += 1;
return Vec::with_capacity(size);
}
}
self.stats.allocated += 1;
Vec::with_capacity(min)
}
#[inline(never)]
pub fn put(&mut self, mut buf: Vec<u8>) {
let cap = buf.capacity();
for (i, &size) in self.classes.iter().enumerate() {
if cap == size {
if self.free[i].len() < self.max_cached[i] {
buf.clear();
self.free[i].push(buf);
self.stats.returned += 1;
self.stats.cached += 1;
self.stats.cached_bytes += size;
} else {
self.stats.freed += 1;
}
return;
}
}
self.stats.freed += 1;
}
pub fn trim(&mut self, keep: usize) {
for i in 0..3 {
while self.free[i].len() > keep {
self.free[i].pop();
self.stats.cached -= 1;
self.stats.cached_bytes -= self.classes[i];
self.stats.freed += 1;
}
}
}
pub fn stats(&self) -> PoolStats {
self.stats
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reuses_buffers_by_class() {
let mut p = BufPool::new(DEFAULT_CLASSES, 4);
let a = p.get(100);
assert_eq!(a.capacity(), 2048);
let a_ptr = a.as_ptr();
p.put(a);
let mut b = p.get(2048);
assert_eq!(b.as_ptr(), a_ptr, "the same allocation comes back");
b.extend_from_slice(b"dirty");
p.put(b);
let c = p.get(1);
assert!(c.is_empty(), "returned buffers are cleared");
assert_eq!(p.get(3000).capacity(), 16 * 1024);
assert_eq!(p.get(70_000).capacity(), 70_000);
let s = p.stats();
assert_eq!((s.allocated, s.reused), (3, 2));
}
#[test]
fn caps_the_cache_and_frees_odd_sizes() {
let mut p = BufPool::new(DEFAULT_CLASSES, 2);
let bufs: Vec<Vec<u8>> = (0..5).map(|_| p.get(10)).collect();
for b in bufs {
p.put(b);
}
assert_eq!(p.stats().cached, 2);
assert_eq!(p.stats().freed, 3);
p.put(Vec::with_capacity(100));
assert_eq!(p.stats().freed, 4);
assert_eq!(p.stats().cached_bytes, 4096);
p.trim(0);
assert_eq!(p.stats().cached, 0);
}
}