miden-crypto/benches/store.rs
benches/store.rsRaw
1use criterion::{black_box, criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
2use miden_crypto::merkle::{MerkleStore, MerkleTree, NodeIndex, SimpleSmt};
3use miden_crypto::Word;
4use miden_crypto::{hash::rpo::RpoDigest, Felt};
5use rand_utils::{rand_array, rand_value};
6
7/// Since MerkleTree can only be created when a power-of-two number of elements is used, the sample
8/// sizes are limited to that.
9static BATCH_SIZES: [usize; 3] = [2usize.pow(4), 2usize.pow(7), 2usize.pow(10)];
10
11/// Generates a random `RpoDigest`.
12fn random_rpo_digest() -> RpoDigest {
13 rand_array::<Felt, 4>().into()
14}
15
16/// Generates a random `Word`.
17fn random_word() -> Word {
18 rand_array::<Felt, 4>().into()
19}
20
21/// Generates a u64 in `0..range`.
22fn random_index(range: u64) -> u64 {
23 rand_value::<u64>() % range
24}
25
26/// Benchmarks getting an empty leaf from the SMT and MerkleStore backends.
27fn get_empty_leaf_simplesmt(c: &mut Criterion) {
28 let mut group = c.benchmark_group("get_empty_leaf_simplesmt");
29
30 let depth = 63u8;
31 let size = 2u64.pow(depth as u32);
32
33 // both SMT and the store are pre-populated with empty hashes, accessing these values is what is
34 // being benchmarked here, so no values are inserted into the backends
35 let smt = SimpleSmt::new(depth).unwrap();
36 let store = MerkleStore::new();
37 let root = smt.root();
38
39 group.bench_function(BenchmarkId::new("SimpleSmt", depth), |b| {
40 b.iter_batched(
41 || random_index(size),
42 |value| black_box(smt.get_node(&NodeIndex::new(depth, value))),
43 BatchSize::SmallInput,
44 )
45 });
46
47 group.bench_function(BenchmarkId::new("MerkleStore", depth), |b| {
48 b.iter_batched(
49 || random_index(size),
50 |value| black_box(store.get_node(root, NodeIndex::new(depth, value))),
51 BatchSize::SmallInput,
52 )
53 });
54}
55
56/// Benchmarks getting a leaf on Merkle trees and Merkle stores of varying power-of-two sizes.
57fn get_leaf_merkletree(c: &mut Criterion) {
58 let mut group = c.benchmark_group("get_leaf_merkletree");
59
60 let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
61 let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
62
63 for size in BATCH_SIZES {
64 let leaves = &random_data[..size];
65
66 let mtree_leaves: Vec<Word> = leaves.iter().map(|v| v.into()).collect();
67 let mtree = MerkleTree::new(mtree_leaves.clone()).unwrap();
68 let store = MerkleStore::new().with_merkle_tree(mtree_leaves).unwrap();
69 let depth = mtree.depth();
70 let root = mtree.root();
71 let size_u64 = size as u64;
72
73 group.bench_function(BenchmarkId::new("MerkleTree", size), |b| {
74 b.iter_batched(
75 || random_index(size_u64),
76 |value| black_box(mtree.get_node(NodeIndex::new(depth, value))),
77 BatchSize::SmallInput,
78 )
79 });
80
81 group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
82 b.iter_batched(
83 || random_index(size_u64),
84 |value| black_box(store.get_node(root, NodeIndex::new(depth, value))),
85 BatchSize::SmallInput,
86 )
87 });
88 }
89}
90
91/// Benchmarks getting a leaf on SMT and Merkle stores of varying power-of-two sizes.
92fn get_leaf_simplesmt(c: &mut Criterion) {
93 let mut group = c.benchmark_group("get_leaf_simplesmt");
94
95 let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
96 let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
97
98 for size in BATCH_SIZES {
99 let leaves = &random_data[..size];
100
101 let smt_leaves = leaves
102 .iter()
103 .enumerate()
104 .map(|(c, v)| (c.try_into().unwrap(), v.into()))
105 .collect::<Vec<(u64, Word)>>();
106 let smt = SimpleSmt::new(63)
107 .unwrap()
108 .with_leaves(smt_leaves.clone())
109 .unwrap();
110 let store = MerkleStore::new()
111 .with_sparse_merkle_tree(smt_leaves)
112 .unwrap();
113 let depth = smt.depth();
114 let root = smt.root();
115 let size_u64 = size as u64;
116
117 group.bench_function(BenchmarkId::new("SimpleSmt", size), |b| {
118 b.iter_batched(
119 || random_index(size_u64),
120 |value| black_box(smt.get_node(&NodeIndex::new(depth, value))),
121 BatchSize::SmallInput,
122 )
123 });
124
125 group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
126 b.iter_batched(
127 || random_index(size_u64),
128 |value| black_box(store.get_node(root, NodeIndex::new(depth, value))),
129 BatchSize::SmallInput,
130 )
131 });
132 }
133}
134
135/// Benchmarks getting a node at half of the depth of an empty SMT and an empty Merkle store.
136fn get_node_of_empty_simplesmt(c: &mut Criterion) {
137 let mut group = c.benchmark_group("get_node_of_empty_simplesmt");
138
139 let depth = 63u8;
140 let size = 2u64.pow(depth as u32);
141
142 // both SMT and the store are pre-populated with the empty hashes, accessing the internal nodes
143 // of these values is what is being benchmarked here, so no values are inserted into the
144 // backends.
145 let smt = SimpleSmt::new(depth).unwrap();
146 let store = MerkleStore::new();
147 let root = smt.root();
148 let half_depth = depth / 2;
149
150 group.bench_function(BenchmarkId::new("SimpleSmt", depth), |b| {
151 b.iter_batched(
152 || random_index(size),
153 |value| black_box(smt.get_node(&NodeIndex::new(half_depth, value))),
154 BatchSize::SmallInput,
155 )
156 });
157
158 group.bench_function(BenchmarkId::new("MerkleStore", depth), |b| {
159 b.iter_batched(
160 || random_index(size),
161 |value| black_box(store.get_node(root, NodeIndex::new(half_depth, value))),
162 BatchSize::SmallInput,
163 )
164 });
165}
166
167/// Benchmarks getting a node at half of the depth of a Merkle tree and Merkle store of varying
168/// power-of-two sizes.
169fn get_node_merkletree(c: &mut Criterion) {
170 let mut group = c.benchmark_group("get_node_merkletree");
171
172 let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
173 let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
174
175 for size in BATCH_SIZES {
176 let leaves = &random_data[..size];
177
178 let mtree_leaves: Vec<Word> = leaves.iter().map(|v| v.into()).collect();
179 let mtree = MerkleTree::new(mtree_leaves.clone()).unwrap();
180 let store = MerkleStore::new().with_merkle_tree(mtree_leaves).unwrap();
181 let half_depth = mtree.depth() / 2;
182 let root = mtree.root();
183 let size_u64 = size as u64;
184
185 group.bench_function(BenchmarkId::new("MerkleTree", size), |b| {
186 b.iter_batched(
187 || random_index(size_u64),
188 |value| black_box(mtree.get_node(NodeIndex::new(half_depth, value))),
189 BatchSize::SmallInput,
190 )
191 });
192
193 group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
194 b.iter_batched(
195 || random_index(size_u64),
196 |value| black_box(store.get_node(root, NodeIndex::new(half_depth, value))),
197 BatchSize::SmallInput,
198 )
199 });
200 }
201}
202
203/// Benchmarks getting a node at half the depth on SMT and Merkle stores of varying power-of-two
204/// sizes.
205fn get_node_simplesmt(c: &mut Criterion) {
206 let mut group = c.benchmark_group("get_node_simplesmt");
207
208 let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
209 let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
210
211 for size in BATCH_SIZES {
212 let leaves = &random_data[..size];
213
214 let smt_leaves = leaves
215 .iter()
216 .enumerate()
217 .map(|(c, v)| (c.try_into().unwrap(), v.into()))
218 .collect::<Vec<(u64, Word)>>();
219 let smt = SimpleSmt::new(63)
220 .unwrap()
221 .with_leaves(smt_leaves.clone())
222 .unwrap();
223 let store = MerkleStore::new()
224 .with_sparse_merkle_tree(smt_leaves)
225 .unwrap();
226 let root = smt.root();
227 let size_u64 = size as u64;
228 let half_depth = smt.depth() / 2;
229
230 group.bench_function(BenchmarkId::new("SimpleSmt", size), |b| {
231 b.iter_batched(
232 || random_index(size_u64),
233 |value| black_box(smt.get_node(&NodeIndex::new(half_depth, value))),
234 BatchSize::SmallInput,
235 )
236 });
237
238 group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
239 b.iter_batched(
240 || random_index(size_u64),
241 |value| black_box(store.get_node(root, NodeIndex::new(half_depth, value))),
242 BatchSize::SmallInput,
243 )
244 });
245 }
246}
247
248/// Benchmarks getting a path of a leaf on the Merkle tree and Merkle store backends.
249fn get_leaf_path_merkletree(c: &mut Criterion) {
250 let mut group = c.benchmark_group("get_leaf_path_merkletree");
251
252 let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
253 let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
254
255 for size in BATCH_SIZES {
256 let leaves = &random_data[..size];
257
258 let mtree_leaves: Vec<Word> = leaves.iter().map(|v| v.into()).collect();
259 let mtree = MerkleTree::new(mtree_leaves.clone()).unwrap();
260 let store = MerkleStore::new().with_merkle_tree(mtree_leaves).unwrap();
261 let depth = mtree.depth();
262 let root = mtree.root();
263 let size_u64 = size as u64;
264
265 group.bench_function(BenchmarkId::new("MerkleTree", size), |b| {
266 b.iter_batched(
267 || random_index(size_u64),
268 |value| black_box(mtree.get_path(NodeIndex::new(depth, value))),
269 BatchSize::SmallInput,
270 )
271 });
272
273 group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
274 b.iter_batched(
275 || random_index(size_u64),
276 |value| black_box(store.get_path(root, NodeIndex::new(depth, value))),
277 BatchSize::SmallInput,
278 )
279 });
280 }
281}
282
283/// Benchmarks getting a path of a leaf on the SMT and Merkle store backends.
284fn get_leaf_path_simplesmt(c: &mut Criterion) {
285 let mut group = c.benchmark_group("get_leaf_path_simplesmt");
286
287 let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
288 let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
289
290 for size in BATCH_SIZES {
291 let leaves = &random_data[..size];
292
293 let smt_leaves = leaves
294 .iter()
295 .enumerate()
296 .map(|(c, v)| (c.try_into().unwrap(), v.into()))
297 .collect::<Vec<(u64, Word)>>();
298 let smt = SimpleSmt::new(63)
299 .unwrap()
300 .with_leaves(smt_leaves.clone())
301 .unwrap();
302 let store = MerkleStore::new()
303 .with_sparse_merkle_tree(smt_leaves)
304 .unwrap();
305 let depth = smt.depth();
306 let root = smt.root();
307 let size_u64 = size as u64;
308
309 group.bench_function(BenchmarkId::new("SimpleSmt", size), |b| {
310 b.iter_batched(
311 || random_index(size_u64),
312 |value| black_box(smt.get_path(NodeIndex::new(depth, value))),
313 BatchSize::SmallInput,
314 )
315 });
316
317 group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
318 b.iter_batched(
319 || random_index(size_u64),
320 |value| black_box(store.get_path(root, NodeIndex::new(depth, value))),
321 BatchSize::SmallInput,
322 )
323 });
324 }
325}
326
327/// Benchmarks creation of the different storage backends
328fn new(c: &mut Criterion) {
329 let mut group = c.benchmark_group("new");
330
331 let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
332 let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
333
334 for size in BATCH_SIZES {
335 let leaves = &random_data[..size];
336
337 // MerkleTree constructor is optimized to work with vectors. Create a new copy of the data
338 // and pass it to the benchmark function
339 group.bench_function(BenchmarkId::new("MerkleTree::new", size), |b| {
340 b.iter_batched(
341 || leaves.iter().map(|v| v.into()).collect::<Vec<Word>>(),
342 |l| black_box(MerkleTree::new(l)),
343 BatchSize::SmallInput,
344 )
345 });
346
347 // This could be done with `bench_with_input`, however to remove variables while comparing
348 // with MerkleTree it is using `iter_batched`
349 group.bench_function(
350 BenchmarkId::new("MerkleStore::with_merkle_tree", size),
351 |b| {
352 b.iter_batched(
353 || leaves.iter().map(|v| v.into()).collect::<Vec<Word>>(),
354 |l| black_box(MerkleStore::new().with_merkle_tree(l)),
355 BatchSize::SmallInput,
356 )
357 },
358 );
359
360 group.bench_function(BenchmarkId::new("SimpleSmt::new", size), |b| {
361 b.iter_batched(
362 || {
363 leaves
364 .iter()
365 .enumerate()
366 .map(|(c, v)| (c.try_into().unwrap(), v.into()))
367 .collect::<Vec<(u64, Word)>>()
368 },
369 |l| black_box(SimpleSmt::new(63).unwrap().with_leaves(l)),
370 BatchSize::SmallInput,
371 )
372 });
373
374 group.bench_function(
375 BenchmarkId::new("MerkleStore::with_sparse_merkle_tree", size),
376 |b| {
377 b.iter_batched(
378 || {
379 leaves
380 .iter()
381 .enumerate()
382 .map(|(c, v)| (c.try_into().unwrap(), v.into()))
383 .collect::<Vec<(u64, Word)>>()
384 },
385 |l| black_box(MerkleStore::new().with_sparse_merkle_tree(l)),
386 BatchSize::SmallInput,
387 )
388 },
389 );
390 }
391}
392
393/// Benchmarks updating a leaf on MerkleTree and MerkleStore backends.
394fn update_leaf_merkletree(c: &mut Criterion) {
395 let mut group = c.benchmark_group("update_leaf_merkletree");
396
397 let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
398 let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
399
400 for size in BATCH_SIZES {
401 let leaves = &random_data[..size];
402
403 let mtree_leaves: Vec<Word> = leaves.iter().map(|v| v.into()).collect();
404 let mut mtree = MerkleTree::new(mtree_leaves.clone()).unwrap();
405 let mut store = MerkleStore::new().with_merkle_tree(mtree_leaves).unwrap();
406 let depth = mtree.depth();
407 let root = mtree.root();
408 let size_u64 = size as u64;
409
410 group.bench_function(BenchmarkId::new("MerkleTree", size), |b| {
411 b.iter_batched(
412 || (random_index(size_u64), random_word()),
413 |(index, value)| black_box(mtree.update_leaf(index, value)),
414 BatchSize::SmallInput,
415 )
416 });
417
418 let mut store_root = root;
419 group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
420 b.iter_batched(
421 || (random_index(size_u64), random_word()),
422 |(index, value)| {
423 // The MerkleTree automatically updates its internal root, the Store maintains
424 // the old root and adds the new one. Here we update the root to have a fair
425 // comparison
426 store_root = store
427 .set_node(root, NodeIndex::new(depth, index), value)
428 .unwrap()
429 .root;
430 black_box(store_root)
431 },
432 BatchSize::SmallInput,
433 )
434 });
435 }
436}
437
438/// Benchmarks updating a leaf on SMT and MerkleStore backends.
439fn update_leaf_simplesmt(c: &mut Criterion) {
440 let mut group = c.benchmark_group("update_leaf_simplesmt");
441
442 let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
443 let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
444
445 for size in BATCH_SIZES {
446 let leaves = &random_data[..size];
447
448 let smt_leaves = leaves
449 .iter()
450 .enumerate()
451 .map(|(c, v)| (c.try_into().unwrap(), v.into()))
452 .collect::<Vec<(u64, Word)>>();
453 let mut smt = SimpleSmt::new(63)
454 .unwrap()
455 .with_leaves(smt_leaves.clone())
456 .unwrap();
457 let mut store = MerkleStore::new()
458 .with_sparse_merkle_tree(smt_leaves)
459 .unwrap();
460 let depth = smt.depth();
461 let root = smt.root();
462 let size_u64 = size as u64;
463
464 group.bench_function(BenchmarkId::new("SimpleSMT", size), |b| {
465 b.iter_batched(
466 || (random_index(size_u64), random_word()),
467 |(index, value)| black_box(smt.update_leaf(index, value)),
468 BatchSize::SmallInput,
469 )
470 });
471
472 let mut store_root = root;
473 group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
474 b.iter_batched(
475 || (random_index(size_u64), random_word()),
476 |(index, value)| {
477 // The MerkleTree automatically updates its internal root, the Store maintains
478 // the old root and adds the new one. Here we update the root to have a fair
479 // comparison
480 store_root = store
481 .set_node(root, NodeIndex::new(depth, index), value)
482 .unwrap()
483 .root;
484 black_box(store_root)
485 },
486 BatchSize::SmallInput,
487 )
488 });
489 }
490}
491
492criterion_group!(
493 store_group,
494 get_empty_leaf_simplesmt,
495 get_leaf_merkletree,
496 get_leaf_path_merkletree,
497 get_leaf_path_simplesmt,
498 get_leaf_simplesmt,
499 get_node_merkletree,
500 get_node_of_empty_simplesmt,
501 get_node_simplesmt,
502 new,
503 update_leaf_merkletree,
504 update_leaf_simplesmt,
505);
506criterion_main!(store_group);