From 3175393f9effe9f616efa96e7692e539412d12b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Bj=C3=B8rnager=20Jensen?= Date: Fri, 11 Apr 2025 15:51:52 +0200 Subject: [PATCH] Update lints; Rewrite benchmarks; Update readme; --- CHANGELOG.md | 6 + Cargo.toml | 13 +- README.md | 48 +- oct-benchmarks/Cargo.toml | 6 +- oct-benchmarks/src/benchmark/mod.rs | 350 +++++++++++ oct-benchmarks/src/main.rs | 943 +++++++--------------------- oct-benchmarks/src/stats/mod.rs | 32 + oct-benchmarks/src/suite/mod.rs | 63 ++ oct/src/lib.rs | 54 +- 9 files changed, 729 insertions(+), 786 deletions(-) create mode 100644 oct-benchmarks/src/benchmark/mod.rs create mode 100644 oct-benchmarks/src/stats/mod.rs create mode 100644 oct-benchmarks/src/suite/mod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 76df920..a6f9701 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ This is the changelog of [Oct](https://crates.io/crates/oct/). See `README.md` for more information. +## 0.23.1 + +* Update lints +* Rewrite benchmarks +* Update readme + ## 0.23.0 * Update lints diff --git a/Cargo.toml b/Cargo.toml index 7a3c208..5de8688 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,15 +1,14 @@ [profile.release] +opt-level = 3 +lto = "fat" codegen-units = 1 -lto = "fat" -opt-level = 3 - [workspace] members = ["oct", "oct-benchmarks", "oct-macros"] resolver = "2" [workspace.package] -version = "0.23.0" +version = "0.23.1" authors = ["Gabriel Bjørnager Jensen"] readme = "README.md" repository = "https://mandelbrot.dk/bjoernager/oct/" @@ -17,7 +16,6 @@ keywords = ["encoding", "io", "network", "no-std", "serialization"] categories = ["encoding", "network-programming"] [workspace.lints.clippy] -alloc_instead_of_core = "forbid" arc_with_non_send_sync = "forbid" as_pointer_underscore = "forbid" as_ptr_cast_mut = "forbid" @@ -145,8 +143,6 @@ same_name_method = "forbid" self_named_module_files = "forbid" separated_literal_suffix = "warn" single_char_pattern = "warn" -std_instead_of_alloc = "forbid" -std_instead_of_core = "forbid" str_split_at_newline = "warn" string_lit_as_bytes = "forbid" string_lit_chars_any = "warn" @@ -179,3 +175,6 @@ verbose_bit_mask = "warn" verbose_file_reads = "warn" wildcard_dependencies = "forbid" zero_prefixed_literal = "allow" + +[workspace.lints.rust] +invalid_atomic_ordering = "forbid" diff --git a/README.md b/README.md index 4a3661a..02fff79 100644 --- a/README.md +++ b/README.md @@ -16,33 +16,39 @@ This crate is compatible with `no_std`. As Oct is optimised exclusively for a single, binary format, it *may* outperform other libraries that are more generic in nature. The `oct-benchmarks` binary compares multiple scenarios using Oct and other, similar crates. -According to my runs on an AMD Ryzen 7 3700X with default settings, these benchmarks indicate that Oct usually outperforms the other tested crates – as demonstrated in the following table: +According to my runs, these benchmarks indicate that Oct usually outperforms the other tested crates -- as demonstrated in the following table: -| Benchmark | [Bincode] | [Borsh] | Oct | [Postcard] | -| :--------------------------------- | --------: | ------: | -----: | ---------: | -| `encode_u8` | 0.886 | 0.855 | 0.764 | 0.812 | -| `encode_u32` | 1.225 | 0.938 | 0.764 | 2.653 | -| `encode_u128` | 2.685 | 2.219 | 1.643 | 6.061 | -| `encode_char` | 1.604 | 1.211 | 0.781 | 2.375 | -| `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.000 | -| `encode_struct_unnamed` | 1.399 | 1.196 | 0.779 | 2.235 | -| `encode_struct_named` | 1.458 | 1.441 | 1.026 | 3.016 | -| `encode_enum_unit` | 0.287 | 0.287 | 0.000 | 0.286 | -| `decode_u8` | 0.865 | 0.916 | 1.003 | 0.911 | -| `decode_non_zero_u8` | 1.204 | 1.246 | 1.217 | 1.241 | -| `decode_bool` | 1.067 | 1.036 | 1.028 | 1.187 | -| **Total time** → | 12.681 | 11.345 | 9.005 | 20.776 | -| **Total deviation (p.c.)** → | +41 | +26 | ±0 | +131 | +| Benchmark | Oct | [Bincode] | [Borsh] | [Postcard] | +| :--------------------------------- | -----: | --------: | ------: | ---------: | +| `encode_u8` | 100.00 | 102.88 | 102.73 | 102.63 | +| `encode_u16` | 100.00 | 110.67 | 95.62 | 204.46 | +| `encode_u32` | 100.00 | 171.04 | 111.23 | 257.03 | +| `encode_u64` | 100.00 | 171.14 | 116.93 | 379.45 | +| `encode_u128` | 100.00 | 170.11 | 118.74 | 361.56 | +| `encode_bool` | 100.00 | 94.86 | 101.49 | 100.03 | +| `encode_unit_struct` | 100.00 | 99.87 | 99.85 | 99.96 | +| `encode_newtype` | 100.00 | 202.31 | 109.80 | 243.34 | +| `encode_struct` | 100.00 | 50.19 | 50.09 | 118.93 | +| `encode_enum` | 100.00 | 107.57 | 84.80 | 113.98 | +| `decode_u8` | 100.00 | 5.65 | 5.83 | 5.52 | +| `decode_u16` | 100.00 | 706.27 | 135.52 | 671.54 | +| `decode_u32` | 100.00 | 651.28 | 105.80 | 410.24 | +| `decode_u64` | 100.00 | 697.40 | 141.31 | 1549.56 | +| `decode_u128` | 100.00 | 529.90 | 117.32 | 1425.68 | +| `decode_bool` | 100.00 | 74.59 | 80.24 | 74.55 | +| `decode_unit_struct` | 100.00 | 76.05 | 115.57 | 65.87 | +| `decode_newtype` | 100.00 | 859.92 | 105.83 | 247.19 | +| `decode_struct` | 100.00 | 28.59 | 28.60 | 28.35 | [Bincode]: https://crates.io/crates/bincode/ [Borsh]: https://crates.io/crates/borsh/ [Postcard]: https://crates.io/crates/postcard/ -All quantities are measured in seconds unless otherwise noted. +... wherein quantities denote indicies (with `100` being the reference). +Lower is better. -Currently, Oct's weakest point seems to be decoding. -Please note that I myself find large (relatively speaking) inconsistencies between runs in these last three benchmarks. -Do feel free to conduct your own tests of Oct. +Feedback is greatly appreciated on the mechanics of these benchmarks. +Do also feel free to conduct your own tests of Oct. ## Data model @@ -54,7 +60,7 @@ It is recommended for implementors of custom types to adhere to this convention See specific types' implementations for notes on their data models. -**Note that the data model is currently not stabilised,** and may not necessarily be in the near future (at least before [specialisation](https://github.com/rust-lang/rust/issues/31844/)). +**Note that not all data models may be stabilised at the current moment.** It may therefore be undesired to store encodings long-term. ## Usage & Examples diff --git a/oct-benchmarks/Cargo.toml b/oct-benchmarks/Cargo.toml index fcc3960..06a69ca 100644 --- a/oct-benchmarks/Cargo.toml +++ b/oct-benchmarks/Cargo.toml @@ -32,9 +32,10 @@ readme.workspace = true repository.workspace = true [dependencies] -oct = { path = "../oct", version = "0.23", features = ["proc-macro"]} +oct = { path = "../oct", version = "0.23", features = ["proc-macro", "std"]} bincode = "2.0" +indexmap = "2.9" rand = "0.9" rayon = "1.10" zerocopy = "0.8" @@ -44,4 +45,5 @@ postcard = { version = "1.1", features = ["use-std"] } serde = { version = "1.0", features = ["derive"] } [lints] -workspace = true +# FIXME: Borsh does not like our lints. >:( +#workspace = true diff --git a/oct-benchmarks/src/benchmark/mod.rs b/oct-benchmarks/src/benchmark/mod.rs new file mode 100644 index 0000000..8f8f428 --- /dev/null +++ b/oct-benchmarks/src/benchmark/mod.rs @@ -0,0 +1,350 @@ +// Copyright (c) 2024-2025 Gabriel Bjørnager Jensen. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use crate::Stats; + +use indexmap::IndexMap; +use std::time::Instant; + +pub type BenchmarkInit = fn(u32) -> Vec; +pub type BenchmarkMain = fn(Vec, u32); + +#[macro_export] +macro_rules! benchmark { + { + name: $name:expr, + kind: encode, + ty: $ty:ty$(,)? + } => {{ + use $crate::{BenchmarkInit, BenchmarkMain}; + + let bincode_init: BenchmarkInit = |_count| { + use ::std::vec::Vec; + + Vec::::new() + }; + + let bincode_main: BenchmarkMain = |mut data: ::std::vec::Vec, count| { + use ::bincode::encode_into_std_write; + use ::bincode::config; + use ::rand::random; + + for _ in 0x0..count { + encode_into_std_write(random::<$ty>(), &mut data, config::standard()).unwrap(); + } + }; + + let borsh_init: BenchmarkInit = |_count| { + use ::std::vec::Vec; + + Vec::::new() + }; + + let borsh_main: BenchmarkMain = |mut data: ::std::vec::Vec, count| { + use ::borsh::to_writer; + use ::rand::random; + + for _ in 0x0..count { + to_writer(&mut data, &random::<$ty>()).unwrap(); + } + }; + + let oct_init: BenchmarkInit = |count| { + use ::oct::encode::SizedEncode; + use ::std::vec::Vec; + + let len = <$ty>::MAX_ENCODED_SIZE * count as usize; + + let mut data = Vec::with_capacity(len); + data.resize(len, Default::default()); + + data + }; + + let oct_main: BenchmarkMain = |mut data: ::std::vec::Vec, count| { + use ::oct::encode::{Encode, Output}; + use ::rand::random; + + let mut output = Output::new(&mut data); + + for _ in 0x0..count { + Encode::encode(&random::<$ty>(), &mut output).unwrap(); + } + }; + + let postcard_init: BenchmarkInit = |_count| { + use ::std::vec::Vec; + + Vec::::new() + }; + + let postcard_main: BenchmarkMain = |mut data: ::std::vec::Vec, count| { + use ::postcard::to_io; + use ::rand::random; + + for _ in 0x0..count { + to_io(&random::<$ty>(), &mut data).unwrap(); + } + }; + + let code = [ + ( + "oct", + (oct_init, oct_main), + ), + ( + "bincode", + (bincode_init, bincode_main), + ), + ( + "borsh", + (borsh_init, borsh_main), + ), + ( + "postcard", + (postcard_init, postcard_main), + ), + ]; + + $crate::Benchmark { + name: $name, + + code: code.into(), + } + }}; + + { + name: $name:expr, + kind: decode, + ty: $ty:ty$(,)? + } => {{ + let bincode_init: BenchmarkInit = |count| { + use ::bincode::encode_into_std_write; + use ::bincode::config; + use ::rand::random; + use ::std::vec::Vec; + + let mut data = Vec::::new(); + + for _ in 0x0..count { + encode_into_std_write(random::<$ty>(), &mut data, config::standard()).unwrap(); + } + + data + }; + + let bincode_main: BenchmarkMain = |data: ::std::vec::Vec, count| { + use ::bincode::decode_from_std_read; + use ::bincode::config; + use ::std::io::Cursor; + + let mut data = Cursor::new(data); + + for _ in 0x0..count { + let _: $ty = decode_from_std_read(&mut data, config::standard()).unwrap(); + } + }; + + let borsh_init: BenchmarkInit = |count| { + use ::borsh::to_writer; + use ::rand::random; + use ::std::vec::Vec; + + let mut data = Vec::::new(); + + for _ in 0x0..count { + to_writer(&mut data, &random::<$ty>()).unwrap(); + } + + data + }; + + let borsh_main: BenchmarkMain = |data: ::std::vec::Vec, count| { + use ::borsh::from_slice; + use ::std::io::{Cursor, Read}; + + let mut data = Cursor::new(data); + + for _ in 0x0..count { + let mut buf = [0x00; size_of::<$ty>()]; + data.read_exact(&mut buf).unwrap(); + + let _: $ty = from_slice(&buf).unwrap(); + } + }; + + let oct_init: BenchmarkInit = |count| { + use ::oct::encode::{Encode, Output, SizedEncode}; + use ::rand::random; + use ::std::vec::Vec; + + let len = <$ty>::MAX_ENCODED_SIZE * count as usize; + + let mut data = Vec::with_capacity(len); + data.resize(len, Default::default()); + + let mut output = Output::new(&mut data); + + for _ in 0x0..count { + Encode::encode(&random::<$ty>(), &mut output).unwrap(); + } + + data + }; + + let oct_main: BenchmarkMain = |data: ::std::vec::Vec, count| { + use ::oct::decode::{Decode, Input}; + + let mut input = Input::new(&data); + + for _ in 0x0..count { + let _: $ty = Decode::decode(&mut input).unwrap(); + } + }; + + let postcard_init: BenchmarkInit = |count| { + use ::postcard::to_io; + use ::rand::random; + use ::std::vec::Vec; + + let mut data = Vec::::new(); + + for _ in 0x0..count { + to_io(&random::<$ty>(), &mut data).unwrap(); + } + + data + }; + + let postcard_main: BenchmarkMain = |data: ::std::vec::Vec, count| { + use ::postcard::take_from_bytes; + + let mut data = data.as_slice(); + + for _ in 0x0..count { + let _value: $ty; + (_value, data) = take_from_bytes(data).unwrap(); + } + }; + + let code = [ + ( + "oct", + (oct_init, oct_main), + ), + ( + "bincode", + (bincode_init, bincode_main), + ), + ( + "borsh", + (borsh_init, borsh_main), + ), + ( + "postcard", + (postcard_init, postcard_main), + ), + ]; + + $crate::Benchmark { + name: $name, + + code: code.into(), + } + }}; +} + +pub struct Benchmark { + pub name: &'static str, + + pub code: IndexMap<&'static str, (BenchmarkInit, BenchmarkMain)>, +} + +impl Benchmark { + pub fn run(self, value_count: u32, run_count: u32) { + assert!(value_count != 0x0); + assert!(run_count != 0x0); + + eprintln!("- running benchmark `{}`:", self.name); + + fn print_stats(stats: Stats, ref_stats: Option) { + eprint!(" {:>9.3}s", stats.main_duration.as_secs_f64()); + + if let Some(ref_stats) = ref_stats { + let difference = (stats.main_duration.as_secs_f64() / ref_stats.main_duration.as_secs_f64() - 1.0) * 100.0; + + let colour: u8 = if difference >= 0.0 { + 0x20 + } else if difference < 0.0 { + 0x1F + } else { + 0x00 + }; + + let difference = format!("{difference:+.2}"); + + eprint!(" => \u{001B}[{colour:03}m{difference:>8}%\u{001B}[000m"); + } + + eprintln!(); + } + + let mut ref_stats = None; + + for (crate_name, (init, main)) in self.code { + let crate_name = format!("`{crate_name}`"); + + eprint!("--- {crate_name:<16}"); + + let mut stats = Stats { + value_count, + run_count, + + ..Default::default() + }; + + for i in 0x0..run_count { + eprint!(" {i}..."); + + let init_start = Instant::now(); + let data = init(value_count); + let init_end = Instant::now(); + + stats.data_size += data.len(); + + let main_start = Instant::now(); + main(data, value_count); + let main_end = Instant::now(); + + stats.init_duration += init_end.duration_since(init_start); + stats.main_duration += main_end.duration_since(main_start); + } + + stats.data_size /= run_count as usize; + + stats.init_duration /= run_count; + stats.main_duration /= run_count; + + print_stats(stats, ref_stats); + + ref_stats.get_or_insert(stats); + } + } +} diff --git a/oct-benchmarks/src/main.rs b/oct-benchmarks/src/main.rs index a0f7378..1548756 100644 --- a/oct-benchmarks/src/main.rs +++ b/oct-benchmarks/src/main.rs @@ -1,5 +1,3 @@ -// MIT License -// // Copyright (c) 2024-2025 Gabriel Bjørnager Jensen. // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -20,776 +18,255 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -use core::array; -use core::num::NonZero; -use rand::random; +const _: () = assert!(usize::BITS >= u32::BITS); + +mod benchmark; +mod stats; +mod suite; + +pub use benchmark::{Benchmark, BenchmarkInit, BenchmarkMain}; +pub use stats::Stats; +pub use suite::Suite; + use rand::distr::{Distribution, StandardUniform}; -use std::time::Instant; -use zerocopy::{Immutable, IntoBytes}; - -const TEST_COUNT: u32 = 0x4; - -const VALUE_COUNT: usize = 0xFFFFFFF; - -macro_rules! benchmark { - { - $($name:ident: { - bincode: $bincode_op:block$(,)? - - borsh: $borsh_op:block$(,)? - - oct: $oct_op:block$(,)? - - postcard: $postcard_op:block$(,)? - }$(,)?)+ - } => {{ - use ::core::{concat, stringify}; - use ::std::{eprint, eprintln}; - - 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_oct_duration = 0.0; - let mut total_postcard_duration = 0.0; - - $({ - eprintln!(); - eprint!(concat!("\u{001B}[001mrunning benchmark `", stringify!($name), "`...\u{001B}[022m")); - - let mut bincode_duration = 0.0; - let mut borsh_duration = 0.0; - let mut oct_duration = 0.0; - let mut postcard_duration = 0.0; - - for i in 0x0..TEST_COUNT { - eprint!(" {i}..."); - - eprint!("\u{001B}[093m"); - - bincode_duration += time! { $bincode_op }; - borsh_duration += time! { $borsh_op }; - oct_duration += time! { $oct_op }; - postcard_duration += time! { $postcard_op }; - - eprint!("\u{001B}[000m"); - } - - eprintln!(); - - bincode_duration /= f64::from(TEST_COUNT); - borsh_duration /= f64::from(TEST_COUNT); - oct_duration /= f64::from(TEST_COUNT); - postcard_duration /= f64::from(TEST_COUNT); - - eprint!("\u{001B}[000m"); - eprintln!("bincode: {}", format_score(bincode_duration, oct_duration)); - eprintln!("borsh: {}", format_score(borsh_duration, oct_duration)); - eprintln!("oct: {}", format_score(oct_duration, oct_duration)); - eprintln!("postcard: {}", format_score(postcard_duration, oct_duration)); - - total_bincode_duration += bincode_duration; - total_borsh_duration += borsh_duration; - total_oct_duration += oct_duration; - total_postcard_duration += postcard_duration; - })* - - eprintln!(); - eprintln!("\u{001B}[001mtotal score:\u{001B}[022m"); - eprintln!("bincode: {}", format_score(total_bincode_duration, total_oct_duration)); - eprintln!("borsh: {}", format_score(total_borsh_duration, total_oct_duration)); - eprintln!("oct: {}", format_score(total_oct_duration, total_oct_duration)); - eprintln!("postcard: {}", format_score(total_postcard_duration, total_oct_duration)); - }}; -} +use rand::Rng; #[derive(bincode::Decode, bincode::Encode)] -#[derive(borsh::BorshSerialize)] +#[derive(borsh::BorshDeserialize, borsh::BorshSerialize)] #[derive(oct::decode::Decode, oct::encode::Encode, oct::encode::SizedEncode)] #[derive(serde::Deserialize, serde::Serialize)] #[repr(transparent)] struct Unit; +impl Distribution for StandardUniform { + fn sample(&self, _rng: &mut R) -> Unit { + Unit + } +} + #[derive(bincode::Decode, bincode::Encode)] -#[derive(borsh::BorshSerialize)] +#[derive(borsh::BorshDeserialize, borsh::BorshSerialize)] #[derive(oct::decode::Decode, oct::encode::Encode, oct::encode::SizedEncode)] #[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) +impl Distribution for StandardUniform { + fn sample(&self, rng: &mut R) -> Unnamed { + let c: char = self.sample(rng); + Unnamed(c.into()) } } #[derive(bincode::Decode, bincode::Encode)] -#[derive(borsh::BorshSerialize)] +#[derive(borsh::BorshDeserialize, borsh::BorshSerialize)] #[derive(oct::decode::Decode, oct::encode::Encode, oct::encode::SizedEncode)] #[derive(serde::Deserialize, serde::Serialize)] #[repr(transparent)] struct Named { buf: [u8; 0x8] } -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 } +impl Distribution for StandardUniform { + fn sample(&self, rng: &mut R) -> Named { + let i: u64 = self.sample(rng); + Named { buf: i.to_be_bytes() } } } +#[repr(u8)] #[derive(bincode::Decode, bincode::Encode)] -#[derive(borsh::BorshSerialize)] +#[derive(borsh::BorshDeserialize, borsh::BorshSerialize)] #[derive(oct::decode::Decode, oct::encode::Encode, oct::encode::SizedEncode)] #[derive(serde::Deserialize, serde::Serialize)] enum Enum { + None, Unit(Unit), Unnamed(Unnamed), Named(Named), } -fn generate_random_data(item_size: usize, value_count: usize) -> impl Iterator -where - T: Immutable + IntoBytes + Sized, - StandardUniform: Distribution, -{ - let count = item_size * value_count; +impl Distribution for StandardUniform { + fn sample(&self, rng: &mut R) -> Enum { + let d = >::sample(self, rng) & 0b00000011; - let mut data = Vec::new(); + match d { + 0x0 => Enum::None, + 0x1 => Enum::Unit( self.sample(rng)), + 0x2 => Enum::Unnamed(self.sample(rng)), + 0x3 => Enum::Named( self.sample(rng)), - for _ in 0x0..count { - let value = random::(); - - data.extend(value.as_bytes()); + _ => unreachable!(), + } } - - data.into_iter() } fn main() { - println!("##################"); - println!("# OCT BENCHMARKS #"); - println!("##################"); - println!(); - println!("Each benchmark has a version written for the following crates:"); - println!(); - println!("- Bincode: "); - println!("- Borsh: "); - println!("- Oct: "); - 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 Oct (which should always be c. `0%` for oct 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!("test_count: {TEST_COUNT}"); - eprintln!("value_count: {VALUE_COUNT}"); - - benchmark! { - encode_u8: { - bincode: { - // Requires `std`. - - use bincode::encode_into_std_write; - use bincode::config; - - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - encode_into_std_write(random::(), &mut buf, config::standard()).unwrap(); - } - } - - borsh: { - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - borsh::to_writer(&mut buf, &random::()).unwrap(); - } - } - - oct: { - use oct::encode::{Encode, Output, SizedEncode}; - - const ITEM_SIZE: usize = u8::MAX_ENCODED_SIZE; - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT].into_boxed_slice(); - let mut stream = Output::new(&mut buf); - - for _ in 0x0..VALUE_COUNT { - random::().encode(&mut stream).unwrap(); - } - } - - postcard: { - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - postcard::to_io(&random::(), &mut buf).unwrap(); - } - } - } - - encode_u32: { - bincode: { - use bincode::encode_into_std_write; - use bincode::config; - - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - encode_into_std_write(random::(), &mut buf, config::standard()).unwrap(); - } - } - - borsh: { - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - borsh::to_writer(&mut buf, &random::()).unwrap(); - } - } - - oct: { - use oct::encode::{Encode, Output, SizedEncode}; - - const ITEM_SIZE: usize = u32::MAX_ENCODED_SIZE; - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT].into_boxed_slice(); - let mut stream = Output::new(&mut buf); - - for _ in 0x0..VALUE_COUNT { - random::().encode(&mut stream).unwrap(); - } - } - - postcard: { - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - postcard::to_io(&random::(), &mut buf).unwrap(); - } - } - } - - encode_u128: { - bincode: { - use bincode::encode_into_std_write; - use bincode::config; - - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - encode_into_std_write(random::(), &mut buf, config::standard()).unwrap(); - } - } - - borsh: { - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - borsh::to_writer(&mut buf, &random::()).unwrap(); - } - } - - oct: { - use oct::encode::{Encode, Output, SizedEncode}; - - const ITEM_SIZE: usize = u128::MAX_ENCODED_SIZE; - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT].into_boxed_slice(); - let mut stream = Output::new(&mut buf); - - for _ in 0x0..VALUE_COUNT { - random::().encode(&mut stream).unwrap(); - } - } - - postcard: { - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - postcard::to_io(&random::(), &mut buf).unwrap(); - } - } - } - - encode_char: { - bincode: { - use bincode::encode_into_std_write; - use bincode::config; - - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - encode_into_std_write(random::(), &mut buf, config::standard()).unwrap(); - } - } - - borsh: { - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - borsh::to_writer::(&mut buf, &random::().into()).unwrap(); - } - } - - oct: { - use oct::encode::{Encode, Output, SizedEncode}; - - const ITEM_SIZE: usize = char::MAX_ENCODED_SIZE; - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT].into_boxed_slice(); - let mut stream = Output::new(&mut buf); - - for _ in 0x0..VALUE_COUNT { - random::().encode(&mut stream).unwrap(); - } - } - - postcard: { - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - postcard::to_io(&random::(), &mut buf).unwrap(); - } - } - } - - encode_struct_unit: { - bincode: { - use bincode::encode_into_std_write; - use bincode::config; - - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - encode_into_std_write(Unit, &mut buf, config::standard()).unwrap(); - } - } - - borsh: { - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - borsh::to_writer(&mut buf, &Unit).unwrap(); - } - } - - oct: { - use oct::encode::{Encode, Output, SizedEncode}; - - const ITEM_SIZE: usize = Unit::MAX_ENCODED_SIZE; - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT].into_boxed_slice(); - let mut stream = Output::new(&mut buf); - - for _ in 0x0..VALUE_COUNT { - Unit.encode(&mut stream).unwrap(); - } - } - - postcard: { - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - postcard::to_io(&Unit, &mut buf).unwrap(); - } - } - } - - encode_struct_unnamed: { - bincode: { - use bincode::encode_into_std_write; - use bincode::config; - - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - encode_into_std_write(Unnamed::from_char(random()), &mut buf, config::standard()).unwrap(); - } - } - - borsh: { - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - borsh::to_writer(&mut buf, &Unnamed::from_char(random())).unwrap(); - } - } - - oct: { - use oct::encode::{Encode, Output, SizedEncode}; - - const ITEM_SIZE: usize = Unnamed::MAX_ENCODED_SIZE; - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT].into_boxed_slice(); - let mut stream = Output::new(&mut buf); - - for _ in 0x0..VALUE_COUNT { - Unnamed::from_char(random()).encode(&mut stream).unwrap(); - } - } - - postcard: { - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - postcard::to_io(&Unnamed::from_char(random()), &mut buf).unwrap(); - } - } - } - - encode_struct_named: { - bincode: { - use bincode::encode_into_std_write; - use bincode::config; - - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - encode_into_std_write(Named::from_u64(random()), &mut buf, config::standard()).unwrap(); - } - } - - borsh: { - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - borsh::to_writer(&mut buf, &Named::from_u64(random())).unwrap(); - } - } - - oct: { - use oct::encode::{Encode, Output, SizedEncode}; - - const ITEM_SIZE: usize = Named::MAX_ENCODED_SIZE; - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT].into_boxed_slice(); - let mut stream = Output::new(&mut buf); - - for _ in 0x0..VALUE_COUNT { - Named::from_u64(random()).encode(&mut stream).unwrap(); - } - } - - postcard: { - const ITEM_SIZE: usize = size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - postcard::to_io(&Named::from_u64(random()), &mut buf).unwrap(); - } - } - } - - encode_enum_unit: { - bincode: { - use bincode::encode_into_std_write; - use bincode::config; - - const ITEM_SIZE: usize = - size_of::() // discriminant - + size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - encode_into_std_write(Enum::Unit(Unit), &mut buf, config::standard()).unwrap(); - } - } - - borsh: { - const ITEM_SIZE: usize = - size_of::() // discriminant - + size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - borsh::to_writer(&mut buf, &Enum::Unit(Unit)).unwrap(); - } - } - - oct: { - use oct::encode::{Encode, Output, SizedEncode}; - - const ITEM_SIZE: usize = - isize::MAX_ENCODED_SIZE // discriminant - + Unit::MAX_ENCODED_SIZE; - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT].into_boxed_slice(); - let mut stream = Output::new(&mut buf); - - for _ in 0x0..VALUE_COUNT { - Enum::Unit(Unit).encode(&mut stream).unwrap(); - } - } - - postcard: { - const ITEM_SIZE: usize = - size_of::() // discriminant - + size_of::(); - - let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT]; - - for _ in 0x0..VALUE_COUNT { - postcard::to_io(&Enum::Unit(Unit), &mut buf).unwrap(); - } - } - } - - decode_u8: { - bincode: { - use bincode::decode_from_slice; - use bincode::config; - - const ITEM_SIZE: usize = size_of::(); - - let buf: Box<[_]> = generate_random_data::(ITEM_SIZE, VALUE_COUNT).collect(); - - for i in 0x0..VALUE_COUNT { - let data = array::from_ref(&buf[i]).as_slice(); - - let _: (u8, usize) = decode_from_slice(data, config::standard()).unwrap(); - } - } - - borsh: { - const ITEM_SIZE: usize = size_of::(); - - let buf: Box<[_]> = generate_random_data::(ITEM_SIZE, VALUE_COUNT).collect(); - - for i in 0x0..VALUE_COUNT { - let data = array::from_ref(&buf[i]).as_slice(); - - let _: u8 = borsh::from_slice(data).unwrap(); - } - } - - oct: { - use oct::decode::{Decode, Input}; - use oct::encode::SizedEncode; - - const ITEM_SIZE: usize = u8::MAX_ENCODED_SIZE; - - let buf: Box<[_]> = generate_random_data::(ITEM_SIZE, VALUE_COUNT).collect(); - let mut stream = Input::new(&buf); - - for _ in 0x0..VALUE_COUNT { - let _ = u8::decode(&mut stream).unwrap(); - } - } - - postcard: { - const ITEM_SIZE: usize = size_of::(); - - let buf: Box<[_]> = generate_random_data::(ITEM_SIZE, VALUE_COUNT).collect(); - - for i in 0x0..VALUE_COUNT { - let data = array::from_ref(&buf[i]).as_slice(); - - let _: u8 = postcard::from_bytes(data).unwrap(); - } - } - } - - decode_non_zero_u8: { - bincode: { - use bincode::decode_from_slice; - use bincode::config; - - const ITEM_SIZE: usize = size_of::>(); - - let buf: Box<[_]> = generate_random_data::>(ITEM_SIZE, VALUE_COUNT).collect(); - - for i in 0x0..VALUE_COUNT { - let data = array::from_ref(&buf[i]).as_slice(); - - let _: (NonZero, usize) = decode_from_slice(data, config::standard()).unwrap(); - } - } - - borsh: { - const ITEM_SIZE: usize = size_of::>(); - - let buf: Box<[_]> = generate_random_data::>(ITEM_SIZE, VALUE_COUNT).collect(); - - for i in 0x0..VALUE_COUNT { - let data = array::from_ref(&buf[i]).as_slice(); - - let _: NonZero = borsh::from_slice(data).unwrap(); - } - } - - oct: { - use oct::decode::{Decode, Input}; - use oct::encode::SizedEncode; - - const ITEM_SIZE: usize = NonZero::::MAX_ENCODED_SIZE; - - let buf: Box<[_]> = generate_random_data::>(ITEM_SIZE, VALUE_COUNT).collect(); - let mut stream = Input::new(&buf); - - for _ in 0x0..VALUE_COUNT { - let _ = NonZero::::decode(&mut stream).unwrap(); - } - } - - postcard: { - const ITEM_SIZE: usize = size_of::>(); - - let buf: Box<[_]> = generate_random_data::>(ITEM_SIZE, VALUE_COUNT).collect(); - - for i in 0x0..VALUE_COUNT { - let data = array::from_ref(&buf[i]).as_slice(); - - let _: NonZero = postcard::from_bytes(data).unwrap(); - } - } - } - - decode_bool: { - bincode: { - use bincode::decode_from_slice; - use bincode::config; - - const ITEM_SIZE: usize = size_of::(); - - let buf: Box<[_]> = generate_random_data::(ITEM_SIZE, VALUE_COUNT).collect(); - - for i in 0x0..VALUE_COUNT { - let data = array::from_ref(&buf[i]).as_slice(); - - let _: (bool, usize) = decode_from_slice(data, config::standard()).unwrap(); - } - } - - borsh: { - const ITEM_SIZE: usize = size_of::(); - - let buf: Box<[_]> = generate_random_data::(ITEM_SIZE, VALUE_COUNT).collect(); - - for i in 0x0..VALUE_COUNT { - let data = array::from_ref(&buf[i]).as_slice(); - - let _: bool = borsh::from_slice(data).unwrap(); - } - } - - oct: { - use oct::decode::{Decode, Input}; - use oct::encode::SizedEncode; - - const ITEM_SIZE: usize = bool::MAX_ENCODED_SIZE; - - let buf: Box<[_]> = generate_random_data::(ITEM_SIZE, VALUE_COUNT).collect(); - let mut stream = Input::new(&buf); - - for _ in 0x0..VALUE_COUNT { - let _ = bool::decode(&mut stream); - } - } - - postcard: { - const ITEM_SIZE: usize = size_of::(); - - let buf: Box<[_]> = generate_random_data::(ITEM_SIZE, VALUE_COUNT).collect(); - - for i in 0x0..VALUE_COUNT { - let data = array::from_ref(&buf[i]).as_slice(); - - let _: bool = postcard::from_bytes(data).unwrap(); - } - } - } - }; + eprintln!("##################"); + eprintln!("# OCT BENCHMARKS #"); + eprintln!("##################"); + eprintln!(); + + let mut benchmarks = Suite::new(0xFFFFFFF, 0x4); + + benchmarks.push_benchmark( + benchmark! { + name: "encode_u8", + kind: encode, + ty: u8, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "encode_u16", + kind: encode, + ty: u16, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "encode_u32", + kind: encode, + ty: u32, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "encode_u64", + kind: encode, + ty: u64, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "encode_u128", + kind: encode, + ty: u128, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "encode_bool", + kind: encode, + ty: bool, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "encode_unit_struct", + kind: encode, + ty: Unit, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "encode_newtype", + kind: encode, + ty: Unnamed, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "encode_struct", + kind: encode, + ty: Named, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "encode_enum", + kind: encode, + ty: Enum, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "decode_u8", + kind: decode, + ty: u8, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "decode_u16", + kind: decode, + ty: u16, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "decode_u32", + kind: decode, + ty: u32, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "decode_u64", + kind: decode, + ty: u64, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "decode_u128", + kind: decode, + ty: u128, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "decode_bool", + kind: decode, + ty: bool, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "decode_unit_struct", + kind: decode, + ty: Unit, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "decode_newtype", + kind: decode, + ty: Unnamed, + }, + ); + + benchmarks.push_benchmark( + benchmark! { + name: "decode_struct", + kind: decode, + ty: Named, + }, + ); + + // Does not work with Borsh. +// benchmarks.push_benchmark( +// benchmark! { +// name: "decode_enum", +// kind: decode, +// ty: Enum, +// }, +// ); + + benchmarks.run(); } diff --git a/oct-benchmarks/src/stats/mod.rs b/oct-benchmarks/src/stats/mod.rs new file mode 100644 index 0000000..d902e48 --- /dev/null +++ b/oct-benchmarks/src/stats/mod.rs @@ -0,0 +1,32 @@ +// Copyright (c) 2024-2025 Gabriel Bjørnager Jensen. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use std::time::Duration; + +#[derive(Clone, Copy, Debug, Default)] +pub struct Stats { + pub value_count: u32, + pub run_count: u32, + + pub data_size: usize, + + pub init_duration: Duration, + pub main_duration: Duration, +} diff --git a/oct-benchmarks/src/suite/mod.rs b/oct-benchmarks/src/suite/mod.rs new file mode 100644 index 0000000..63d8c80 --- /dev/null +++ b/oct-benchmarks/src/suite/mod.rs @@ -0,0 +1,63 @@ +// Copyright (c) 2024-2025 Gabriel Bjørnager Jensen. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use crate::Benchmark; + +pub struct Suite { + value_count: u32, + run_count: u32, + + benchmarks: Vec, +} + +impl Suite { + #[inline] + #[must_use] + pub fn new(value_count: u32, run_count: u32) -> Self { + assert!(value_count != 0x0); + assert!(run_count != 0x0); + + Self { + value_count, + run_count, + + benchmarks: Default::default(), + } + } + + #[inline] + #[track_caller] + pub fn push_benchmark(&mut self, benchmark: Benchmark) { + self.benchmarks.push(benchmark); + } + + pub fn run(self) { + let benchmark_count = self.benchmarks.len(); + + eprintln!("running `{benchmark_count}` benchmark(s)"); + eprintln!(); + + for benchmark in self.benchmarks { + benchmark.run(self.value_count, self.run_count); + + eprintln!(); + } + } +} diff --git a/oct/src/lib.rs b/oct/src/lib.rs index 1058a69..d1e9790 100644 --- a/oct/src/lib.rs +++ b/oct/src/lib.rs @@ -24,33 +24,39 @@ //! As Oct is optimised exclusively for a single, binary format, it *may* outperform other libraries that are more generic in nature. //! //! The `oct-benchmarks` binary compares multiple scenarios using Oct and other, similar crates. -//! According to my runs on an AMD Ryzen 7 3700X with default settings, these benchmarks indicate that Oct usually outperforms the other tested crates -- as demonstrated in the following table: +//! According to my runs, these benchmarks indicate that Oct usually outperforms the other tested crates -- as demonstrated in the following table: //! -//! | Benchmark | [Bincode] | [Borsh] | Oct | [Postcard] | -//! | :--------------------------------- | --------: | ------: | -----: | ---------: | -//! | `encode_u8` | 0.886 | 0.855 | 0.764 | 0.812 | -//! | `encode_u32` | 1.225 | 0.938 | 0.764 | 2.653 | -//! | `encode_u128` | 2.685 | 2.219 | 1.643 | 6.061 | -//! | `encode_char` | 1.604 | 1.211 | 0.781 | 2.375 | -//! | `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.000 | -//! | `encode_struct_unnamed` | 1.399 | 1.196 | 0.779 | 2.235 | -//! | `encode_struct_named` | 1.458 | 1.441 | 1.026 | 3.016 | -//! | `encode_enum_unit` | 0.287 | 0.287 | 0.000 | 0.286 | -//! | `decode_u8` | 0.865 | 0.916 | 1.003 | 0.911 | -//! | `decode_non_zero_u8` | 1.204 | 1.246 | 1.217 | 1.241 | -//! | `decode_bool` | 1.067 | 1.036 | 1.028 | 1.187 | -//! | **Total time** → | 12.681 | 11.345 | 9.005 | 20.776 | -//! | **Total deviation (p.c.)** → | +41 | +26 | ±0 | +131 | +//! | Benchmark | Oct | [Bincode] | [Borsh] | [Postcard] | +//! | :--------------------------------- | -----: | --------: | ------: | ---------: | +//! | `encode_u8` | 100.00 | 102.88 | 102.73 | 102.63 | +//! | `encode_u16` | 100.00 | 110.67 | 95.62 | 204.46 | +//! | `encode_u32` | 100.00 | 171.04 | 111.23 | 257.03 | +//! | `encode_u64` | 100.00 | 171.14 | 116.93 | 379.45 | +//! | `encode_u128` | 100.00 | 170.11 | 118.74 | 361.56 | +//! | `encode_bool` | 100.00 | 94.86 | 101.49 | 100.03 | +//! | `encode_unit_struct` | 100.00 | 99.87 | 99.85 | 99.96 | +//! | `encode_newtype` | 100.00 | 202.31 | 109.80 | 243.34 | +//! | `encode_struct` | 100.00 | 50.19 | 50.09 | 118.93 | +//! | `encode_enum` | 100.00 | 107.57 | 84.80 | 113.98 | +//! | `decode_u8` | 100.00 | 5.65 | 5.83 | 5.52 | +//! | `decode_u16` | 100.00 | 706.27 | 135.52 | 671.54 | +//! | `decode_u32` | 100.00 | 651.28 | 105.80 | 410.24 | +//! | `decode_u64` | 100.00 | 697.40 | 141.31 | 1549.56 | +//! | `decode_u128` | 100.00 | 529.90 | 117.32 | 1425.68 | +//! | `decode_bool` | 100.00 | 74.59 | 80.24 | 74.55 | +//! | `decode_unit_struct` | 100.00 | 76.05 | 115.57 | 65.87 | +//! | `decode_newtype` | 100.00 | 859.92 | 105.83 | 247.19 | +//! | `decode_struct` | 100.00 | 28.59 | 28.60 | 28.35 | //! //! [Bincode]: https://crates.io/crates/bincode/ //! [Borsh]: https://crates.io/crates/borsh/ //! [Postcard]: https://crates.io/crates/postcard/ //! -//! All quantities are measured in seconds unless otherwise noted. +//! ... wherein quantities denote indicies (with `100` being the reference). +//! Lower is better. //! -//! Currently, Oct's weakest point seems to be decoding. -//! Please note that I myself find large (relatively speaking) inconsistencies between runs in these last three benchmarks. -//! Do feel free to conduct your own tests of Oct. +//! Feedback is greatly appreciated on the mechanics of these benchmarks. +//! Do also feel free to conduct your own tests of Oct. //! //! # Data model //! @@ -62,7 +68,7 @@ //! //! See specific types' implementations for notes on their data models. //! -//! **Note that the data model is currently not stabilised,** and may not necessarily be in the near future (at least before [specialisation](https://github.com/rust-lang/rust/issues/31844/)). +//! **Note that not all data models may be stabilised at the current moment.** //! It may therefore be undesired to store encodings long-term. //! //! # Usage & Examples @@ -227,8 +233,10 @@ #![cfg_attr(feature = "unstable-docs", feature(doc_cfg, rustdoc_internals))] -#![forbid(invalid_atomic_ordering)] -#![warn(missing_docs)] +#![forbid(clippy::alloc_instead_of_core)] +#![deny( missing_docs)] +#![forbid(clippy::std_instead_of_alloc)] +#![forbid(clippy::std_instead_of_core)] #![cfg_attr(feature = "unstable-docs", allow(internal_features))]