Update lints; Rewrite benchmarks; Update readme;

This commit is contained in:
Gabriel Bjørnager Jensen 2025-04-11 15:51:52 +02:00
parent bc22f92297
commit 3175393f9e
9 changed files with 729 additions and 786 deletions

View file

@ -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

View file

@ -1,15 +1,14 @@
[profile.release]
codegen-units = 1
lto = "fat"
opt-level = 3
lto = "fat"
codegen-units = 1
[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"

View file

@ -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

View file

@ -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

View file

@ -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<u8>;
pub type BenchmarkMain = fn(Vec<u8>, 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::<u8>::new()
};
let bincode_main: BenchmarkMain = |mut data: ::std::vec::Vec<u8>, 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::<u8>::new()
};
let borsh_main: BenchmarkMain = |mut data: ::std::vec::Vec<u8>, 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<u8>, 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::<u8>::new()
};
let postcard_main: BenchmarkMain = |mut data: ::std::vec::Vec<u8>, 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::<u8>::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<u8>, 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::<u8>::new();
for _ in 0x0..count {
to_writer(&mut data, &random::<$ty>()).unwrap();
}
data
};
let borsh_main: BenchmarkMain = |data: ::std::vec::Vec<u8>, 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<u8>, 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::<u8>::new();
for _ in 0x0..count {
to_io(&random::<$ty>(), &mut data).unwrap();
}
data
};
let postcard_main: BenchmarkMain = |data: ::std::vec::Vec<u8>, 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<Stats>) {
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);
}
}
}

View file

