From b608d10ca18866c6bceab774c54833f7f70d4ac9 Mon Sep 17 00:00:00 2001 From: Moudy Date: Thu, 21 May 2026 16:44:40 +0200 Subject: [PATCH] feat(crypto_primitives_bench): migrate to criterion harness --- Cargo.lock | 168 +++++++++++++++-- Cargo.toml | 1 + docs/benchmarks/crypto_primitives_bench.md | 33 +++- tools/crypto_primitives_bench/Cargo.toml | 11 +- tools/crypto_primitives_bench/README.md | 25 ++- .../benches/primitives.rs | 90 +++++++++ tools/crypto_primitives_bench/src/main.rs | 175 ------------------ 7 files changed, 294 insertions(+), 209 deletions(-) create mode 100644 tools/crypto_primitives_bench/benches/primitives.rs delete mode 100644 tools/crypto_primitives_bench/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index e81f6326..4236cd7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,6 +91,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -126,6 +135,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" version = "0.6.21" @@ -1405,6 +1420,12 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cbindgen" version = "0.29.2" @@ -1489,6 +1510,33 @@ dependencies = [ "windows-link", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -1860,6 +1908,41 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +dependencies = [ + "cast", + "itertools 0.13.0", +] + [[package]] name = "critical-section" version = "1.2.0" @@ -1942,12 +2025,10 @@ dependencies = [ name = "crypto_primitives_bench" version = "0.1.0" dependencies = [ - "anyhow", + "criterion", "key_protocol", "nssa_core", "rand 0.8.5", - "serde", - "serde_json", ] [[package]] @@ -2098,7 +2179,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -2582,7 +2663,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3145,6 +3226,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hash32" version = "0.2.1" @@ -3561,7 +3653,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.3", "tokio", "tower-service", "tracing", @@ -6583,6 +6675,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -6737,6 +6835,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "parking" version = "2.2.1" @@ -6893,6 +7001,34 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "polling" version = "3.11.0" @@ -7235,7 +7371,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.5.10", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -7272,7 +7408,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.3", "tracing", "windows-sys 0.52.0", ] @@ -8167,7 +8303,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -9138,7 +9274,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -9373,6 +9509,16 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.10.0" @@ -10469,7 +10615,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f8be040a..de8dd74c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -131,6 +131,7 @@ url = { version = "2.5.4", features = ["serde"] } tokio-retry = "0.3.0" schemars = "1.2" async-stream = "0.3.6" +criterion = "0.8" logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" } logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" } diff --git a/docs/benchmarks/crypto_primitives_bench.md b/docs/benchmarks/crypto_primitives_bench.md index f25650af..74c534fc 100644 --- a/docs/benchmarks/crypto_primitives_bench.md +++ b/docs/benchmarks/crypto_primitives_bench.md @@ -14,15 +14,17 @@ Cryptographic primitives used by client/wallet code. Measures the per-call cost ## Results -100 timed iterations per operation, 2 warmup discarded. +Criterion sample_size = 50, warm_up_time = 2 s, measurement_time = 10 s. Slope-regression point estimate in the middle column; 95% confidence interval bounds in the outer columns. -| Operation | best (µs) | mean (µs) | stdev (µs) | -|---|---:|---:|---:| -| KeyChain::new_os_random | 2,979.62 (2.98 ms) | 3,138.18 (3.14 ms) | 258.59 (0.26 ms) | -| KeyChain::new_mnemonic | 2,979.12 (2.98 ms) | 3,012.76 (3.01 ms) | 46.09 (0.05 ms) | -| SharedSecretKey::new (sender DH) | 74.17 (0.07 ms) | 74.48 (0.07 ms) | 0.22 (<0.01 ms) | -| EncryptionScheme::encrypt | 0.88 (<0.01 ms) | 0.92 (<0.01 ms) | 0.03 (<0.01 ms) | -| EncryptionScheme::decrypt | 0.75 (<0.01 ms) | 0.78 (<0.01 ms) | 0.04 (<0.01 ms) | +| Operation | low | point | high | outliers (mild + severe) | +|---|---:|---:|---:|---:| +| keychain/new_os_random | 3.11 ms | 3.21 ms | 3.34 ms | 3 + 5 | +| keychain/new_mnemonic | 3.05 ms | 3.11 ms | 3.23 ms | 0 + 2 | +| shared_secret_key/sender_dh | 76.7 µs | 78.4 µs | 80.6 µs | 3 + 4 | +| encryption/encrypt | 1.11 µs | 1.17 µs | 1.25 µs | 1 + 5 | +| encryption/decrypt | 907 ns | 928 ns | 954 ns | 0 + 3 | + +Numbers from a single M2 Pro dev box. For full estimates (slope, mean, median, MAD, std-dev) and the noise model, see `target/criterion///estimates.json` after running locally. ## Findings @@ -33,10 +35,21 @@ Cryptographic primitives used by client/wallet code. Measures the per-call cost ## Reproduce ```sh -cargo run --release -p crypto_primitives_bench +cargo bench -p crypto_primitives_bench --bench primitives ``` -JSON output: `target/crypto_primitives_bench.json`. +JSON estimates: `target/criterion///estimates.json`. HTML report: `target/criterion/report/index.html`. + +## Baseline comparison + +```sh +# On main: +cargo bench -p crypto_primitives_bench --bench primitives -- --save-baseline main +# On your branch: +cargo bench -p crypto_primitives_bench --bench primitives -- --baseline main +``` + +Criterion reports per-bench change as a percentage with a 95% confidence interval; deltas within the CI are reported as "no significant change" rather than red. ## Caveats diff --git a/tools/crypto_primitives_bench/Cargo.toml b/tools/crypto_primitives_bench/Cargo.toml index e6d1240c..38005bd0 100644 --- a/tools/crypto_primitives_bench/Cargo.toml +++ b/tools/crypto_primitives_bench/Cargo.toml @@ -8,11 +8,12 @@ publish = false [lints] workspace = true -[dependencies] +[dev-dependencies] key_protocol.workspace = true nssa_core = { workspace = true, features = ["host"] } - -anyhow.workspace = true -serde.workspace = true -serde_json.workspace = true rand = { workspace = true } +criterion = { workspace = true, features = ["html_reports"] } + +[[bench]] +name = "primitives" +harness = false diff --git a/tools/crypto_primitives_bench/README.md b/tools/crypto_primitives_bench/README.md index f45174c4..eb2da149 100644 --- a/tools/crypto_primitives_bench/README.md +++ b/tools/crypto_primitives_bench/README.md @@ -1,20 +1,29 @@ # crypto_primitives_bench -Cryptographic primitive microbenchmarks used by client/wallet code. Single host binary, no live sequencer or Bedrock needed. +Criterion-driven microbenchmarks for the cryptographic primitives client/wallet code uses on every transaction. No live sequencer or Bedrock needed. ## Run ```sh -cargo run --release -p crypto_primitives_bench +cargo bench -p crypto_primitives_bench --bench primitives ``` ## What you'll see -Per-operation `best_us`, `mean_us`, and `stdev_us` over 100 iterations (plus 2 warmup): +Criterion's per-operation report (point estimate, 95% CI, outlier counts) for: -- `KeyChain::new_os_random` — full mnemonic → SSK → NSK/VSK + public-key derivation (HMAC-SHA512 PBKDF dominates). -- `KeyChain::new_mnemonic` — same pipeline, mnemonic exposed. -- `SharedSecretKey::new (sender DH)` — secp256k1 ECDH per recipient. -- `EncryptionScheme::encrypt` / `decrypt` — ChaCha20 over an Account note. +- `keychain/new_os_random`: full mnemonic → SSK → NSK/VSK + public-key derivation (HMAC-SHA512 PBKDF dominates). +- `keychain/new_mnemonic`: same pipeline, mnemonic exposed. +- `shared_secret_key/sender_dh`: secp256k1 ECDH per recipient (includes ephemeral key gen). +- `encryption/encrypt` / `decrypt`: ChaCha20 over an Account note. -JSON output is written to `target/crypto_primitives_bench.json`. +Per-bench JSON estimates are written under `target/criterion///`. HTML reports at `target/criterion/report/index.html`. + +## Baseline comparison + +```sh +# On main: +cargo bench -p crypto_primitives_bench --bench primitives -- --save-baseline main +# On your branch: +cargo bench -p crypto_primitives_bench --bench primitives -- --baseline main +``` diff --git a/tools/crypto_primitives_bench/benches/primitives.rs b/tools/crypto_primitives_bench/benches/primitives.rs new file mode 100644 index 00000000..3691dbea --- /dev/null +++ b/tools/crypto_primitives_bench/benches/primitives.rs @@ -0,0 +1,90 @@ +//! Criterion microbenchmarks for client/wallet cryptographic primitives. +//! +//! Measures: +//! - `KeyChain::new_os_random` (mnemonic → SSK → NSK/VSK + public keys) +//! - `KeyChain::new_mnemonic` (same, but mnemonic exposed) +//! - `SharedSecretKey::new` (Diffie-Hellman shared key derivation, the per-recipient cost) +//! - `EncryptionScheme::encrypt` / `decrypt` (Account note encryption) + +use std::time::Duration; + +use criterion::{Criterion, criterion_group, criterion_main}; +use key_protocol::key_management::KeyChain; +use nssa_core::{ + Commitment, EncryptionScheme, SharedSecretKey, + account::{Account, AccountId}, + encryption::{EphemeralPublicKey, EphemeralSecretKey}, + program::PrivateAccountKind, +}; +use rand::{RngCore as _, rngs::OsRng}; + +fn bench_keychain(c: &mut Criterion) { + let mut g = c.benchmark_group("keychain"); + g.sample_size(50).noise_threshold(0.05); + g.bench_function("new_os_random", |b| b.iter(KeyChain::new_os_random)); + g.bench_function("new_mnemonic", |b| { + b.iter(|| { + let (_kc, _mnemonic) = KeyChain::new_mnemonic(""); + }); + }); + g.finish(); +} + +fn bench_shared_secret_key(c: &mut Criterion) { + // One-time setup: recipient's viewing public key (sender side bench). + let recipient_kc = KeyChain::new_os_random(); + let vpk = recipient_kc.viewing_public_key; + + let mut g = c.benchmark_group("shared_secret_key"); + g.sample_size(50).noise_threshold(0.05); + g.bench_function("sender_dh", |b| { + b.iter(|| { + let mut bytes = [0_u8; 32]; + OsRng.fill_bytes(&mut bytes); + let esk: EphemeralSecretKey = bytes; + let _epk = EphemeralPublicKey::from(&esk); + SharedSecretKey::new(esk, &vpk) + }); + }); + g.finish(); +} + +fn bench_encryption(c: &mut Criterion) { + // One-time setup: a fixed Account/Commitment and a SharedSecretKey to bench + // encrypt/decrypt over a representative note. ESK gen is excluded from the + // measured loop (covered by the SharedSecretKey bench above). + let recipient_kc = KeyChain::new_os_random(); + let vpk = recipient_kc.viewing_public_key; + let account = Account::default(); + let account_id = AccountId::new([7; 32]); + let commitment = Commitment::new(&account_id, &account); + let shared = { + let mut bytes = [0_u8; 32]; + OsRng.fill_bytes(&mut bytes); + let esk: EphemeralSecretKey = bytes; + SharedSecretKey::new(esk, &vpk) + }; + let kind = PrivateAccountKind::Regular(0_u128); + let output_index: u32 = 0; + + let mut g = c.benchmark_group("encryption"); + g.sample_size(50).noise_threshold(0.05); + g.bench_function("encrypt", |b| { + b.iter(|| EncryptionScheme::encrypt(&account, &kind, &shared, &commitment, output_index)); + }); + // One ciphertext for the decrypt bench (encrypt is deterministic given inputs). + let ct = EncryptionScheme::encrypt(&account, &kind, &shared, &commitment, output_index); + g.bench_function("decrypt", |b| { + b.iter(|| EncryptionScheme::decrypt(&ct, &shared, &commitment, output_index)); + }); + g.finish(); +} + +criterion_group! { + name = benches; + config = Criterion::default() + .warm_up_time(Duration::from_secs(2)) + .measurement_time(Duration::from_secs(10)); + targets = bench_keychain, bench_shared_secret_key, bench_encryption +} +criterion_main!(benches); diff --git a/tools/crypto_primitives_bench/src/main.rs b/tools/crypto_primitives_bench/src/main.rs deleted file mode 100644 index 619153c9..00000000 --- a/tools/crypto_primitives_bench/src/main.rs +++ /dev/null @@ -1,175 +0,0 @@ -//! Cryptographic primitive microbenchmarks used by client/wallet code. -//! -//! Measures: -//! - `KeyChain::new_os_random` (mnemonic → SSK → NSK/VSK + public keys) -//! - `KeyChain::new_mnemonic` (same, but mnemonic exposed) -//! - `SharedSecretKey::new` (Diffie-Hellman shared key derivation, the per-recipient cost) -//! - `EncryptionScheme::encrypt` / `decrypt` (Account note encryption) -//! -//! Reports best-of-N wall time per operation. No live stack required. - -#![expect( - clippy::arithmetic_side_effects, - clippy::as_conversions, - clippy::cast_precision_loss, - clippy::float_arithmetic, - clippy::print_stdout, - reason = "Bench tool" -)] - -use std::{path::PathBuf, time::Instant}; - -use anyhow::Result; -use key_protocol::key_management::KeyChain; -use nssa_core::{ - Commitment, EncryptionScheme, SharedSecretKey, - account::{Account, AccountId}, - encryption::{EphemeralPublicKey, EphemeralSecretKey}, - program::PrivateAccountKind, -}; -use rand::{RngCore as _, rngs::OsRng}; -use serde::Serialize; - -const ITERS: usize = 100; - -#[derive(Debug, Serialize)] -struct OpResult { - op: &'static str, - iters: usize, - best_us: f64, - mean_us: f64, - stdev_us: f64, -} - -fn time(op: &'static str, iters: usize, mut f: F) -> OpResult { - // Warmup - for _ in 0..2 { - f(); - } - let mut samples_ns: Vec = Vec::with_capacity(iters); - for _ in 0..iters { - let t = Instant::now(); - f(); - samples_ns.push(t.elapsed().as_nanos() as f64); - } - let best_ns = samples_ns.iter().copied().fold(f64::INFINITY, f64::min); - let mean_ns: f64 = samples_ns.iter().sum::() / iters as f64; - let stdev_ns = if iters > 1 { - let var: f64 = samples_ns - .iter() - .map(|s| (s - mean_ns).powi(2)) - .sum::() - / (iters - 1) as f64; - var.sqrt() - } else { - 0.0 - }; - OpResult { - op, - iters, - best_us: best_ns / 1_000.0, - mean_us: mean_ns / 1_000.0, - stdev_us: stdev_ns / 1_000.0, - } -} - -fn main() -> Result<()> { - let mut results: Vec = Vec::new(); - - results.push(time("KeyChain::new_os_random", ITERS, || { - let _kc = KeyChain::new_os_random(); - })); - - results.push(time("KeyChain::new_mnemonic", ITERS, || { - let (_kc, _mnemonic) = KeyChain::new_mnemonic(""); - })); - - // SharedSecretKey: caller has ephemeral secret, recipient has VSK→VPK. - // We bench the SENDER side: derive ephemeral pubkey, then SharedSecretKey::new(scalar, point). - let recipient_kc = KeyChain::new_os_random(); - let vpk = recipient_kc.viewing_public_key; - results.push(time("SharedSecretKey::new (sender DH)", ITERS, || { - let mut bytes = [0_u8; 32]; - OsRng.fill_bytes(&mut bytes); - let esk: EphemeralSecretKey = bytes; - let _epk = EphemeralPublicKey::from(&esk); - let _ssk = SharedSecretKey::new(esk, &vpk); - })); - - // EncryptionScheme::encrypt / decrypt over a small Account note. - let account = Account::default(); - let account_id = AccountId::new([7; 32]); - let commitment = Commitment::new(&account_id, &account); - let shared = { - let mut bytes = [0_u8; 32]; - OsRng.fill_bytes(&mut bytes); - let esk: EphemeralSecretKey = bytes; - SharedSecretKey::new(esk, &vpk) - }; - let kind = PrivateAccountKind::Regular(0_u128); - let output_index: u32 = 0; - - let mut produced_ct = None; - results.push(time("EncryptionScheme::encrypt", ITERS, || { - let ct = EncryptionScheme::encrypt(&account, &kind, &shared, &commitment, output_index); - produced_ct = Some(ct); - })); - let ct = produced_ct.expect("encrypt produced ciphertext"); - results.push(time("EncryptionScheme::decrypt", ITERS, || { - let _decoded = EncryptionScheme::decrypt(&ct, &shared, &commitment, output_index); - })); - - print_table(&results); - write_json(&results)?; - Ok(()) -} - -fn print_table(results: &[OpResult]) { - let ow = results - .iter() - .map(|r| r.op.len()) - .max() - .unwrap_or(0) - .max("op".len()); - let cw = 22_usize; - println!( - "{:6} {:>cw$} {:>cw$} {:>cw$}", - "op", "iters", "best_us (ms)", "mean_us (ms)", "stdev_us (ms)", - ); - println!("{}", "-".repeat(ow + 6 + cw * 3 + 8)); - for r in results { - println!( - "{:6} {:>cw$} {:>cw$} {:>cw$}", - r.op, - r.iters, - fmt_us_ms(r.best_us), - fmt_us_ms(r.mean_us), - fmt_us_ms(r.stdev_us), - ); - } -} - -fn fmt_us_ms(us: f64) -> String { - let ms = us / 1_000.0; - if ms < 0.01 { - format!("{us:.2} (<0.01 ms)") - } else { - format!("{us:.2} ({ms:.2} ms)") - } -} - -fn write_json(results: &[OpResult]) -> Result<()> { - let workspace_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("..") - .join("..") - .canonicalize()?; - let out_path = workspace_root - .join("target") - .join("crypto_primitives_bench.json"); - if let Some(parent) = out_path.parent() { - std::fs::create_dir_all(parent)?; - } - std::fs::write(&out_path, serde_json::to_string_pretty(&results)?)?; - println!("\nJSON written to {}", out_path.display()); - Ok(()) -}