Skip to content

Commit 32fc597

Browse files
benchmark
1 parent afd1d68 commit 32fc597

2 files changed

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

0 commit comments

Comments
 (0)