Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ name = "deku"
harness = false
required-features = ["alloc"]

[[bench]]
name = "bebits"
harness = false
required-features = ["alloc", "bits"]

[lints]
workspace = true

Expand Down
75 changes: 75 additions & 0 deletions benches/bebits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//! Big-endian bit-packed headers: the shape used by real network / space
//! protocols (CCSDS, IPv4, DVB-S2). Mirrors the CCSDS TM Transfer Frame
//! primary header, 6 octets / 11 fields.
use criterion::{criterion_group, criterion_main, Criterion};
use deku::prelude::*;
use no_std_io::io::Cursor;

#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
#[deku(endian = "big")]
struct TmPrimaryHeader {
#[deku(bits = 2)]
tfvn: u8,
#[deku(bits = 10)]
scid: u16,
#[deku(bits = 3)]
vcid: u8,
#[deku(bits = 1)]
ocf: u8,
mcfc: u8,
vcfc: u8,
#[deku(bits = 1)]
tfs: u8,
#[deku(bits = 1)]
syn: u8,
#[deku(bits = 1)]
po: u8,
#[deku(bits = 2)]
sli: u8,
#[deku(bits = 11)]
fhp: u16,
}

/// Same 6 octets, byte-aligned: the deku fast path, for scale.
#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
#[deku(endian = "big")]
struct SixBytes {
a: u8,
b: u8,
c: u8,
d: u8,
e: u8,
f: u8,
}

/// Worst case in the docs: a 1-bit field in a wide container.
#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
#[deku(endian = "big")]
struct OneBitU64 {
#[deku(bits = 1)]
a: u64,
}

fn bench(c: &mut Criterion) {
let buf = [0x2Au8, 0xB5, 0x11, 0x22, 0xC7, 0xFF, 0x00, 0x99];
c.bench_function("be_tm_primary_header_11_fields", |b| {
b.iter(|| {
let mut r = Reader::new(Cursor::new(&buf));
TmPrimaryHeader::from_reader_with_ctx(&mut r, ()).unwrap()
})
});
c.bench_function("be_six_bytes_aligned", |b| {
b.iter(|| {
let mut r = Reader::new(Cursor::new(&buf));
SixBytes::from_reader_with_ctx(&mut r, ()).unwrap()
})
});
c.bench_function("be_one_bit_in_u64", |b| {
b.iter(|| {
let mut r = Reader::new(Cursor::new(&buf));
OneBitU64::from_reader_with_ctx(&mut r, ()).unwrap()
})
});
}
criterion_group!(bebits, bench);
criterion_main!(bebits);
25 changes: 25 additions & 0 deletions src/impls/primitive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,22 @@ macro_rules! ImplDekuReadBits {
}
}

// Fast path: big-endian, Msb0 bit order.
//
// The generic paths below zero-extend the value to the container
// width by calling `BoundedBitVec::insert(0, false)` once per
// padding bit, and each `insert` shifts the entire backing array
// right by one bit. That is O(MAX_TYPE_BITS) bit operations per
// padding bit, i.e. O(MAX_TYPE_BITS * (MAX_TYPE_BITS - bit_size))
// per field -- for a 1-bit field in a u64 that is 63 whole-array
// shifts. `load_be` performs the identical zero-extension in one
// step. Only Msb0 + big-endian is handled here; the Lsb0 and
// little-endian paths keep their existing behaviour.
if !input_is_le && order == Order::Msb0 && bit_size > 0 {
let value = bit_slice.load_be::<$inner>();
return Ok((bit_size, <$typ>::from_be_bytes(value.to_be_bytes())));
}

// if read from Lsb order and it's especially cursed since its not just within one byte...
// read_bits returned: [0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1]
// | second | first |
Expand Down Expand Up @@ -286,6 +302,15 @@ macro_rules! ImplDekuReadBits {
}
}

// Fast path: big-endian. This impl has no `Order` parameter and
// so is always Msb0. See the comment on the same fast path in
// the `(Endian, BitSize, Order)` impl above for why the generic
// path below is quadratic in the container width.
if !input_is_le && bit_size > 0 {
let value = bit_slice.load_be::<$inner>();
return Ok((bit_size, <$typ>::from_be_bytes(value.to_be_bytes())));
}

// Create a new BoundedBitVec from the slice and pad un-aligned chunks
// i.e. [10010110, 1110] -> [10010110, 00001110]
const MAX_TYPE_BYTES: usize = core::mem::size_of::<$typ>();
Expand Down
Loading