1
0
Fork 0
mirror of https://codeberg.org/icewind/bitbuffer.git synced 2026-06-03 08:34:07 +02:00

move benchmarks to iai-callgrind

This commit is contained in:
Robin Appelman 2025-07-13 18:35:17 +02:00
commit bf4d038c8d
11 changed files with 311 additions and 299 deletions

67
Cargo.lock generated
View file

@ -8,12 +8,21 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bitbuffer"
version = "0.11.2"
dependencies = [
"bitbuffer_derive",
"iai",
"iai-callgrind",
"maplit",
"memchr",
"num-traits",
@ -35,6 +44,26 @@ dependencies = [
"syn",
]
[[package]]
name = "derive_more"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dyn-clone"
version = "1.0.18"
@ -42,10 +71,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35"
[[package]]
name = "iai"
version = "0.1.1"
name = "iai-callgrind"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71a816c97c42258aa5834d07590b718b4c9a598944cd39a52dc25b351185d678"
checksum = "cd56318753b26f1f33ccf7385041fd31fdb3f8966aa1c62d46cf2efba9835504"
dependencies = [
"bincode",
"derive_more",
"iai-callgrind-macros",
"iai-callgrind-runner",
]
[[package]]
name = "iai-callgrind-macros"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "763d0d9b10ba3177236eda380afb78bafa150556dc4536969f91f938fee1b83a"
dependencies = [
"derive_more",
"proc-macro-error2",
"proc-macro2",
"quote",
"serde",
"serde_json",
"syn",
]
[[package]]
name = "iai-callgrind-runner"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "100409442ecf8137ec5c0ad83548be9f1d22f072b646e6ed16177558e037a0dd"
dependencies = [
"serde",
]
[[package]]
name = "itoa"

View file

@ -18,11 +18,14 @@ schemars = { version = "0.8.21", optional = true }
[dev-dependencies]
maplit = "1.0.2"
iai = "0.1.1"
iai-callgrind = "0.15.2"
serde_json = "1.0.139"
[profile.bench]
debug = true
[[bench]]
name = "bench"
name = "read"
harness = false
[[bench]]

View file

@ -1,260 +0,0 @@
use bitbuffer::{BigEndian, BitRead, BitReadBuffer, BitReadStream, Endianness, LittleEndian};
use iai::black_box;
fn read_perf<E: Endianness>(buffer: &BitReadBuffer<E>) -> u16 {
let size = 5;
let mut pos = 0;
let len = buffer.bit_len();
let mut result: u16 = 0;
loop {
if pos + size > len {
return result;
}
let data = buffer.read_int::<u64>(pos, size).unwrap() as u16;
result = result.wrapping_add(data);
pos += size;
}
}
const ONES: &[u8; 1024 * 1024 * 10] = &[1u8; 1024 * 1024 * 10];
fn perf_le() {
let buffer = BitReadBuffer::new(black_box(ONES), BigEndian);
let data = read_perf(&buffer);
assert_eq!(data, 0);
black_box(data);
}
fn perf_be() {
let buffer = BitReadBuffer::new(black_box(ONES), BigEndian);
let data = read_perf(&buffer);
assert_eq!(data, 0);
black_box(data);
}
fn perf_f32_be() {
let buffer = BitReadBuffer::new(black_box(ONES), BigEndian);
let mut pos = 0;
let len = buffer.bit_len();
let mut result: f32 = 0.0;
loop {
if pos + 32 > len {
break;
}
let num = buffer.read_float::<f32>(pos).unwrap();
result += num;
pos += 32;
}
assert_eq!(result, 0.00000000000000000000000000000006170106);
black_box(result);
}
fn perf_f32_le() {
let buffer = BitReadBuffer::new(black_box(ONES), BigEndian);
let mut pos = 0;
let len = buffer.bit_len();
let mut result: f32 = 0.0;
loop {
if pos + 32 > len {
break;
}
let num = buffer.read_float::<f32>(pos).unwrap();
result += num;
pos += 32;
}
assert_eq!(result, 0.00000000000000000000000000000006170106);
black_box(result);
}
const F64_RESULT: f64 = 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010156250477904244;
fn perf_f64() {
let buffer = BitReadBuffer::new(black_box(ONES), BigEndian);
let mut pos = 0;
let len = buffer.bit_len();
let mut result: f64 = 0.0;
loop {
if pos + 64 > len {
break;
}
let num = buffer.read_float::<f64>(pos).unwrap();
result += num;
pos += 64;
}
assert_eq!(result, F64_RESULT);
black_box(result);
}
fn perf_bool() {
let buffer = BitReadBuffer::new(black_box(ONES), BigEndian);
let mut pos = 0;
let len = buffer.bit_len();
loop {
if pos >= len {
break;
}
let num = buffer.read_bool(pos).unwrap();
black_box(num);
pos += 1;
}
}
const fn build_string_data<const N: usize>(inputs: &[&str]) -> [u8; N] {
let mut data = [0; N];
let mut i = 0;
loop {
let mut y = 0;
while y < inputs.len() {
let mut z = 0;
let input = inputs[y].as_bytes();
while z < input.len() {
i += 1;
if i >= N {
return data;
}
data[i] = input[z];
z += 1;
}
y += 1;
}
}
}
const fn get_string_buffer<const N: usize>() -> [u8; N] {
let inputs = [
"foo\0",
"bar\0",
"something a little bit longer for extra testing\0",
"a\0",
"\0",
];
build_string_data::<N>(&inputs)
}
const STRING_DATA: [u8; 10 * 1024] = get_string_buffer();
fn perf_string_be() {
let buffer = BitReadBuffer::new(black_box(&STRING_DATA), BigEndian);
let mut pos = 0;
let len = buffer.bit_len();
loop {
if pos + (128 * 8) > len {
break;
}
let result = buffer.read_string(pos, None).unwrap();
pos += (result.len() + 1) * 8;
black_box(result);
}
}
fn perf_string_le() {
let buffer = BitReadBuffer::new(black_box(&STRING_DATA), LittleEndian);
let mut pos = 0;
let len = buffer.bit_len();
loop {
if pos + (128 * 8) > len {
break;
}
let result = buffer.read_string(pos, None).unwrap();
pos += (result.len() + 1) * 8;
black_box(result);
}
}
fn perf_bytes_be() {
let buffer = BitReadBuffer::new(black_box(&STRING_DATA), BigEndian);
let mut pos = 0;
let len = buffer.bit_len();
loop {
if pos + (128 * 8) > len {
break;
}
let result = buffer.read_bytes(pos, 128).unwrap();
pos += (result.len() + 1) * 8;
black_box(result);
}
}
fn perf_bytes_le() {
let buffer = BitReadBuffer::new(black_box(&STRING_DATA), LittleEndian);
let mut pos = 0;
let len = buffer.bit_len();
loop {
if pos + (128 * 8) > len {
break;
}
let result = buffer.read_bytes(pos, 128).unwrap();
pos += (result.len() + 1) * 8;
black_box(result);
}
}
fn perf_bytes_be_unaligned() {
let buffer = BitReadBuffer::new(black_box(&STRING_DATA), BigEndian);
let mut pos = 3;
let len = buffer.bit_len();
loop {
if pos + (128 * 8) > len {
break;
}
let result = buffer.read_bytes(pos, 128).unwrap();
pos += (result.len() + 1) * 8;
black_box(result);
}
}
fn perf_bytes_le_unaligned() {
let buffer = BitReadBuffer::new(black_box(&STRING_DATA), LittleEndian);
let mut pos = 3;
let len = buffer.bit_len();
loop {
if pos + (128 * 8) > len {
break;
}
let result = buffer.read_bytes(pos, 128).unwrap();
pos += (result.len() + 1) * 8;
black_box(result);
}
}
#[allow(dead_code)]
#[derive(BitRead)]
struct BasicStruct {
a: f32,
b: bool,
#[size = 7]
c: u32,
}
fn perf_struct() {
let buffer = BitReadBuffer::new(black_box(&STRING_DATA), LittleEndian);
let mut stream: BitReadStream<LittleEndian> = buffer.clone().into();
while stream.bits_left() > 40 {
let result = stream.read::<BasicStruct>().unwrap();
black_box(result);
}
}
iai::main!(
perf_be,
perf_bool,
perf_bytes_be,
perf_bytes_be_unaligned,
perf_bytes_le,
perf_bytes_le_unaligned,
perf_f32_be,
perf_f32_le,
perf_f64,
perf_le,
perf_string_be,
perf_string_le,
perf_struct
);

190
benches/read.rs Normal file
View file

@ -0,0 +1,190 @@
use bitbuffer::{
BigEndian, BitRead, BitReadBuffer, BitReadStream, BitWriteStream, Endianness, LittleEndian,
};
use iai_callgrind::{library_benchmark, library_benchmark_group, main};
use std::hint::black_box;
const ONES: &[u8; 1024 * 1024 * 10] = &[1u8; 1024 * 1024 * 10];
const ONES_LE: BitReadBuffer<'static, LittleEndian> = BitReadBuffer::new(ONES, LittleEndian);
const ONES_BE: BitReadBuffer<'static, BigEndian> = BitReadBuffer::new(ONES, BigEndian);
#[library_benchmark]
#[bench::le(ONES_LE)]
#[bench::be(ONES_BE)]
fn read_perf<E: Endianness>(buffer: BitReadBuffer<E>) -> u16 {
let size = 5;
let mut pos = 0;
let len = buffer.bit_len();
let mut result: u16 = 0;
loop {
if pos + size > len {
return black_box(result);
}
let data = buffer.read_int::<u64>(pos, size).unwrap() as u16;
result = result.wrapping_add(data);
pos += size;
}
}
#[library_benchmark]
#[bench::le(ONES_LE)]
#[bench::be(ONES_BE)]
fn perf_f32<E: Endianness>(buffer: BitReadBuffer<E>) -> f32 {
let mut pos = 0;
let len = buffer.bit_len();
let mut result: f32 = 0.0;
loop {
if pos + 32 > len {
break;
}
let num = buffer.read_float::<f32>(pos).unwrap();
result += num;
pos += 32;
}
assert_eq!(result, 0.00000000000000000000000000000006170106);
black_box(result)
}
const F64_RESULT: f64 = 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010156250477904244;
#[library_benchmark]
#[bench::le(ONES_LE)]
#[bench::be(ONES_BE)]
fn perf_f64<E: Endianness>(buffer: BitReadBuffer<E>) -> f64 {
let mut pos = 0;
let len = buffer.bit_len();
let mut result: f64 = 0.0;
loop {
if pos + 64 > len {
break;
}
let num = buffer.read_float::<f64>(pos).unwrap();
result += num;
pos += 64;
}
assert_eq!(result, F64_RESULT);
black_box(result)
}
#[library_benchmark]
#[bench::le(ONES_LE)]
#[bench::be(ONES_BE)]
fn perf_bool<E: Endianness>(buffer: BitReadBuffer<E>) {
let mut pos = 0;
let len = buffer.bit_len() / 8;
loop {
if pos >= len {
break;
}
let num = buffer.read_bool(pos).unwrap();
black_box(num);
pos += 1;
}
}
fn build_string_buffer<E: Endianness>(
offset: usize,
endianness: E,
) -> (usize, BitReadBuffer<'static, E>) {
let mut data = Vec::new();
let input = [
"foo\0",
"bar\0",
"something a little bit longer for extra testing\0",
"a\0",
"\0",
]
.join("");
let mut writer = BitWriteStream::new(&mut data, endianness);
writer.write_int(0usize, offset).unwrap();
while writer.byte_len() < 10 * 1024 {
writer.write(&input).unwrap()
}
(offset, BitReadBuffer::new_owned(data, endianness))
}
#[library_benchmark(setup = build_string_buffer)]
#[bench::le_alligned(0, LittleEndian)]
#[bench::be_alligned(0, BigEndian)]
#[bench::le_unalligned(3, LittleEndian)]
#[bench::be_unalligned(3, BigEndian)]
fn perf_string<E: Endianness>((offset, buffer): (usize, BitReadBuffer<E>)) {
let mut pos = offset;
let len = buffer.bit_len();
loop {
if pos + (128 * 8) > len {
break;
}
let result = buffer.read_string(pos, None).unwrap();
pos += (result.len() + 1) * 8;
black_box(result);
}
}
#[library_benchmark(setup = build_string_buffer)]
#[bench::le_alligned(0, LittleEndian)]
#[bench::be_alligned(0, BigEndian)]
#[bench::le_unalligned(3, LittleEndian)]
#[bench::be_unalligned(3, BigEndian)]
fn perf_bytes<E: Endianness>((offset, buffer): (usize, BitReadBuffer<E>)) {
let mut pos = offset;
let len = buffer.bit_len();
loop {
if pos + (128 * 8) > len {
break;
}
let result = buffer.read_bytes(pos, 128).unwrap();
pos += (result.len() + 1) * 8;
black_box(result);
}
}
#[allow(dead_code)]
#[derive(BitRead)]
struct BasicStruct {
a: f32,
b: bool,
#[size = 7]
c: u32,
}
const BASIC: BasicStruct = BasicStruct {
a: 0.0,
b: false,
c: 0,
};
#[allow(dead_code)]
#[derive(BitRead)]
#[discriminant_bits = 2]
enum BasicEnum {
#[size = 5]
Foo(i8),
Bar(bool),
Asd(u8),
Empty,
}
const ENUM: BasicEnum = BasicEnum::Empty;
#[library_benchmark]
#[bench::le(ONES_LE, BASIC)]
#[bench::be(ONES_BE, BASIC)]
#[bench::le_enum(ONES_LE, ENUM)]
#[bench::be_enum(ONES_BE, ENUM)]
fn perf_struct<E: Endianness, Struct: BitRead<'static, E>>(
buffer: BitReadBuffer<E>,
_struct: Struct,
) {
let mut stream: BitReadStream<E> = buffer.into();
while stream.bits_left() > 40 {
let result = stream.read::<BasicStruct>().unwrap();
black_box(result);
}
}
library_benchmark_group!(
name = bench_read_group;
benchmarks = read_perf, perf_bool, perf_bytes, perf_f32, perf_f64, perf_string, perf_struct
);
main!(library_benchmark_groups = bench_read_group);

