// Copyright 2024 Gabriel Bjørnager Jensen. use rand::random; // Bincode uses so much memory that it crashes if // we set `VALUE_COUNT` too high. const VALUE_COUNT: usize = 0x0FFFFFFF; use std::time::Instant; macro_rules! benchmark { { $($name:ident: { bincode: $bincode_op:block$(,)? borsh: $borsh_op:block$(,)? bzipper: $bzipper_op:block$(,)? ciborium: $ciborium_op:block$(,)? postcard: $postcard_op:block$(,)? }$(,)?)+ } => {{ use ::std::{concat, eprint, eprintln, stringify}; macro_rules! time { { $op: block } => {{ let start = Instant::now(); $op let stop = Instant::now(); let duration = stop - start; (duration.as_nanos() as f64) / 1_000_000_000.0 }}; } fn format_score(duration: f64, ref_duration: f64) -> String { let vps = (VALUE_COUNT as f64 / duration).round(); let difference = (duration / ref_duration - 1.0) * 100.0; let colour: u8 = if difference >= 0.0 { 0x20 } else if difference < 0.0 { 0x1F } else { 0x00 }; format!("{duration:.3}s ({vps:.0} vps) => \u{001B}[{colour}m{difference:+.2}%\u{001B}[000m") } let mut total_bincode_duration = 0.0; let mut total_borsh_duration = 0.0; let mut total_bzipper_duration = 0.0; let mut total_ciborium_duration = 0.0; let mut total_postcard_duration = 0.0; $({ eprintln!(); eprintln!(concat!("\u{001B}[001mrunning benchmark `", stringify!($name), "`...\u{001B}[022m")); eprint!("\u{001B}[093m"); let bincode_duration = time! { $bincode_op }; let borsh_duration = time! { $borsh_op }; let bzipper_duration = time! { $bzipper_op }; let ciborium_duration = time! { $ciborium_op }; let postcard_duration = time! { $postcard_op }; eprint!("\u{001B}[000m"); eprintln!("bincode: {}", format_score(bincode_duration, bzipper_duration)); eprintln!("borsh: {}", format_score(borsh_duration, bzipper_duration)); eprintln!("bzipper: {}", format_score(bzipper_duration, bzipper_duration)); eprintln!("ciborium: {}", format_score(ciborium_duration, bzipper_duration)); eprintln!("postcard: {}", format_score(postcard_duration, bzipper_duration)); total_bincode_duration += bincode_duration; total_borsh_duration += borsh_duration; total_bzipper_duration += bzipper_duration; total_ciborium_duration += ciborium_duration; total_postcard_duration += postcard_duration; })* eprintln!(); eprintln!("\u{001B}[001mtotal score:\u{001B}[022m"); eprintln!("bincode: {}", format_score(total_bincode_duration, total_bzipper_duration)); eprintln!("borsh: {}", format_score(total_borsh_duration, total_bzipper_duration)); eprintln!("bzipper: {}", format_score(total_bzipper_duration, total_bzipper_duration)); eprintln!("ciborium: {}", format_score(total_ciborium_duration, total_bzipper_duration)); eprintln!("postcard: {}", format_score(total_postcard_duration, total_bzipper_duration)); }}; } #[derive(bzipper::Decode, bzipper::SizedEncode)] #[derive(borsh::BorshSerialize)] #[derive(serde::Deserialize, serde::Serialize)] #[repr(transparent)] struct Unit; #[derive(bzipper::Decode, bzipper::SizedEncode)] #[derive(borsh::BorshSerialize)] #[derive(serde::Deserialize, serde::Serialize)] #[repr(transparent)] struct Unnamed(u32); impl Unnamed { #[inline(always)] #[must_use] pub const fn from_char(value: char) -> Self { Self(value as u32) } } #[derive(bzipper::Decode, bzipper::SizedEncode)] #[derive(borsh::BorshSerialize)] #[derive(serde::Deserialize, serde::Serialize)] #[repr(transparent)] struct Named { buf: [u8; 0x8] } #[derive(bzipper::Decode, bzipper::SizedEncode)] #[derive(borsh::BorshSerialize)] #[derive(serde::Deserialize, serde::Serialize)] enum Enum { Unit(Unit), Unnamed(Unnamed), Named(Named), } impl Named { #[inline(always)] #[must_use] pub const fn from_u64(value: u64) -> Self { let buf = [ (value & 0x00_00_00_00_00_00_00_FF) as u8, ((value & 0x00_00_00_00_00_00_FF_00) >> 0x2) as u8, ((value & 0x00_00_00_00_00_FF_00_00) >> 0x4) as u8, ((value & 0x00_00_00_00_FF_00_00_00) >> 0x6) as u8, ((value & 0x00_00_00_FF_00_00_00_00) >> 0x8) as u8, ((value & 0x00_00_FF_00_00_00_00_00) >> 0xA) as u8, ((value & 0x00_FF_00_00_00_00_00_00) >> 0xC) as u8, ((value & 0xFF_00_00_00_00_00_00_00) >> 0xE) as u8, ]; Self { buf } } } fn main() { println!("######################"); println!("# BZIPPER BENCHMARKS #"); println!("######################"); println!(); println!("Each benchmark has a version written for the following crates:"); println!(); println!("- Bincode: "); println!("- Borsh: "); println!("- Ciborium: "); println!("- bzipper: "); println!("- Postcard: "); println!(); println!("The total time the benchmark took (including memory allocations and dealloca-"); println!("tions) is printed when the benchmark has completed. The `vps` counter signifies"); println!("the amount of values per second that the benchmark has handled (during the en-"); println!("tirety of the benchmark)."); println!(); println!("When every benchmark has concluded, the total run time and vps is listed for"); println!("each crate. A percantage additionally compares the run time between the listed"); println!("crate and bzipper (which should always be `0%` for bzipper itself). DO NOTE THAT"); println!("THESE FINAL RESULTS INDICATE A NON-WEIGHTED AVERAGE ACROSS BENCHMARKS. It can"); println!("therefore be skewed relative to real-world performance by the similarity of some"); println!("benchmarks."); println!(); eprintln!("value_count: {VALUE_COUNT}"); benchmark! { encode_u8: { bincode: { // Requires `std`. use bincode::serialize_into; use std::io::Cursor; let buf_size = size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); for _ in 0x0..VALUE_COUNT { serialize_into(&mut buf, &random::()).unwrap(); } } borsh: { use std::io::Cursor; let buf_size = size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); for _ in 0x0..VALUE_COUNT { borsh::to_writer(&mut buf, &random::()).unwrap(); } } bzipper: { use bzipper::{Encode, OStream, SizedEncode}; let buf_size = u8::MAX_ENCODED_SIZE; // value let mut buf = vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice(); let mut stream = OStream::new(&mut buf); for _ in 0x0..VALUE_COUNT { random::().encode(&mut stream).unwrap(); } } ciborium: { use std::io::Cursor; let buf_size = size_of::() // header + size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); for _ in 0x0..VALUE_COUNT { ciborium::into_writer(&random::(), &mut buf).unwrap(); } } postcard: { use std::io::Cursor; let buf_size = size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); for _ in 0x0..VALUE_COUNT { postcard::to_io(&random::(), &mut buf).unwrap(); } } } encode_struct_unit: { bincode: { use bincode::serialize_into; use std::io::Cursor; let buf_size = size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); for _ in 0x0..VALUE_COUNT { serialize_into(&mut buf, &Unit).unwrap(); } } borsh: { use std::io::Cursor; let buf_size = size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); for _ in 0x0..VALUE_COUNT { borsh::to_writer(&mut buf, &Unit).unwrap(); } } bzipper: { use bzipper::{Encode, OStream, SizedEncode}; let buf_size = Unit::MAX_ENCODED_SIZE; // value let mut buf = vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice(); let mut stream = OStream::new(&mut buf); for _ in 0x0..VALUE_COUNT { Unit.encode(&mut stream).unwrap(); } } ciborium: { use std::io::Cursor; let buf_size = size_of::() // header + size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); for _ in 0x0..VALUE_COUNT { ciborium::into_writer(&Unit, &mut buf).unwrap(); } } postcard: { use std::io::Cursor; let buf_size = size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); for _ in 0x0..VALUE_COUNT { postcard::to_io(&Unit, &mut buf).unwrap(); } } } encode_struct_unnamed: { bincode: { use bincode::serialize_into; use std::io::Cursor; let buf_size = size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); for _ in 0x0..VALUE_COUNT { serialize_into(&mut buf, &Unnamed::from_char(random())).unwrap(); } } borsh: { use std::io::Cursor; let buf_size = size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); for _ in 0x0..VALUE_COUNT { borsh::to_writer(&mut buf, &Unnamed::from_char(random())).unwrap(); } } bzipper: { use bzipper::{Encode, OStream, SizedEncode}; let buf_size = Unnamed::MAX_ENCODED_SIZE; // value let mut buf = vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice(); let mut stream = OStream::new(&mut buf); for _ in 0x0..VALUE_COUNT { Unnamed::from_char(random()).encode(&mut stream).unwrap(); } } ciborium: { use std::io::Cursor; let buf_size = size_of::() // header + size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); for _ in 0x0..VALUE_COUNT { ciborium::into_writer(&Unnamed::from_char(random()), &mut buf).unwrap(); } } postcard: { use std::io::Cursor; let buf_size = size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); for _ in 0x0..VALUE_COUNT { postcard::to_io(&Unnamed::from_char(random()), &mut buf).unwrap(); } } } encode_struct_named: { bincode: { use bincode::serialize_into; use std::io::Cursor; let buf_size = size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); for _ in 0x0..VALUE_COUNT { serialize_into(&mut buf, &Named::from_u64(random())).unwrap(); } } borsh: { use std::io::Cursor; let buf_size = size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); for _ in 0x0..VALUE_COUNT { borsh::to_writer(&mut buf, &Named::from_u64(random())).unwrap(); } } bzipper: { use bzipper::{Encode, OStream, SizedEncode}; let buf_size = Named::MAX_ENCODED_SIZE; // value let mut buf = vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice(); let mut stream = OStream::new(&mut buf); for _ in 0x0..VALUE_COUNT { Named::from_u64(random()).encode(&mut stream).unwrap(); } } ciborium: { use std::io::Cursor; let buf_size = size_of::() // header + size_of::() // tag + size_of::() // header + size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); for _ in 0x0..VALUE_COUNT { ciborium::into_writer(&Named::from_u64(random()), &mut buf).unwrap(); } } postcard: { use std::io::Cursor; let buf_size = size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); for _ in 0x0..VALUE_COUNT { postcard::to_io(&Named::from_u64(random()), &mut buf).unwrap(); } } } encode_enum_unit: { bincode: { use bincode::serialize_into; use std::io::Cursor; let buf_size = size_of::() // discriminant + size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); for _ in 0x0..VALUE_COUNT { serialize_into(&mut buf, &Enum::Unit(Unit)).unwrap(); } } borsh: { use std::io::Cursor; let buf_size = size_of::() // discriminant + size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); for _ in 0x0..VALUE_COUNT { borsh::to_writer(&mut buf, &Enum::Unit(Unit)).unwrap(); } } bzipper: { use bzipper::{Encode, OStream, SizedEncode}; let buf_size = isize::MAX_ENCODED_SIZE // discriminant + Unit::MAX_ENCODED_SIZE; // value let mut buf = vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice(); let mut stream = OStream::new(&mut buf); for _ in 0x0..VALUE_COUNT { Enum::Unit(Unit).encode(&mut stream).unwrap(); } } ciborium: { use std::io::Cursor; let buf_size = size_of::() // header + size_of::() // tag (discriminant) + size_of::() // header + size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); for _ in 0x0..VALUE_COUNT { ciborium::into_writer(&Enum::Unit(Unit), &mut buf).unwrap(); } } postcard: { use std::io::Cursor; let buf_size = size_of::() // discriminant + size_of::(); // value let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); for _ in 0x0..VALUE_COUNT { postcard::to_io(&Enum::Unit(Unit), &mut buf).unwrap(); } } } }; }