use crossbeam_queue::ArrayQueue;
use std::sync::atomic::{AtomicUsize, Ordering};
pub(crate) const NUM_CLASSES: usize = 8;
const CLASS_SIZES: [usize; NUM_CLASSES] = [
4 * 1024, 16 * 1024, 64 * 1024, 256 * 1024, 1024 * 1024, 4 * 1024 * 1024, 16 * 1024 * 1024, 64 * 1024 * 1024, ];
const MIN_CLASS_BITS: u32 = 12;
const BITS_PER_STEP: u32 = 2;
#[derive(Debug)]
pub(crate) struct ClassTable {
classes: Box<[SizeClass]>,
}
impl ClassTable {
pub fn new(max_buffers_per_class: usize) -> Self {
let classes: Vec<SizeClass> = CLASS_SIZES
.iter()
.map(|&size| SizeClass::new(size, max_buffers_per_class))
.collect();
Self { classes: classes.into_boxed_slice() }
}
#[inline]
pub fn route(&self, size: usize) -> Option<(usize, &SizeClass)> {
let idx = route_size(size)?;
Some((idx, &self.classes[idx]))
}
#[inline]
pub fn route_capacity(&self, capacity: usize) -> Option<(usize, &SizeClass)> {
let idx = route_capacity(capacity)?;
Some((idx, &self.classes[idx]))
}
#[inline]
pub fn boundary(class_idx: usize) -> usize {
CLASS_SIZES[class_idx]
}
#[inline]
pub fn classes(&self) -> &[SizeClass] {
&self.classes
}
pub fn total_buffered(&self) -> usize {
self.classes.iter().map(SizeClass::len).sum()
}
pub fn all_empty(&self) -> bool {
self.classes.iter().all(SizeClass::is_empty)
}
pub fn clear_all(&self) {
for class in &*self.classes {
class.clear();
}
}
}
impl std::ops::Index<usize> for ClassTable {
type Output = SizeClass;
#[inline]
fn index(&self, idx: usize) -> &SizeClass {
&self.classes[idx]
}
}
#[inline]
fn route_size(size: usize) -> Option<usize> {
if size == 0 {
return Some(0);
}
let bits = usize::BITS - (size - 1).leading_zeros();
if bits <= MIN_CLASS_BITS {
return Some(0);
}
let class = (bits - MIN_CLASS_BITS).div_ceil(BITS_PER_STEP) as usize;
if class < NUM_CLASSES {
Some(class)
} else {
None
}
}
#[inline]
fn route_capacity(capacity: usize) -> Option<usize> {
if capacity < CLASS_SIZES[0] {
return None;
}
let bits = usize::BITS - capacity.leading_zeros();
let class = ((bits - 1 - MIN_CLASS_BITS) / BITS_PER_STEP) as usize;
Some(class.min(NUM_CLASSES - 1))
}
#[derive(Debug)]
pub(crate) struct SizeClass {
queue: ArrayQueue<Vec<u8>>,
pub class_size: usize,
count: AtomicUsize,
}
impl SizeClass {
pub fn new(class_size: usize, capacity: usize) -> Self {
Self {
queue: ArrayQueue::new(capacity),
class_size,
count: AtomicUsize::new(0),
}
}
#[inline(always)]
pub fn resize_zeroed(buf: &mut Vec<u8>, requested_len: usize) {
debug_assert!(
requested_len <= buf.capacity(),
"requested_len ({requested_len}) > capacity ({})",
buf.capacity(),
);
buf.resize(requested_len, 0);
}
#[inline(always)]
pub fn resize_uninit(buf: &mut Vec<u8>, requested_len: usize) {
debug_assert!(
requested_len <= buf.capacity(),
"requested_len ({requested_len}) > capacity ({})",
buf.capacity(),
);
unsafe { buf.set_len(requested_len) };
}
#[inline]
pub fn pop(&self) -> Option<Vec<u8>> {
self.queue.pop().inspect(|_| {
self.count.fetch_sub(1, Ordering::Relaxed);
})
}
#[inline]
pub fn push(&self, buf: Vec<u8>) -> Result<(), Vec<u8>> {
self.queue.push(buf).map(|()| {
self.count.fetch_add(1, Ordering::Relaxed);
})
}
#[inline]
pub fn len(&self) -> usize {
self.count.load(Ordering::Relaxed)
}
#[inline]
pub fn is_empty(&self) -> bool {
self.count.load(Ordering::Relaxed) == 0
}
pub fn clear(&self) {
while self.queue.pop().is_some() {}
self.count.store(0, Ordering::Relaxed);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn route_size_boundaries() {
assert_eq!(route_size(0), Some(0));
assert_eq!(route_size(1), Some(0));
for (i, &boundary) in CLASS_SIZES.iter().enumerate() {
assert_eq!(route_size(boundary), Some(i), "boundary {boundary}");
if i + 1 < NUM_CLASSES {
assert_eq!(route_size(boundary + 1), Some(i + 1), "boundary+1 {}", boundary + 1);
}
}
assert_eq!(route_size(CLASS_SIZES[NUM_CLASSES - 1] + 1), None);
assert_eq!(route_size(usize::MAX), None);
}
#[test]
fn route_capacity_boundaries() {
assert_eq!(route_capacity(0), None);
assert_eq!(route_capacity(CLASS_SIZES[0] - 1), None);
for (i, &boundary) in CLASS_SIZES.iter().enumerate() {
assert_eq!(route_capacity(boundary), Some(i), "boundary {boundary}");
}
assert_eq!(route_capacity(CLASS_SIZES[0] + 1), Some(0));
assert_eq!(route_capacity(CLASS_SIZES[1] - 1), Some(0));
assert_eq!(route_capacity(CLASS_SIZES[NUM_CLASSES - 1] + 1), Some(NUM_CLASSES - 1));
assert_eq!(route_capacity(usize::MAX), Some(NUM_CLASSES - 1));
}
#[test]
fn class_table_route_returns_correct_class() {
let table = ClassTable::new(32);
let (idx, class) = table.route(4096).unwrap();
assert_eq!(idx, 0);
assert_eq!(class.class_size, 4 * 1024);
let (idx, class) = table.route(65536).unwrap();
assert_eq!(idx, 2);
assert_eq!(class.class_size, 64 * 1024);
assert!(table.route(128 * 1024 * 1024).is_none());
}
}