//! `IoBuf` overhead compared to `Bytes`. //! //! This module compares `IoBuf`, `Bytes`, and `Vec` when decoding a //! fixed-size value. Inputs are prebuilt, then cloned or wrapped inside the //! measurement loop. //! //! `FixedBytes::decode` reads into a fixed array through //! `Buf::try_copy_to_slice`, so one call exercises each buffer's bounds //! check, byte copy, and cursor update. //! //! The `iobuf_bytes` modes decode an `IoBuf` that holds external `Bytes`. The //! `iobuf_native` modes decode one backed by a heap allocation from //! `IoBufMut::with_capacity`, so the pair isolates the owner kind while //! sharing the same handle hot path. //! //! The `Vec` modes are included as a deep-clone baseline. Comparing them //! with `Bytes` and `IoBuf` shows where shared backing storage and atomic //! reference counts pay off relative to simply cloning the bytes. The bare //! `slice` mode decodes from a borrowed slice of the prebuilt payload without //! cloning, giving the no-copy decode floor. `vec_slice` is the same decode //! with the deep clone added. use bytes::{BufMut, Bytes}; use commonware_codec::{Copying, DecodeExt as _}; use commonware_runtime::{IoBuf, IoBufMut}; use commonware_utils::sequence::FixedBytes; use criterion::Criterion; use std::hint::black_box; macro_rules! bench_sizes { ($c:expr, $($size:literal),+ $(,)?) => { $( let vec = payload($size); let bytes = Bytes::from(vec.clone()); let iobuf_bytes = IoBuf::from(bytes.clone()); let iobuf_native = { let mut buffer = IoBufMut::with_capacity(vec.len()); buffer.put_slice(&vec); buffer.freeze() }; bench_decode_fixed::<$size, _>($c, "bytes", || { FixedBytes::<$size>::decode(bytes.clone()).unwrap() }); bench_decode_fixed::<$size, _>($c, "bytes_ref", || { let mut bytes = bytes.clone(); FixedBytes::<$size>::decode(&mut bytes).unwrap() }); bench_decode_fixed::<$size, _>($c, "iobuf_bytes", || { FixedBytes::<$size>::decode(iobuf_bytes.clone()).unwrap() }); bench_decode_fixed::<$size, _>($c, "iobuf_bytes_ref", || { let mut iobuf_bytes = iobuf_bytes.clone(); FixedBytes::<$size>::decode(&mut iobuf_bytes).unwrap() }); bench_decode_fixed::<$size, _>($c, "iobuf_native", || { FixedBytes::<$size>::decode(iobuf_native.clone()).unwrap() }); bench_decode_fixed::<$size, _>($c, "iobuf_native_ref", || { let mut iobuf_native = iobuf_native.clone(); FixedBytes::<$size>::decode(&mut iobuf_native).unwrap() }); bench_decode_fixed::<$size, _>($c, "vec", || { FixedBytes::<$size>::decode(black_box(vec.clone())).unwrap() }); bench_decode_fixed::<$size, _>($c, "vec_slice", || { let vec = black_box(vec.clone()); FixedBytes::<$size>::decode(Copying(vec.as_slice())).unwrap() }); bench_decode_fixed::<$size, _>($c, "slice", || { FixedBytes::<$size>::decode(Copying(vec.as_slice())).unwrap() }); )+ }; } pub fn bench(c: &mut Criterion) { bench_sizes!(c, 32, 128, 512, 1024, 4096); } fn bench_decode_fixed(c: &mut Criterion, mode: &'static str, decode: F) where F: Fn() -> FixedBytes, { let name = format!("{}::decode_fixed/mode={mode} size={N}", module_path!()); c.bench_function(&name, |b| { b.iter(|| { black_box(decode()); }); }); } fn payload(size: usize) -> Vec { let mut bytes = Vec::with_capacity(size); for i in 0..size { bytes.push((i as u8).wrapping_mul(37).wrapping_add(11)); } bytes }