Skip to content

Commit b6cd317

Browse files
chained-growth benchmark
1 parent c6a805b commit b6cd317

2 files changed

Lines changed: 225 additions & 1 deletion

File tree

storage/src/qmdb/benches/bench.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22
33
use criterion::criterion_main;
44

5+
mod chained_growth;
56
mod common;
67
mod generate;
78
mod init;
89
mod merkleize;
910

10-
criterion_main!(generate::benches, init::benches, merkleize::benches);
11+
criterion_main!(
12+
chained_growth::benches,
13+
generate::benches,
14+
init::benches,
15+
merkleize::benches
16+
);
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
//! Benchmark for chained-growth merkleization against Current QMDB variants.
2+
//!
3+
//! Setup (untimed): seed `NUM_KEYS` keys, then grow a chain of `PREBUILT_CHAIN` batches applying
4+
//! each parent while the child is still alive.
5+
//!
6+
//! Timed: do `batches` more merkleize + apply iterations on top of the pre-built chain, with a
7+
//! single random update per batch so each overlay covers a tiny fraction of chunks. Finally
8+
//! `prune` with the last child still alive, so any chain-collapse work the implementation defers
9+
//! to prune is captured too.
10+
11+
use crate::common::{dispatch_arm, make_fixed_value, Digest};
12+
use commonware_cryptography::{Hasher, Sha256};
13+
use commonware_runtime::{
14+
benchmarks::{context, tokio},
15+
tokio::{Config, Context},
16+
};
17+
use commonware_storage::{
18+
merkle::{self, mmb::Family as Mmb},
19+
qmdb::{
20+
any::traits::{DbAny, MerkleizedBatch as _, UnmerkleizedBatch as _},
21+
current::{ordered::fixed::Db as OCFixed, unordered::fixed::Db as UCFixed},
22+
},
23+
translator::EightCap,
24+
};
25+
use criterion::{criterion_group, Criterion};
26+
use rand::{rngs::StdRng, RngCore, SeedableRng};
27+
use std::{
28+
hint::black_box,
29+
time::{Duration, Instant},
30+
};
31+
32+
const SMALL_CHUNK_SIZE: usize = 32;
33+
const LARGE_CHUNK_SIZE: usize = 256;
34+
35+
type CurUFix32Mmb = UCFixed<Mmb, Context, Digest, Digest, Sha256, EightCap, SMALL_CHUNK_SIZE>;
36+
type CurOFix32Mmb = OCFixed<Mmb, Context, Digest, Digest, Sha256, EightCap, SMALL_CHUNK_SIZE>;
37+
type CurUFix256Mmb = UCFixed<Mmb, Context, Digest, Digest, Sha256, EightCap, LARGE_CHUNK_SIZE>;
38+
type CurOFix256Mmb = OCFixed<Mmb, Context, Digest, Digest, Sha256, EightCap, LARGE_CHUNK_SIZE>;
39+
40+
/// Number of pre-populated keys in the seeded database.
41+
const NUM_KEYS: u64 = 1_000_000;
42+
43+
/// Random updates per batch. One update means each batch's chunk overlay covers ~1 / num_chunks
44+
/// of the bitmap, forcing chain reads to walk deep before finding a matching layer.
45+
const UPDATES_PER_BATCH: u64 = 1;
46+
47+
/// Number of batches grown during the untimed seed phase, producing a Db::status chain of this
48+
/// depth that subsequent reads must walk through.
49+
const PREBUILT_CHAIN: u64 = 10_000;
50+
51+
/// Number of additional batches to grow during the timed region.
52+
const GROW_COUNTS: [u64; 1] = [100];
53+
54+
#[derive(Debug, Clone, Copy)]
55+
enum CurrentVariant {
56+
UnorderedFixed32,
57+
OrderedFixed32,
58+
UnorderedFixed256,
59+
OrderedFixed256,
60+
}
61+
62+
impl CurrentVariant {
63+
const fn name(self) -> &'static str {
64+
match self {
65+
Self::UnorderedFixed32 => "current::unordered::fixed::mmb chunk=32",
66+
Self::OrderedFixed32 => "current::ordered::fixed::mmb chunk=32",
67+
Self::UnorderedFixed256 => "current::unordered::fixed::mmb chunk=256",
68+
Self::OrderedFixed256 => "current::ordered::fixed::mmb chunk=256",
69+
}
70+
}
71+
}
72+
73+
const CURRENT_VARIANTS: [CurrentVariant; 4] = [
74+
CurrentVariant::UnorderedFixed32,
75+
CurrentVariant::OrderedFixed32,
76+
CurrentVariant::UnorderedFixed256,
77+
CurrentVariant::OrderedFixed256,
78+
];
79+
80+
/// Construct a Current database for `$variant`, bind it as `$db`, and execute `$body`.
81+
macro_rules! with_current_db {
82+
($ctx:expr, $variant:expr, |mut $db:ident| $body:expr) => {{
83+
match $variant {
84+
CurrentVariant::UnorderedFixed32 => {
85+
dispatch_arm!($ctx, $db, $body, CurUFix32Mmb, cur_fix_cfg)
86+
}
87+
CurrentVariant::OrderedFixed32 => {
88+
dispatch_arm!($ctx, $db, $body, CurOFix32Mmb, cur_fix_cfg)
89+
}
90+
CurrentVariant::UnorderedFixed256 => {
91+
dispatch_arm!($ctx, $db, $body, CurUFix256Mmb, cur_fix_cfg)
92+
}
93+
CurrentVariant::OrderedFixed256 => {
94+
dispatch_arm!($ctx, $db, $body, CurOFix256Mmb, cur_fix_cfg)
95+
}
96+
}
97+
}};
98+
}
99+
100+
/// Pre-populate the database with `num_keys` unique keys, then commit and sync so that
101+
/// seed-phase buffered writes are flushed before the timer starts.
102+
async fn seed_db<F: merkle::Family, C: DbAny<F, Key = Digest, Value = Digest>>(
103+
db: &mut C,
104+
num_keys: u64,
105+
) {
106+
let mut rng = StdRng::seed_from_u64(42);
107+
let mut batch = db.new_batch();
108+
for i in 0u64..num_keys {
109+
let k = Sha256::hash(&i.to_be_bytes());
110+
batch = batch.write(k, Some(make_fixed_value(&mut rng)));
111+
}
112+
let merkleized = batch.merkleize(db, None).await.unwrap();
113+
db.apply_batch(merkleized).await.unwrap();
114+
db.commit().await.unwrap();
115+
db.sync().await.unwrap();
116+
}
117+
118+
/// Write `num_updates` random key updates into a batch.
119+
fn write_random_updates<
120+
B: commonware_storage::qmdb::any::traits::UnmerkleizedBatch<Db, K = Digest, V = Digest>,
121+
Db: ?Sized,
122+
>(
123+
mut batch: B,
124+
num_updates: u64,
125+
num_keys: u64,
126+
rng: &mut StdRng,
127+
) -> B {
128+
for _ in 0..num_updates {
129+
let idx = rng.next_u64() % num_keys;
130+
let k = Sha256::hash(&idx.to_be_bytes());
131+
batch = batch.write(k, Some(make_fixed_value(rng)));
132+
}
133+
batch
134+
}
135+
136+
/// Run a chained-growth sequence with a pre-built deep chain.
137+
///
138+
/// `fork_child` bridges the generic trait and the concrete `new_batch` method on a merkleized
139+
/// batch.
140+
async fn run_chained_growth<
141+
F: merkle::Family,
142+
C: DbAny<F, Key = Digest, Value = Digest>,
143+
Fork: Fn(&C::Merkleized) -> C::Batch,
144+
>(
145+
mut db: C,
146+
grow: u64,
147+
fork_child: Fork,
148+
) -> Duration {
149+
seed_db(&mut db, NUM_KEYS).await;
150+
let mut rng = StdRng::seed_from_u64(99);
151+
152+
// Pre-build a deep chain (untimed).
153+
let initial = write_random_updates(db.new_batch(), UPDATES_PER_BATCH, NUM_KEYS, &mut rng);
154+
let mut parent = initial.merkleize(&db, None).await.unwrap();
155+
for _ in 0..PREBUILT_CHAIN {
156+
let child_batch =
157+
write_random_updates(fork_child(&parent), UPDATES_PER_BATCH, NUM_KEYS, &mut rng);
158+
let child = child_batch.merkleize(&db, None).await.unwrap();
159+
db.apply_batch(parent).await.unwrap();
160+
parent = child;
161+
}
162+
163+
// Flush buffered data so the timed region doesn't inherit setup fsync cost.
164+
db.commit().await.unwrap();
165+
db.sync().await.unwrap();
166+
167+
// Timed: grow more batches on top of the pre-built chain, then prune while the final child is
168+
// still alive so any chain-collapse work the implementation defers to prune is also captured.
169+
let start = Instant::now();
170+
for _ in 0..grow {
171+
let child_batch =
172+
write_random_updates(fork_child(&parent), UPDATES_PER_BATCH, NUM_KEYS, &mut rng);
173+
let child = child_batch.merkleize(&db, None).await.unwrap();
174+
black_box(child.root());
175+
db.apply_batch(parent).await.unwrap();
176+
parent = child;
177+
}
178+
let prune_loc = db.inactivity_floor_loc().await;
179+
db.prune(prune_loc).await.unwrap();
180+
db.apply_batch(parent).await.unwrap();
181+
let total = start.elapsed();
182+
183+
db.destroy().await.unwrap();
184+
total
185+
}
186+
187+
fn bench_chained_growth(c: &mut Criterion) {
188+
let runner = tokio::Runner::new(Config::default());
189+
for batches in GROW_COUNTS {
190+
for &variant in &CURRENT_VARIANTS {
191+
c.bench_function(
192+
&format!(
193+
"{}/variant={} batches={batches}",
194+
module_path!(),
195+
variant.name()
196+
),
197+
|b| {
198+
b.to_async(&runner).iter_custom(|iters| async move {
199+
let ctx = context::get::<Context>();
200+
let mut total = Duration::ZERO;
201+
for _ in 0..iters {
202+
with_current_db!(ctx.clone(), variant, |mut db| {
203+
total += run_chained_growth(db, batches, |p| p.new_batch()).await;
204+
});
205+
}
206+
total
207+
});
208+
},
209+
);
210+
}
211+
}
212+
}
213+
214+
criterion_group! {
215+
name = benches;
216+
config = Criterion::default().sample_size(10);
217+
targets = bench_chained_growth
218+
}

0 commit comments

Comments
 (0)