@ -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<Unit> for StandardUniform {
fn sample<R: Rng + ?Sized>(&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<Unnamed> for StandardUniform {
fn sample<R: Rng + ?Sized>(&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<Named> for StandardUniform {
fn sample<R: Rng + ?Sized>(&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<T>(item_size: usize, value_count: usize) -> impl Iterator<Item = u8>
where
T: Immutable + IntoBytes + Sized,
StandardUniform: Distribution<T>,
{
let count = item_size * value_count;
impl Distribution<Enum> for StandardUniform {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Enum {
let d = <Self as Distribution<u8>>::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::<T>();
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: <https://crates.io/crates/bincode/>");
println!("- Borsh: <https://crates.io/crates/borsh/>");
println!("- Oct: <https://crates.io/crates/oct/>");
println!("- Postcard: <https://crates.io/crates/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!("##################");
eprintln!("# OCT BENCHMARKS #");
eprintln!("##################");
eprintln!();
eprintln!("test_count: {TEST_COUNT}");
eprintln!("value_count: {VALUE_COUNT}");
let mut benchmarks = Suite::new(0xFFFFFFF, 0x4);
benchmarks.push_benchmark(
benchmark! {
encode_u8: {
bincode: {
// Requires `std`.
use bincode::encode_into_std_write;
use bincode::config;
const ITEM_SIZE: usize = size_of::<u8>();
let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
encode_into_std_write(random::<u8>(), &mut buf, config::standard()).unwrap();
}
}
borsh: {
const ITEM_SIZE: usize = size_of::<u8>();
let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
borsh::to_writer(&mut buf, &random::<u8>()).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::<u8>().encode(&mut stream).unwrap();
}
}
postcard: {
const ITEM_SIZE: usize = size_of::<u8>();
let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
postcard::to_io(&random::<u8>(), &mut buf).unwrap();
}
}
}
encode_u32: {
bincode: {
use bincode::encode_into_std_write;
use bincode::config;
const ITEM_SIZE: usize = size_of::<u32>();
let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
encode_into_std_write(random::<u32>(), &mut buf, config::standard()).unwrap();
}
}
borsh: {
const ITEM_SIZE: usize = size_of::<u32>();
let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
borsh::to_writer(&mut buf, &random::<u32>()).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::<u32>().encode(&mut stream).unwrap();
}
}
postcard: {
const ITEM_SIZE: usize = size_of::<u32>();
let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
postcard::to_io(&random::<u32>(), &mut buf).unwrap();
}
}
}
encode_u128: {
bincode: {
use bincode::encode_into_std_write;
use bincode::config;
const ITEM_SIZE: usize = size_of::<u128>();
let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
encode_into_std_write(random::<u128>(), &mut buf, config::standard()).unwrap();
}
}
borsh: {
const ITEM_SIZE: usize = size_of::<u128>();
let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
borsh::to_writer(&mut buf, &random::<u128>()).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::<u128>().encode(&mut stream).unwrap();
}
}
postcard: {
const ITEM_SIZE: usize = size_of::<u128>();
let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
postcard::to_io(&random::<u128>(), &mut buf).unwrap();
}
}
}
encode_char: {
bincode: {
use bincode::encode_into_std_write;
use bincode::config;
const ITEM_SIZE: usize = size_of::<char>();
let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
encode_into_std_write(random::<char>(), &mut buf, config::standard()).unwrap();
}
}
borsh: {
const ITEM_SIZE: usize = size_of::<char>();
let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
borsh::to_writer::<u32, _>(&mut buf, &random::<char>().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::<char>().encode(&mut stream).unwrap();
}
}
postcard: {
const ITEM_SIZE: usize = size_of::<char>();
let mut buf = vec![0x00; ITEM_SIZE * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
postcard::to_io(&random::<char>(), &mut buf).unwrap();
}
}
}
encode_struct_unit: {
bincode: {
use bincode::encode_into_std_write;
use bincode::config;
const ITEM_SIZE: usize = size_of::<Unit>();
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::<Unit>();
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::<Unit>();
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::<Unnamed>();
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::<Unnamed>();
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::<Unnamed>();
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::<Named>();
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::<Named>();
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::<Named>();
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::<u32>() // discriminant
+ size_of::<Unit>();
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::<u8>() // discriminant
+ size_of::<Unit>();
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::<u32>() // discriminant
+ size_of::<Unit>();
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::<u8>();
let buf: Box<[_]> = generate_random_data::<u8>(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::<u8>();
let buf: Box<[_]> = generate_random_data::<u8>(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::<u8>(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::<u8>();
let buf: Box<[_]> = generate_random_data::<u8>(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::<NonZero<u8>>();
let buf: Box<[_]> = generate_random_data::<NonZero<u8>>(ITEM_SIZE, VALUE_COUNT).collect();
for i in 0x0..VALUE_COUNT {
let data = array::from_ref(&buf[i]).as_slice();
let _: (NonZero<u8>, usize) = decode_from_slice(data, config::standard()).unwrap();
}
}
borsh: {
const ITEM_SIZE: usize = size_of::<NonZero<u8>>();
let buf: Box<[_]> = generate_random_data::<NonZero<u8>>(ITEM_SIZE, VALUE_COUNT).collect();
for i in 0x0..VALUE_COUNT {
let data = array::from_ref(&buf[i]).as_slice();
let _: NonZero<u8> = borsh::from_slice(data).unwrap();
}
}
oct: {
use oct::decode::{Decode, Input};
use oct::encode::SizedEncode;
const ITEM_SIZE: usize = NonZero::<u8>::MAX_ENCODED_SIZE;
let buf: Box<[_]> = generate_random_data::<NonZero<u8>>(ITEM_SIZE, VALUE_COUNT).collect();
let mut stream = Input::new(&buf);
for _ in 0x0..VALUE_COUNT {
let _ = NonZero::<u8>::decode(&mut stream).unwrap();
}
}
postcard: {
const ITEM_SIZE: usize = size_of::<NonZero<u8>>();
let buf: Box<[_]> = generate_random_data::<NonZero<u8>>(ITEM_SIZE, VALUE_COUNT).collect();
for i in 0x0..VALUE_COUNT {
let data = array::from_ref(&buf[i]).as_slice();
let _: NonZero<u8> = postcard::from_bytes(data).unwrap();
}
}
}
decode_bool: {
bincode: {
use bincode::decode_from_slice;
use bincode::config;
const ITEM_SIZE: usize = size_of::<bool>();
let buf: Box<[_]> = generate_random_data::<bool>(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::<bool>();
let buf: Box<[_]> = generate_random_data::<bool>(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::<bool>(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::<bool>();
let buf: Box<[_]> = generate_random_data::<bool>(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();
}
}
}
};
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();
}

View file

@ -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,
}

View file

@ -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<Benchmark>,
}
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!();
}
}
}

View file

@ -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** &#8594; | 12.681 | 11.345 | 9.005 | 20.776 |
//! | **Total deviation (p.c.)** &#8594; | +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))]