Skip to content

Commit dca4b7e

Browse files
chained-growth benchmark
1 parent c6a805b commit dca4b7e

2 files changed

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

0 commit comments

Comments
 (0)