Update lints; Rewrite benchmarks; Update readme;
This commit is contained in:
parent
bc22f92297
commit
3175393f9e
9 changed files with 729 additions and 786 deletions
|
@ -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
|
||||
|
|
13
Cargo.toml
13
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"
|
||||
|
|
48
README.md
48
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
|
||||
|
|
|
@ -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
|
||||
|
|
350
oct-benchmarks/src/benchmark/mod.rs
Normal file
350
oct-benchmarks/src/benchmark/mod.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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!("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::<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();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
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();
|
||||
}
|
||||
|
|
32
oct-benchmarks/src/stats/mod.rs
Normal file
32
oct-benchmarks/src/stats/mod.rs
Normal 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,
|
||||
}
|
63
oct-benchmarks/src/suite/mod.rs
Normal file
63
oct-benchmarks/src/suite/mod.rs
Normal 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!();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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))]
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue