#![allow(dead_code)]
pub const CACHE_LINE_BYTES: usize = 64;
pub struct CacheAlignedBuffer {
storage: Vec<u8>,
offset: usize,
len: usize,
}
impl CacheAlignedBuffer {
#[must_use]
pub fn new(len: usize) -> Self {
assert!(len > 0, "CacheAlignedBuffer: len must be > 0");
let alloc_size = len + CACHE_LINE_BYTES - 1;
let mut storage = vec![0u8; alloc_size];
let base_ptr = storage.as_ptr() as usize;
let offset = if base_ptr % CACHE_LINE_BYTES == 0 {
0
} else {
CACHE_LINE_BYTES - (base_ptr % CACHE_LINE_BYTES)
};
let _ = &mut storage[offset..offset + len];
Self {
storage,
offset,
len,
}
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.len
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
#[must_use]
pub fn as_slice(&self) -> &[u8] {
&self.storage[self.offset..self.offset + self.len]
}
#[inline]
#[must_use]
pub fn as_mut_slice(&mut self) -> &mut [u8] {
&mut self.storage[self.offset..self.offset + self.len]
}
#[inline]
#[must_use]
pub fn as_ptr(&self) -> *const u8 {
self.as_slice().as_ptr()
}
#[inline]
#[must_use]
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.as_mut_slice().as_mut_ptr()
}
#[inline]
pub fn fill(&mut self, value: u8) {
self.as_mut_slice().fill(value);
}
pub fn copy_from_slice(&mut self, src: &[u8]) -> Result<(), ()> {
if src.len() > self.len {
return Err(());
}
self.as_mut_slice()[..src.len()].copy_from_slice(src);
Ok(())
}
#[must_use]
pub fn alignment_offset(&self) -> usize {
self.offset
}
#[must_use]
pub fn storage_capacity(&self) -> usize {
self.storage.len()
}
}
impl std::fmt::Debug for CacheAlignedBuffer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CacheAlignedBuffer")
.field("len", &self.len)
.field("offset", &self.offset)
.field(
"aligned_ptr",
&format_args!("{:#x}", self.as_ptr() as usize),
)
.finish()
}
}
pub struct CacheAlignedPool {
buffers: Vec<CacheAlignedBuffer>,
buffer_size: usize,
peak_outstanding: usize,
outstanding: usize,
}
impl CacheAlignedPool {
#[must_use]
pub fn new(count: usize, buffer_size: usize) -> Self {
assert!(buffer_size > 0, "CacheAlignedPool: buffer_size must be > 0");
let buffers = (0..count)
.map(|_| CacheAlignedBuffer::new(buffer_size))
.collect();
Self {
buffers,
buffer_size,
peak_outstanding: 0,
outstanding: 0,
}
}
pub fn acquire(&mut self) -> Option<CacheAlignedBuffer> {
let buf = self.buffers.pop()?;
self.outstanding += 1;
if self.outstanding > self.peak_outstanding {
self.peak_outstanding = self.outstanding;
}
Some(buf)
}
pub fn release(&mut self, buf: CacheAlignedBuffer) {
if self.outstanding > 0 {
self.outstanding -= 1;
}
self.buffers.push(buf);
}
#[must_use]
pub fn available(&self) -> usize {
self.buffers.len()
}
#[must_use]
pub fn outstanding(&self) -> usize {
self.outstanding
}
#[must_use]
pub fn peak_outstanding(&self) -> usize {
self.peak_outstanding
}
#[must_use]
pub fn buffer_size(&self) -> usize {
self.buffer_size
}
pub fn grow(&mut self, additional: usize) {
for _ in 0..additional {
self.buffers.push(CacheAlignedBuffer::new(self.buffer_size));
}
}
#[must_use]
pub fn is_exhausted(&self) -> bool {
self.buffers.is_empty()
}
}
impl std::fmt::Debug for CacheAlignedPool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CacheAlignedPool")
.field("available", &self.available())
.field("outstanding", &self.outstanding)
.field("buffer_size", &self.buffer_size)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn buffer_is_cache_line_aligned() {
for size in [1_usize, 15, 64, 65, 128, 1024, 4096] {
let buf = CacheAlignedBuffer::new(size);
let ptr = buf.as_ptr() as usize;
assert_eq!(
ptr % CACHE_LINE_BYTES,
0,
"not aligned for size={size}: ptr={ptr:#x}"
);
}
}
#[test]
fn buffer_len_is_exact() {
let buf = CacheAlignedBuffer::new(256);
assert_eq!(buf.len(), 256);
}
#[test]
fn buffer_initialised_to_zero() {
let buf = CacheAlignedBuffer::new(128);
assert!(buf.as_slice().iter().all(|&b| b == 0));
}
#[test]
fn buffer_fill_and_read() {
let mut buf = CacheAlignedBuffer::new(64);
buf.fill(0xDE);
assert!(buf.as_slice().iter().all(|&b| b == 0xDE));
}
#[test]
fn buffer_copy_from_slice_ok() {
let mut buf = CacheAlignedBuffer::new(8);
let data = [1u8, 2, 3, 4, 5, 6, 7, 8];
buf.copy_from_slice(&data).expect("copy should succeed");
assert_eq!(buf.as_slice(), &data);
}
#[test]
fn buffer_copy_from_slice_overflow() {
let mut buf = CacheAlignedBuffer::new(4);
let data = [0u8; 8];
assert!(buf.copy_from_slice(&data).is_err());
}
#[test]
fn storage_capacity_gte_len() {
let buf = CacheAlignedBuffer::new(100);
assert!(buf.storage_capacity() >= buf.len());
}
#[test]
fn pool_acquire_release() {
let mut pool = CacheAlignedPool::new(4, 512);
assert_eq!(pool.available(), 4);
let b1 = pool.acquire().expect("available");
let b2 = pool.acquire().expect("available");
assert_eq!(pool.available(), 2);
assert_eq!(pool.outstanding(), 2);
pool.release(b1);
pool.release(b2);
assert_eq!(pool.available(), 4);
assert_eq!(pool.outstanding(), 0);
}
#[test]
fn pool_exhaustion() {
let mut pool = CacheAlignedPool::new(2, 64);
let _b1 = pool.acquire().expect("ok");
let _b2 = pool.acquire().expect("ok");
assert!(pool.is_exhausted());
assert!(pool.acquire().is_none());
}
#[test]
fn pool_grow() {
let mut pool = CacheAlignedPool::new(2, 64);
pool.grow(3);
assert_eq!(pool.available(), 5);
}
#[test]
fn pool_peak_outstanding() {
let mut pool = CacheAlignedPool::new(4, 64);
let b1 = pool.acquire().expect("ok");
let b2 = pool.acquire().expect("ok");
let b3 = pool.acquire().expect("ok");
assert_eq!(pool.peak_outstanding(), 3);
pool.release(b3);
pool.release(b2);
pool.release(b1);
assert_eq!(pool.peak_outstanding(), 3);
}
#[test]
fn pool_buffer_size() {
let pool = CacheAlignedPool::new(2, 2048);
assert_eq!(pool.buffer_size(), 2048);
}
#[test]
fn pool_acquired_buffers_aligned() {
let mut pool = CacheAlignedPool::new(4, 1024);
let mut bufs = Vec::new();
while let Some(b) = pool.acquire() {
bufs.push(b);
}
for buf in &bufs {
let ptr = buf.as_ptr() as usize;
assert_eq!(ptr % CACHE_LINE_BYTES, 0);
}
}
#[test]
fn buffer_mut_ptr_aligned() {
let mut buf = CacheAlignedBuffer::new(64);
let ptr = buf.as_mut_ptr() as usize;
assert_eq!(ptr % CACHE_LINE_BYTES, 0);
}
#[test]
fn buffer_not_empty() {
let buf = CacheAlignedBuffer::new(1);
assert!(!buf.is_empty());
}
#[test]
fn debug_impl() {
let buf = CacheAlignedBuffer::new(32);
let s = format!("{buf:?}");
assert!(s.contains("CacheAlignedBuffer"));
let pool = CacheAlignedPool::new(2, 32);
let ps = format!("{pool:?}");
assert!(ps.contains("CacheAlignedPool"));
}
}