miden-crypto/src/main.rs
src/main.rsRaw
1use std::time::Instant;
2
3use clap::Parser;
4use miden_crypto::{
5 hash::rpo::{Rpo256, RpoDigest},
6 merkle::{MerkleError, Smt},
7 Felt, Word, EMPTY_WORD, ONE,
8};
9use rand::{prelude::IteratorRandom, thread_rng, Rng};
10use rand_utils::rand_value;
11
12#[derive(Parser, Debug)]
13#[clap(name = "Benchmark", about = "SMT benchmark", version, rename_all = "kebab-case")]
14pub struct BenchmarkCmd {
15 /// Size of the tree
16 #[clap(short = 's', long = "size")]
17 size: usize,
18}
19
20fn main() {
21 benchmark_smt();
22}
23
24/// Run a benchmark for [`Smt`].
25pub fn benchmark_smt() {
26 let args = BenchmarkCmd::parse();
27 let tree_size = args.size;
28
29 // prepare the `leaves` vector for tree creation
30 let mut entries = Vec::new();
31 for i in 0..tree_size {
32 let key = rand_value::<RpoDigest>();
33 let value = [ONE, ONE, ONE, Felt::new(i as u64)];
34 entries.push((key, value));
35 }
36
37 let mut tree = construction(entries.clone(), tree_size).unwrap();
38 insertion(&mut tree).unwrap();
39 batched_insertion(&mut tree).unwrap();
40 batched_update(&mut tree, entries).unwrap();
41 proof_generation(&mut tree).unwrap();
42}
43
44/// Runs the construction benchmark for [`Smt`], returning the constructed tree.
45pub fn construction(entries: Vec<(RpoDigest, Word)>, size: usize) -> Result<Smt, MerkleError> {
46 println!("Running a construction benchmark:");
47 let now = Instant::now();
48 let tree = Smt::with_entries(entries)?;
49 let elapsed = now.elapsed().as_secs_f32();
50
51 println!("Constructed a SMT with {size} key-value pairs in {elapsed:.1} seconds");
52 println!("Number of leaf nodes: {}\n", tree.leaves().count());
53
54 Ok(tree)
55}
56
57/// Runs the insertion benchmark for the [`Smt`].
58pub fn insertion(tree: &mut Smt) -> Result<(), MerkleError> {
59 const NUM_INSERTIONS: usize = 1_000;
60
61 println!("Running an insertion benchmark:");
62
63 let size = tree.num_leaves();
64 let mut insertion_times = Vec::new();
65
66 for i in 0..NUM_INSERTIONS {
67 let test_key = Rpo256::hash(&rand_value::<u64>().to_be_bytes());
68 let test_value = [ONE, ONE, ONE, Felt::new((size + i) as u64)];
69
70 let now = Instant::now();
71 tree.insert(test_key, test_value);
72 let elapsed = now.elapsed();
73 insertion_times.push(elapsed.as_micros());
74 }
75
76 println!(
77 "An average insertion time measured by {NUM_INSERTIONS} inserts into an SMT with {size} leaves is {:.0} μs\n",
78 // calculate the average
79 insertion_times.iter().sum::<u128>() as f64 / (NUM_INSERTIONS as f64),
80 );
81
82 Ok(())
83}
84
85pub fn batched_insertion(tree: &mut Smt) -> Result<(), MerkleError> {
86 const NUM_INSERTIONS: usize = 1_000;
87
88 println!("Running a batched insertion benchmark:");
89
90 let size = tree.num_leaves();
91
92 let new_pairs: Vec<(RpoDigest, Word)> = (0..NUM_INSERTIONS)
93 .map(|i| {
94 let key = Rpo256::hash(&rand_value::<u64>().to_be_bytes());
95 let value = [ONE, ONE, ONE, Felt::new((size + i) as u64)];
96 (key, value)
97 })
98 .collect();
99
100 let now = Instant::now();
101 let mutations = tree.compute_mutations(new_pairs);
102 let compute_elapsed = now.elapsed().as_secs_f64() * 1000_f64; // time in ms
103
104 let now = Instant::now();
105 tree.apply_mutations(mutations)?;
106 let apply_elapsed = now.elapsed().as_secs_f64() * 1000_f64; // time in ms
107
108 println!(
109 "An average insert-batch computation time measured by a {NUM_INSERTIONS}-batch into an SMT with {size} leaves over {:.1} ms is {:.0} μs",
110 compute_elapsed,
111 compute_elapsed * 1000_f64 / NUM_INSERTIONS as f64, // time in μs
112 );
113
114 println!(
115 "An average insert-batch application time measured by a {NUM_INSERTIONS}-batch into an SMT with {size} leaves over {:.1} ms is {:.0} μs",
116 apply_elapsed,
117 apply_elapsed * 1000_f64 / NUM_INSERTIONS as f64, // time in μs
118 );
119
120 println!(
121 "An average batch insertion time measured by a 1k-batch into an SMT with {size} leaves totals to {:.1} ms",
122 (compute_elapsed + apply_elapsed),
123 );
124
125 println!();
126
127 Ok(())
128}
129
130pub fn batched_update(tree: &mut Smt, entries: Vec<(RpoDigest, Word)>) -> Result<(), MerkleError> {
131 const NUM_UPDATES: usize = 1_000;
132 const REMOVAL_PROBABILITY: f64 = 0.2;
133
134 println!("Running a batched update benchmark:");
135
136 let size = tree.num_leaves();
137 let mut rng = thread_rng();
138
139 let new_pairs =
140 entries
141 .into_iter()
142 .choose_multiple(&mut rng, NUM_UPDATES)
143 .into_iter()
144 .map(|(key, _)| {
145 let value = if rng.gen_bool(REMOVAL_PROBABILITY) {
146 EMPTY_WORD
147 } else {
148 [ONE, ONE, ONE, Felt::new(rng.gen())]
149 };
150
151 (key, value)
152 });
153
154 assert_eq!(new_pairs.len(), NUM_UPDATES);
155
156 let now = Instant::now();
157 let mutations = tree.compute_mutations(new_pairs);
158 let compute_elapsed = now.elapsed().as_secs_f64() * 1000_f64; // time in ms
159
160 let now = Instant::now();
161 tree.apply_mutations(mutations)?;
162 let apply_elapsed = now.elapsed().as_secs_f64() * 1000_f64; // time in ms
163
164 println!(
165 "An average update-batch computation time measured by a {NUM_UPDATES}-batch into an SMT with {size} leaves over {:.1} ms is {:.0} μs",
166 compute_elapsed,
167 compute_elapsed * 1000_f64 / NUM_UPDATES as f64, // time in μs
168 );
169
170 println!(
171 "An average update-batch application time measured by a {NUM_UPDATES}-batch into an SMT with {size} leaves over {:.1} ms is {:.0} μs",
172 apply_elapsed,
173 apply_elapsed * 1000_f64 / NUM_UPDATES as f64, // time in μs
174 );
175
176 println!(
177 "An average batch update time measured by a 1k-batch into an SMT with {size} leaves totals to {:.1} ms",
178 (compute_elapsed + apply_elapsed),
179 );
180
181 println!();
182
183 Ok(())
184}
185
186/// Runs the proof generation benchmark for the [`Smt`].
187pub fn proof_generation(tree: &mut Smt) -> Result<(), MerkleError> {
188 const NUM_PROOFS: usize = 100;
189
190 println!("Running a proof generation benchmark:");
191
192 let mut insertion_times = Vec::new();
193 let size = tree.num_leaves();
194
195 for i in 0..NUM_PROOFS {
196 let test_key = Rpo256::hash(&rand_value::<u64>().to_be_bytes());
197 let test_value = [ONE, ONE, ONE, Felt::new((size + i) as u64)];
198 tree.insert(test_key, test_value);
199
200 let now = Instant::now();
201 let _proof = tree.open(&test_key);
202 insertion_times.push(now.elapsed().as_micros());
203 }
204
205 println!(
206 "An average proving time measured by {NUM_PROOFS} value proofs in an SMT with {size} leaves in {:.0} μs",
207 // calculate the average
208 insertion_times.iter().sum::<u128>() as f64 / (NUM_PROOFS as f64),
209 );
210
211 Ok(())
212}