diff --git a/Cargo.toml b/Cargo.toml index 86589ff2..b859e7e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,6 @@ license.workspace = true homepage.workspace = true repository.workspace = true -[lib] -bench = false - [package.metadata.docs.rs] all-features = true rustdoc-args = [ @@ -101,7 +98,6 @@ ruint = { path = ".", features = ["arbitrary", "proptest"] } ark-bn254-03 = { version = "0.3.0", package = "ark-bn254" } ark-bn254-04 = { version = "0.4.0", package = "ark-bn254" } -criterion = { version = "2.10", package = "codspeed-criterion-compat" } rand-09 = { version = "0.9", package = "rand" } approx = "0.5" @@ -115,6 +111,10 @@ serde_json = "1.0" # borsh borsh = { version = "1.5", features = ["derive"] } +# benches only; we still need to include these here to make rust-analyzer work +arrayvec = "0.7" +criterion = { version = "3", package = "codspeed-criterion-compat" } + [features] default = ["std"] std = [ @@ -202,3 +202,13 @@ strip = false [profile.bench] inherits = "profiling" + +# Looks like there is no way to completely disable cargo's automatic bench/test/etc. finding. +[lib] +bench = false + +[[bench]] +name = "bench" +harness = false +path = "benches/bench.rs" +# required-features = ["run_with_-p_ruint-bench"] diff --git a/benches/benches/base_convert.rs b/benches/benches/base_convert.rs new file mode 100644 index 00000000..ab324cec --- /dev/null +++ b/benches/benches/base_convert.rs @@ -0,0 +1,69 @@ +use crate::prelude::*; +use std::collections::BTreeSet; + +#[allow(clippy::single_element_loop)] +pub fn group(criterion: &mut Criterion) { + const_for!(BITS in BENCH { + const LIMBS: usize = nlimbs(BITS); + for input_bits in BTreeSet::from([BITS]) { + for base in [10] { + bench_from_base::(criterion, input_bits, base, false, |digits| { + Uint::::from_base_le(base, digits.iter().copied()).unwrap() + }); + bench_from_base::(criterion, input_bits, base, true, |digits| { + Uint::::from_base_be(base, digits.iter().copied()).unwrap() + }); + + bench_to_base::(criterion, input_bits, base, false, |n, f| { + n.to_base_le(base).for_each(f); + }); + bench_to_base::(criterion, input_bits, base, true, |n, f| { + n.to_base_be(base).for_each(f); + }); + } + } + }); +} + +fn bench_from_base( + criterion: &mut Criterion, + input_bits: usize, + base: u64, + is_be: bool, + mut f: impl FnMut(&[u64]) -> Uint, +) { + let endian = if is_be { "be" } else { "le" }; + bench_arbitrary_with( + criterion, + &format!("base_convert/{BITS}/{input_bits}/{base}/{endian}"), + Uint::::arbitrary().prop_map(|mut n| { + n >>= BITS - input_bits; + let digits: Vec = if is_be { + n.to_base_be(base).collect() + } else { + n.to_base_le(base).collect() + }; + arrayvec::ArrayVec::::from_iter(digits) + }), + |n| f(black_box(n.as_slice())), + ); +} + +fn bench_to_base( + criterion: &mut Criterion, + input_bits: usize, + base: u64, + is_be: bool, + mut f: impl FnMut(Uint, fn(u64)), +) { + fn noop(_: u64) {} + let noop = black_box(noop); + + let endian = if is_be { "be" } else { "le" }; + bench_arbitrary_with( + criterion, + &format!("base_convert/{BITS}/{input_bits}/{base}/{endian}"), + Uint::::arbitrary().prop_map(|n| n >> (BITS - input_bits)), + |n| f(n, noop), + ); +} diff --git a/benches/benches/fmt.rs b/benches/benches/fmt.rs new file mode 100644 index 00000000..a9b86e93 --- /dev/null +++ b/benches/benches/fmt.rs @@ -0,0 +1,33 @@ +use crate::prelude::*; +use std::fmt::Write; + +pub fn group(criterion: &mut Criterion) { + const_for!(BITS in BENCH { + const LIMBS: usize = nlimbs(BITS); + + bench_fmt::(criterion, "fmt/binary", |n, buf| { + write!(buf, "{n:b}").unwrap() + }); + bench_fmt::(criterion, "fmt/octal", |n, buf| { + write!(buf, "{n:o}").unwrap() + }); + bench_fmt::(criterion, "fmt/decimal", |n, buf| { + write!(buf, "{n}").unwrap() + }); + bench_fmt::(criterion, "fmt/hex", |n, buf| { + write!(buf, "{n:x}").unwrap() + }); + }); +} + +fn bench_fmt( + criterion: &mut Criterion, + name: &str, + mut f: impl FnMut(Uint, &mut String) -> T, +) { + let mut buf = String::with_capacity(BITS); + bench_unop(criterion, name, |n| { + buf.clear(); + f(n, black_box(&mut buf)) + }); +} diff --git a/benches/benches/mod.rs b/benches/benches/mod.rs index cabc844a..77cc2ec2 100644 --- a/benches/benches/mod.rs +++ b/benches/benches/mod.rs @@ -1,8 +1,10 @@ mod add; mod algorithms; +mod base_convert; mod bits; mod cmp; mod div; +mod fmt; mod log; mod modular; mod mul; @@ -12,8 +14,8 @@ mod root; pub(crate) mod prelude; pub fn group(c: &mut criterion::Criterion) { - cmp::group(c); bits::group(c); + add::group(c); mul::group(c); div::group(c); @@ -21,5 +23,11 @@ pub fn group(c: &mut criterion::Criterion) { log::group(c); root::group(c); modular::group(c); + + cmp::group(c); + + base_convert::group(c); + fmt::group(c); + algorithms::group(c); } diff --git a/benches/benches/prelude.rs b/benches/benches/prelude.rs index 39d09d23..539f7ff5 100644 --- a/benches/benches/prelude.rs +++ b/benches/benches/prelude.rs @@ -86,12 +86,12 @@ fn manual_batch( name: &str, ) -> impl FnMut(T) { assert!( - !std::mem::needs_drop::(), + !needs_drop::(), "cannot batch inputs that need to be dropped: {}", std::any::type_name::(), ); assert!( - !std::mem::needs_drop::(), + !needs_drop::(), "cannot batch outputs that need to be dropped: {}", std::any::type_name::(), ); @@ -108,6 +108,17 @@ fn manual_batch( } } +#[cfg(codspeed)] +fn needs_drop() -> bool { + // SAFETY: `ArrayVec` doesn't implement `Copy` when `T: Copy` even though it + // can. + if std::any::type_name::().contains("ArrayVec() + } +} + #[cfg(not(codspeed))] fn manual_batch( _setup: impl FnMut() -> T, diff --git a/ruint-bench/Cargo.toml b/ruint-bench/Cargo.toml index 7499cd17..9737469a 100644 --- a/ruint-bench/Cargo.toml +++ b/ruint-bench/Cargo.toml @@ -20,7 +20,8 @@ harness = false [dev-dependencies] ruint = { path = "..", features = ["std", "proptest"] } -criterion = { version = "2.10", package = "codspeed-criterion-compat" } +arrayvec = "0.7" +criterion = { version = "3", package = "codspeed-criterion-compat" } proptest = "1" [[bench]]