From 2214ff2425b05e95f6ba41f1ec164afbd1eb46c1 Mon Sep 17 00:00:00 2001 From: Andrey Khmuro Date: Mon, 14 Aug 2023 12:34:14 +0200 Subject: [PATCH] chore: TSMT benchmark --- Cargo.toml | 11 ++- src/hash/rpo/digest.rs | 14 +++ src/main.rs | 164 +++++++++++++++++++++++++++++++++++ src/merkle/tiered_smt/mod.rs | 6 ++ 4 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 3a863d8..04dc990 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,13 @@ keywords = ["miden", "crypto", "hash", "merkle"] edition = "2021" rust-version = "1.67" +[[bin]] +name = "miden-crypto" +path = "src/main.rs" +bench = false +doctest = false +required-features = ["std"] + [[bench]] name = "hash" harness = false @@ -26,15 +33,17 @@ harness = false [features] default = ["blake3/default", "std", "winter_crypto/default", "winter_math/default", "winter_utils/default"] -std = ["blake3/std", "winter_crypto/std", "winter_math/std", "winter_utils/std"] +std = ["blake3/std", "winter_crypto/std", "winter_math/std", "winter_utils/std", "rand_utils"] serde = ["winter_math/serde", "dep:serde", "serde/alloc"] [dependencies] blake3 = { version = "1.4", default-features = false } +clap = { version = "4.3.21", features = ["derive"] } winter_crypto = { version = "0.6", package = "winter-crypto", default-features = false } winter_math = { version = "0.6", package = "winter-math", default-features = false } winter_utils = { version = "0.6", package = "winter-utils", default-features = false } serde = { version = "1.0", features = [ "derive" ], optional = true, default-features = false } +rand_utils = { version = "0.6", package = "winter-rand-utils", optional = true } [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } diff --git a/src/hash/rpo/digest.rs b/src/hash/rpo/digest.rs index 18071ae..2a269d6 100644 --- a/src/hash/rpo/digest.rs +++ b/src/hash/rpo/digest.rs @@ -4,6 +4,7 @@ use crate::utils::{ DeserializationError, HexParseError, Serializable, }; use core::{cmp::Ordering, fmt::Display, ops::Deref}; +use winter_utils::Randomizable; /// The number of bytes needed to encoded a digest pub const DIGEST_BYTES: usize = 32; @@ -95,6 +96,19 @@ impl Display for RpoDigest { } } +impl Randomizable for RpoDigest { + const VALUE_SIZE: usize = DIGEST_BYTES; + + fn from_random_bytes(bytes: &[u8]) -> Option { + let bytes_array: Option<[u8; 32]> = bytes.try_into().ok(); + if let Some(bytes_array) = bytes_array { + Self::try_from(bytes_array).ok() + } else { + None + } + } +} + // CONVERSIONS: FROM RPO DIGEST // ================================================================================================ diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..8700dec --- /dev/null +++ b/src/main.rs @@ -0,0 +1,164 @@ +use clap::Parser; +use miden_crypto::{ + hash::rpo::RpoDigest, + merkle::MerkleError, + Felt, Word, ONE, + {hash::rpo::Rpo256, merkle::TieredSmt}, +}; +use rand_utils::rand_value; +use std::time::Instant; + +#[derive(Parser, Debug)] +#[clap( + name = "Benchmark", + about = "Tiered SMT benchmark", + version, + rename_all = "kebab-case" +)] +pub struct BenchmarkCmd { + /// Size of the tree + #[clap(short = 's', long = "size")] + size: u64, + + /// Run the construction benchmark + #[clap(short = 'c', long = "construction")] + construction: bool, + + /// Run the insertion benchmark + #[clap(short = 'i', long = "insertion")] + insertion: bool, + + /// Run the proof generation benchmark + #[clap(short = 'p', long = "proof-generation")] + proof_generation: bool, +} + +fn main() { + benchmark_tsmt(); +} + +/// Run a benchmark for the Tiered SMT. +pub fn benchmark_tsmt() { + let args = BenchmarkCmd::parse(); + let tree_size = args.size; + + // prepare the `leaves` vector for tree creation + let mut leaves = Vec::new(); + for i in 0..tree_size { + let key = rand_value::(); + let value = [ONE, ONE, ONE, Felt::new(i)]; + leaves.push((key, value)); + } + + let mut tree: Option = None; + + // if the `-c` argument was specified + if args.construction { + tree = Some(construction(leaves.clone(), tree_size).unwrap()); + } + + // if the `-i` argument was specified + if args.insertion { + if let Some(inner_tree) = tree { + tree = Some(insertion(inner_tree, tree_size).unwrap()); + } else { + let inner_tree = TieredSmt::with_leaves(leaves.clone()).unwrap(); + tree = Some(insertion(inner_tree, tree_size).unwrap()); + } + } + + // if the `-p` argument was specified + if args.proof_generation { + if let Some(inner_tree) = tree { + proof_generation(inner_tree, tree_size).unwrap(); + } else { + let inner_tree = TieredSmt::with_leaves(leaves).unwrap(); + proof_generation(inner_tree, tree_size).unwrap(); + } + } +} + +/// Run the construction benchmark for the Tiered SMT. +pub fn construction(leaves: Vec<(RpoDigest, Word)>, size: u64) -> Result { + println!("Running a construction benchmark:"); + let now = Instant::now(); + let tree = TieredSmt::with_leaves(leaves)?; + let elapsed = now.elapsed(); + println!( + "Constructed a TSMT with {} key-value pairs in {:.3} seconds", + size, + elapsed.as_secs_f32(), + ); + + // Count how many nodes end up at each tier + let mut nodes_num_16_32_48 = (0, 0, 0); + + tree.upper_leaf_nodes().for_each(|(index, _)| match index.depth() { + 16 => nodes_num_16_32_48.0 += 1, + 32 => nodes_num_16_32_48.1 += 1, + 48 => nodes_num_16_32_48.2 += 1, + _ => unreachable!(), + }); + + println!("Number of nodes on depth 16: {}", nodes_num_16_32_48.0); + println!("Number of nodes on depth 32: {}", nodes_num_16_32_48.1); + println!("Number of nodes on depth 48: {}", nodes_num_16_32_48.2); + println!("Number of nodes on depth 64: {}\n", tree.bottom_leaves().count()); + + Ok(tree) +} + +/// Run the insertion benchmark for the Tiered SMT. +pub fn insertion(mut tree: TieredSmt, size: u64) -> Result { + println!("Running an insertion benchmark:"); + + let mut insertion_times = Vec::new(); + + for i in 0..20 { + let test_key = Rpo256::hash(&rand_value::().to_be_bytes()); + let test_value = [ONE, ONE, ONE, Felt::new(size + i)]; + + let now = Instant::now(); + tree.insert(test_key, test_value); + let elapsed = now.elapsed(); + insertion_times.push(elapsed.as_secs_f32()); + } + + println!( + "An average insertion time measured by 20 inserts into a TSMT with {} key-value pairs is {:.3} milliseconds\n", + size, + // calculate the average by dividing by 20 and convert to milliseconds by multiplying by + // 1000. As a result, we can only multiply by 50 + insertion_times.iter().sum::() * 50f32, + ); + + Ok(tree) +} + +/// Run the proof generation benchmark for the Tiered SMT. +pub fn proof_generation(mut tree: TieredSmt, size: u64) -> Result<(), MerkleError> { + println!("Running a proof generation benchmark:"); + + let mut insertion_times = Vec::new(); + + for i in 0..20 { + let test_key = Rpo256::hash(&rand_value::().to_be_bytes()); + let test_value = [ONE, ONE, ONE, Felt::new(size + i)]; + tree.insert(test_key, test_value); + + let now = Instant::now(); + let _proof = tree.prove(test_key); + let elapsed = now.elapsed(); + insertion_times.push(elapsed.as_secs_f32()); + } + + println!( + "An average proving time measured by 20 value proofs in a TSMT with {} key-value pairs in {:.3} microseconds", + size, + // calculate the average by dividing by 20 and convert to microseconds by multiplying by + // 1000000. As a result, we can only multiply by 50000 + insertion_times.iter().sum::() * 50000f32, + ); + + Ok(()) +} diff --git a/src/merkle/tiered_smt/mod.rs b/src/merkle/tiered_smt/mod.rs index 2cb8792..d2cc529 100644 --- a/src/merkle/tiered_smt/mod.rs +++ b/src/merkle/tiered_smt/mod.rs @@ -274,6 +274,12 @@ impl TieredSmt { }) } + /// Returns an iterator over upper leaves (i.e., depth = 16, 32, or 48) for this [TieredSmt] + /// where each yielded item is a (node_index, value) tuple. + pub fn upper_leaf_nodes(&self) -> impl Iterator { + self.nodes.upper_leaves() + } + /// Returns an iterator over bottom leaves (i.e., depth = 64) of this [TieredSmt]. /// /// Each yielded item consists of the hash of the leaf and its contents, where contents is