View file

@ -1,15 +1,35 @@
use bitbuffer::{BitWriteStream, LittleEndian};
use iai::black_box;
use bitbuffer::{BigEndian, BitWriteStream, Endianness, LittleEndian};
use iai_callgrind::{library_benchmark, library_benchmark_group, main};
use std::hint::black_box;
fn write_int_le() {
let mut out = Vec::with_capacity(128);
fn create_output<E: Endianness>(bit_size: usize, endianness: E) -> (E, Vec<u8>, usize) {
(
endianness,
Vec::with_capacity(bit_size.next_power_of_two() / 8 * 128),
bit_size,
)
}
#[library_benchmark(setup = create_output)]
#[bench::le_small(7, LittleEndian)]
#[bench::be_small(7, BigEndian)]
#[bench::le_medium(15, LittleEndian)]
#[bench::be_medoim(15, BigEndian)]
#[bench::le_large(57, LittleEndian)]
#[bench::be_large(57, BigEndian)]
fn write_int_le<E: Endianness>((endianness, mut out, bit_size): (E, Vec<u8>, usize)) {
{
let mut write = BitWriteStream::new(&mut out, LittleEndian);
let mut write = BitWriteStream::new(&mut out, endianness);
for i in 0..128 {
write.write_sized(&black_box(i), 7).unwrap();
write.write_sized(&black_box(i as u64), bit_size).unwrap();
}
}
black_box(out);
}
iai::main!(write_int_le);
library_benchmark_group!(
name = bench_write_group;
benchmarks = write_int_le
);
main!(library_benchmark_groups = bench_write_group);

View file

@ -49,14 +49,14 @@ impl Discriminant {
pub fn read_token(&self, last_discriminant: &mut isize, span: Span) -> TokenStream {
match self {
Discriminant::Int(discriminant) => {
let lit = LitInt::new(&format!("{}", discriminant), span);
let lit = LitInt::new(&discriminant.to_string(), span);
*last_discriminant = *discriminant as isize;
quote! { #lit }
}
Discriminant::Wildcard => quote! { _ },
Discriminant::Default => {
let new_discriminant = (*last_discriminant + 1) as usize;
let lit = LitInt::new(&format!("{}", new_discriminant), span);
let lit = LitInt::new(&new_discriminant.to_string(), span);
*last_discriminant += 1;
quote! { #lit }
}
@ -70,18 +70,18 @@ impl Discriminant {
) -> TokenStream {
match self {
Discriminant::Int(discriminant) => {
let lit = LitInt::new(&format!("{}", discriminant), span);
let lit = LitInt::new(&discriminant.to_string(), span);
*last_discriminant = *discriminant as isize;
quote_spanned! { span => #lit }
}
Discriminant::Wildcard => {
let free_discriminant = max_discriminant + 1;
let lit = LitInt::new(&format!("{}", free_discriminant), span);
let lit = LitInt::new(&free_discriminant.to_string(), span);
quote_spanned! { span => #lit }
}
Discriminant::Default => {
let new_discriminant = (*last_discriminant + 1) as usize;
let lit = LitInt::new(&format!("{}", new_discriminant), span);
let lit = LitInt::new(&new_discriminant.to_string(), span);
*last_discriminant += 1;
quote_spanned! { span => #lit }
}

View file

@ -35,7 +35,7 @@ fn names(fields: &[FieldParam]) -> impl Iterator<Item = Ident> + '_ {
fields
.iter()
.enumerate()
.map(|(index, field)| Ident::new(&format!("__field_{}", index), field.span()))
.map(|(index, field)| Ident::new(&format!("__field_{index}"), field.span()))
}
fn writes(fields: &[FieldParam]) -> impl Iterator<Item = TokenStream> + '_ {

34
flake.lock generated
View file

@ -2,11 +2,11 @@
"nodes": {
"crane": {
"locked": {
"lastModified": 1742394900,
"narHash": "sha256-vVOAp9ahvnU+fQoKd4SEXB2JG2wbENkpqcwlkIXgUC0=",
"lastModified": 1751562746,
"narHash": "sha256-smpugNIkmDeicNz301Ll1bD7nFOty97T79m4GUMUczA=",
"owner": "ipetkov",
"repo": "crane",
"rev": "70947c1908108c0c551ddfd73d4f750ff2ea67cd",
"rev": "aed2020fd3dc26e1e857d4107a5a67a33ab6c1fd",
"type": "github"
},
"original": {
@ -22,11 +22,11 @@
]
},
"locked": {
"lastModified": 1747659009,
"narHash": "sha256-3FFAthqh4rWKTClF+WgM+9CmMDlnfWcPdd3hGBFVNHc=",
"lastModified": 1752106289,
"narHash": "sha256-BC4hWjDDNVmRLvBFCHz7oKiapf1ndBRXPDNle/H+6e8=",
"owner": "nix-community",
"repo": "flakelight",
"rev": "93d72adbe0b022791b0faadfb31cb6e98c37f0ad",
"rev": "0c7170f19b170882f167a71cad1124714ffc192e",
"type": "github"
},
"original": {
@ -44,11 +44,11 @@
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1748035164,
"narHash": "sha256-PMWOFLi3oU/Eb+y4a0MlHS0IcUZ+UFCoGmA1Q7DnoW4=",
"lastModified": 1752438127,
"narHash": "sha256-1NKLbztqg0/sYUhZgxus+WJU+jZThkV+pga/kqjB3/A=",
"ref": "refs/heads/main",
"rev": "e6d5d797b3d2c738309152f2545518b2b117f72b",
"revCount": 48,
"rev": "31877680aa6d391e59a598d35081e24b4316ab9a",
"revCount": 60,
"type": "git",
"url": "https://codeberg.org/icewind/mill-scale.git"
},
@ -59,16 +59,16 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1747862697,
"narHash": "sha256-U4HaNZ1W26cbOVm0Eb5OdGSnfQVWQKbLSPrSSa78KC0=",
"lastModified": 1752162966,
"narHash": "sha256-3MxxkU8ZXMHXcbFz7UE4M6qnIPTYGcE/7EMqlZNnVDE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2baa12ff69913392faf0ace833bc54bba297ea95",
"rev": "10e687235226880ed5e9f33f1ffa71fe60f2638a",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-24.11",
"ref": "nixos-25.05",
"type": "indirect"
}
},
@ -88,11 +88,11 @@
]
},
"locked": {
"lastModified": 1742697269,
"narHash": "sha256-Lpp0XyAtIl1oGJzNmTiTGLhTkcUjwSkEb0gOiNzYFGM=",
"lastModified": 1752374969,
"narHash": "sha256-Ky3ynEkJXih7mvWyt9DWoiSiZGqPeHLU1tlBU4b0mcc=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "01973c84732f9275c50c5f075dd1f54cc04b3316",
"rev": "75fb000638e6d0f57cb1e8b7a4550cbdd8c76f1d",
"type": "github"
},
"original": {

View file

@ -1,6 +1,6 @@
{
inputs = {
nixpkgs.url = "nixpkgs/nixos-24.11";
nixpkgs.url = "nixpkgs/nixos-25.05";
flakelight = {
url = "github:nix-community/flakelight";
inputs.nixpkgs.follows = "nixpkgs";

View file

@ -1,5 +1,5 @@
/// Trait for specifying endianness of bit buffer
pub trait Endianness: private::Sealed {
pub trait Endianness: private::Sealed + Copy {
/// Get the endianness as string, either LittleEndian or BigEndian
fn as_string() -> &'static str {
if Self::is_le() {

View file

@ -123,7 +123,7 @@ where
/// ];
/// let buffer = BitReadBuffer::new(&bytes, LittleEndian);
/// ```
pub fn new(bytes: &'a [u8], _endianness: E) -> Self {
pub const fn new(bytes: &'a [u8], _endianness: E) -> Self {
let byte_len = bytes.len();
BitReadBuffer {