#![forbid(unsafe_code)]
use std::fmt::Write as _;
use std::fs;
use std::hint::black_box;
use std::path::PathBuf;
use std::sync::{Arc, Barrier};
use std::time::{Duration, Instant};
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use obj::{Db, Document, Id, IndexSpec};
use rand::seq::SliceRandom;
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
use serde::{Deserialize, Serialize};
mod common;
use common::{bench_enforce_enabled, fresh_db, reopen};
const POPULATE_COUNT: usize = 100_000;
const POPULATE_BATCH: usize = 1_000;
const SMALL_POPULATE_COUNT: usize = 1_000;
const PAYLOAD_BYTES: usize = 480;
const CONCURRENT_THREADS: usize = 8;
const GATE_SAMPLES: usize = 11;
const ORDER_OF_MAGNITUDE_NS: u128 = 10;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PerfDoc {
customer_id: u64,
payload: Vec<u8>,
}
impl Document for PerfDoc {
const COLLECTION: &'static str = "perf_docs";
const VERSION: u32 = 1;
fn indexes() -> Vec<IndexSpec> {
vec![IndexSpec::unique("by_customer_id", "customer_id").expect("unique spec")]
}
}
#[derive(Debug, Clone)]
struct PerfRow {
operation: &'static str,
measured_us: f64,
target_display: &'static str,
target_ns: Option<u128>,
notes: &'static str,
}
struct PopulatedDb {
customer_ids: Vec<u64>,
ids: Vec<Id>,
}
fn populate(db: &Db, count: usize) -> PopulatedDb {
let mut rng = ChaCha8Rng::seed_from_u64(0xC0DE_FACE_1234_5678);
let mut customer_ids: Vec<u64> = (1..=(count as u64)).collect();
customer_ids.shuffle(&mut rng);
let mut ids: Vec<Id> = Vec::with_capacity(count);
let mut inserted = 0usize;
while inserted < count {
let batch_end = (inserted + POPULATE_BATCH).min(count);
let slice: Vec<u64> = customer_ids[inserted..batch_end].to_vec();
let batch_ids: Vec<Id> = db
.transaction(|tx| {
let coll = tx.collection::<PerfDoc>()?;
let mut out = Vec::with_capacity(slice.len());
for cid in &slice {
let mut payload = vec![0u8; PAYLOAD_BYTES];
rng.fill(&mut payload[..]);
let id = coll.insert(PerfDoc {
customer_id: *cid,
payload,
})?;
out.push(id);
}
Ok(out)
})
.expect("populate batch");
ids.extend(batch_ids);
inserted = batch_end;
}
PopulatedDb { customer_ids, ids }
}
fn measure_median_ns<F: FnMut()>(mut op: F) -> u128 {
let mut samples: Vec<u128> = Vec::with_capacity(GATE_SAMPLES);
for _ in 0..GATE_SAMPLES {
let start = Instant::now();
op();
samples.push(start.elapsed().as_nanos());
}
samples.sort_unstable();
samples[GATE_SAMPLES / 2]
}
fn measure_median_ns_with_setup<S, T, F>(mut setup: S, mut op: F) -> u128
where
S: FnMut() -> T,
F: FnMut(T),
{
let mut samples: Vec<u128> = Vec::with_capacity(GATE_SAMPLES);
for _ in 0..GATE_SAMPLES {
let input = setup();
let start = Instant::now();
op(input);
samples.push(start.elapsed().as_nanos());
}
samples.sort_unstable();
samples[GATE_SAMPLES / 2]
}
fn row_point_read_warm(c: &mut Criterion) -> PerfRow {
let bench_db = fresh_db("perf_warm");
let populated = populate(&bench_db.db, SMALL_POPULATE_COUNT);
let hot_id = populated.ids[0];
let mut group = c.benchmark_group("perf_table");
group.sample_size(50);
group.warm_up_time(Duration::from_millis(500));
group.measurement_time(Duration::from_secs(2));
group.bench_function("point_read_warm", |b| {
b.iter(|| {
let got: Option<PerfDoc> = bench_db
.db
.read_transaction(|tx| tx.collection::<PerfDoc>()?.get(hot_id))
.expect("read");
black_box(got);
});
});
group.finish();
let median_ns = measure_median_ns(|| {
let got: Option<PerfDoc> = bench_db
.db
.read_transaction(|tx| tx.collection::<PerfDoc>()?.get(hot_id))
.expect("read");
black_box(got);
});
PerfRow {
operation: "Point read, warm cache",
measured_us: ns_to_us(median_ns),
target_display: "~100 µs",
target_ns: Some(100_000),
notes: "Memory-mapped, zero-copy",
}
}
fn row_point_read_cold(c: &mut Criterion) -> PerfRow {
let bench_db = fresh_db("perf_cold");
let populated = populate(&bench_db.db, SMALL_POPULATE_COUNT);
let hot_id = populated.ids[0];
let path = bench_db.path.clone();
let tempdir = bench_db.tempdir;
drop(bench_db.db);
let mut group = c.benchmark_group("perf_table");
group.sample_size(20);
group.warm_up_time(Duration::from_millis(500));
group.measurement_time(Duration::from_secs(3));
group.bench_function("point_read_cold", |b| {
b.iter_batched(
|| Db::open(&path).expect("re-open cold"),
|db| {
let got: Option<PerfDoc> = db
.read_transaction(|tx| tx.collection::<PerfDoc>()?.get(hot_id))
.expect("read");
black_box(got);
},
BatchSize::PerIteration,
);
});
group.finish();
let median_ns = measure_median_ns_with_setup(
|| Db::open(&path).expect("re-open cold"),
|db| {
let got: Option<PerfDoc> = db
.read_transaction(|tx| tx.collection::<PerfDoc>()?.get(hot_id))
.expect("read");
black_box(got);
},
);
drop(tempdir);
PerfRow {
operation: "Point read, cold page",
measured_us: ns_to_us(median_ns),
target_display: "~100 µs",
target_ns: Some(100_000),
notes: "Single page from NVMe",
}
}
fn row_single_insert(c: &mut Criterion) -> PerfRow {
let bench_db = fresh_db("perf_insert1");
let mut rng = ChaCha8Rng::seed_from_u64(0xA5A5_DECA_5555_1111);
let mut seq = 0u64;
let mut group = c.benchmark_group("perf_table");
group.sample_size(30);
group.warm_up_time(Duration::from_millis(500));
group.measurement_time(Duration::from_secs(3));
group.bench_function("single_insert", |b| {
b.iter(|| {
seq += 1;
let mut payload = vec![0u8; PAYLOAD_BYTES];
rng.fill(&mut payload[..]);
bench_db
.db
.transaction(|tx| {
tx.collection::<PerfDoc>()?.insert(PerfDoc {
customer_id: seq,
payload: payload.clone(),
})
})
.expect("insert");
});
});
group.finish();
let median_ns = measure_median_ns(|| {
seq += 1;
let mut payload = vec![0u8; PAYLOAD_BYTES];
rng.fill(&mut payload[..]);
bench_db
.db
.transaction(|tx| {
tx.collection::<PerfDoc>()?.insert(PerfDoc {
customer_id: seq,
payload: payload.clone(),
})
})
.expect("insert");
});
PerfRow {
operation: "Single insert",
measured_us: ns_to_us(median_ns),
target_display: "~750 µs",
target_ns: Some(750_000),
notes: "WAL append",
}
}
fn row_batch_insert_1k(c: &mut Criterion) -> PerfRow {
let mut group = c.benchmark_group("perf_table");
group.sample_size(10);
group.warm_up_time(Duration::from_millis(500));
group.measurement_time(Duration::from_secs(5));
group.bench_function("batch_insert_1k", |b| {
b.iter_batched(
|| (fresh_db("perf_batch1k"), build_payloads(1_000, 0xB1B1)),
|(bench_db, batch)| {
bench_db
.db
.transaction(|tx| {
let coll = tx.collection::<PerfDoc>()?;
for doc in batch {
let _ = coll.insert(doc)?;
}
Ok(())
})
.expect("batch insert");
},
BatchSize::PerIteration,
);
});
group.finish();
let median_ns = measure_median_ns_with_setup(
|| (fresh_db("perf_batch1k"), build_payloads(1_000, 0xB1B2)),
|(bench_db, batch)| {
bench_db
.db
.transaction(|tx| {
let coll = tx.collection::<PerfDoc>()?;
for doc in batch {
let _ = coll.insert(doc)?;
}
Ok(())
})
.expect("batch insert");
},
);
PerfRow {
operation: "Batch insert, 1 000 docs, one tx",
measured_us: ns_to_us(median_ns),
target_display: "~110 ms",
target_ns: Some(110_000_000),
notes: "Single transaction",
}
}
fn row_batch_insert_10k(c: &mut Criterion) -> PerfRow {
let mut group = c.benchmark_group("perf_table");
group.sample_size(10);
group.warm_up_time(Duration::from_secs(1));
group.measurement_time(Duration::from_secs(15));
group.bench_function("batch_insert_10k", |b| {
b.iter_batched(
|| (fresh_db("perf_batch10k"), build_payloads(10_000, 0xB10B)),
|(bench_db, batch)| {
bench_db
.db
.transaction(|tx| {
let coll = tx.collection::<PerfDoc>()?;
for doc in batch {
let _ = coll.insert(doc)?;
}
Ok(())
})
.expect("batch insert");
},
BatchSize::PerIteration,
);
});
group.finish();
let median_ns = measure_median_ns_with_setup_n(
3,
|| (fresh_db("perf_batch10k"), build_payloads(10_000, 0xB10C)),
|(bench_db, batch)| {
bench_db
.db
.transaction(|tx| {
let coll = tx.collection::<PerfDoc>()?;
for doc in batch {
let _ = coll.insert(doc)?;
}
Ok(())
})
.expect("batch insert");
},
);
PerfRow {
operation: "Batch insert, 10 000 docs, one tx",
measured_us: ns_to_us(median_ns),
target_display: "~1.3 s",
target_ns: Some(1_300_000_000),
notes: "Single transaction",
}
}
fn row_index_lookup(c: &mut Criterion) -> PerfRow {
let bench_db = fresh_db("perf_idx");
let populated = populate(&bench_db.db, POPULATE_COUNT);
let customer_ids = populated.customer_ids;
let mut rng = ChaCha8Rng::seed_from_u64(0x1DEC_DEAD_C0DE_4242);
let mut group = c.benchmark_group("perf_table");
group.sample_size(50);
group.warm_up_time(Duration::from_secs(1));
group.measurement_time(Duration::from_secs(3));
group.bench_function("index_lookup_100k", |b| {
b.iter(|| {
let idx = rng.random_range(0..customer_ids.len());
let cid = customer_ids[idx];
let got: Option<PerfDoc> = bench_db
.db
.find_unique::<PerfDoc>("by_customer_id", cid)
.expect("find_unique");
black_box(got);
});
});
group.finish();
let median_ns = measure_median_ns(|| {
let idx = rng.random_range(0..customer_ids.len());
let cid = customer_ids[idx];
let got: Option<PerfDoc> = bench_db
.db
.find_unique::<PerfDoc>("by_customer_id", cid)
.expect("find_unique");
black_box(got);
});
PerfRow {
operation: "Index lookup",
measured_us: ns_to_us(median_ns),
target_display: "~250 µs",
target_ns: Some(250_000),
notes: "B-tree traversal",
}
}
fn row_collection_scan(c: &mut Criterion) -> PerfRow {
let bench_db = fresh_db("perf_scan");
let _populated = populate(&bench_db.db, POPULATE_COUNT);
let bench_db = reopen(bench_db);
let mut group = c.benchmark_group("perf_table");
group.sample_size(10);
group.warm_up_time(Duration::from_secs(2));
group.measurement_time(Duration::from_secs(15));
group.bench_function("collection_scan_100k", |b| {
b.iter(|| {
let docs: Vec<PerfDoc> = bench_db.db.all::<PerfDoc>().expect("all");
assert_eq!(docs.len(), POPULATE_COUNT, "scan must return every doc");
black_box(docs);
});
});
group.finish();
let median_ns = measure_median_ns(|| {
let docs: Vec<PerfDoc> = bench_db.db.all::<PerfDoc>().expect("all");
debug_assert_eq!(docs.len(), POPULATE_COUNT, "gate scan length");
black_box(docs);
});
PerfRow {
operation: "Collection scan, 100 000 docs",
measured_us: ns_to_us(median_ns),
target_display: "~750 ms",
target_ns: Some(750_000_000),
notes: "Sequential read",
}
}
fn row_concurrent_readers(c: &mut Criterion) -> PerfRow {
let bench_db = fresh_db("perf_conc");
let _populated = populate(&bench_db.db, POPULATE_COUNT);
let bench_db = reopen(bench_db);
let db = Arc::new(bench_db.db);
let mut group = c.benchmark_group("perf_table");
group.sample_size(10);
group.warm_up_time(Duration::from_secs(1));
group.measurement_time(Duration::from_secs(3));
group.bench_function("concurrent_readers_8", |b| {
b.iter(|| {
let docs: Vec<PerfDoc> = db.all::<PerfDoc>().expect("all");
black_box(docs);
});
});
group.finish();
let (per_thread_ns, ratio_pct) = measure_concurrent(&db);
drop(db);
drop(bench_db.tempdir);
let pass = if ratio_pct >= 80 { "PASS" } else { "INFO" };
println!(
"[perf_table] concurrent_readers: scaling ratio = {}.{:02}× (target ≥ 0.80) [{pass}]",
ratio_pct / 100,
ratio_pct % 100,
);
PerfRow {
operation: "Concurrent readers, 8 threads",
measured_us: ns_to_us(per_thread_ns),
target_display: "linear scaling",
target_ns: None,
notes: "No reader contention",
}
}
fn measure_concurrent(db: &Arc<Db>) -> (u128, u128) {
let one = measure_concurrent_pass(db, 1);
let eight = measure_concurrent_pass(db, CONCURRENT_THREADS);
let ratio_hundredths = if one == 0 {
0
} else {
(eight * 100) / (one * (CONCURRENT_THREADS as u128))
};
(eight, ratio_hundredths)
}
fn measure_concurrent_pass(db: &Arc<Db>, n: usize) -> u128 {
let mut samples: Vec<u128> = Vec::with_capacity(GATE_SAMPLES);
for _ in 0..GATE_SAMPLES {
let barrier = Arc::new(Barrier::new(n + 1));
let mut handles = Vec::with_capacity(n);
for _ in 0..n {
let db = Arc::clone(db);
let barrier = Arc::clone(&barrier);
handles.push(std::thread::spawn(move || {
barrier.wait();
let docs: Vec<PerfDoc> = db.all::<PerfDoc>().expect("all");
black_box(docs);
}));
}
barrier.wait();
let start = Instant::now();
for h in handles {
h.join().expect("join");
}
samples.push(start.elapsed().as_nanos());
}
samples.sort_unstable();
samples[GATE_SAMPLES / 2]
}
#[allow(clippy::cast_precision_loss)]
fn ns_to_us(ns: u128) -> f64 {
(ns as f64) / 1_000.0
}
fn build_payloads(n: usize, seed: u64) -> Vec<PerfDoc> {
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let mut out = Vec::with_capacity(n);
for i in 0..n {
let mut payload = vec![0u8; PAYLOAD_BYTES];
rng.fill(&mut payload[..]);
out.push(PerfDoc {
customer_id: (i + 1) as u64,
payload,
});
}
out
}
fn measure_median_ns_with_setup_n<S, T, F>(samples: usize, mut setup: S, mut op: F) -> u128
where
S: FnMut() -> T,
F: FnMut(T),
{
let mut acc: Vec<u128> = Vec::with_capacity(samples);
for _ in 0..samples {
let input = setup();
let start = Instant::now();
op(input);
acc.push(start.elapsed().as_nanos());
}
acc.sort_unstable();
acc[samples / 2]
}
fn format_table(rows: &[PerfRow]) -> String {
let mut out = String::new();
out.push_str("# obj perf table (M14 #119)\n\n");
out.push_str("| Operation | Measured (median) | Gate baseline | Notes |\n");
out.push_str("|---|---|---|---|\n");
for r in rows {
let measured = format_measured(r.measured_us);
writeln!(
out,
"| {} | {} | {} | {} |",
r.operation, measured, r.target_display, r.notes,
)
.expect("write to String");
}
out.push_str("\n*Local hardware. Each row's baseline is the wall time the bench is expected to clear on the CI shared runner; `OBJ_BENCH_ENFORCE=1` enforces a 10× headroom over each baseline. See `design.md` § Performance for the aspirational targets.*\n");
out
}
fn format_measured(us: f64) -> String {
if us < 1.0 {
format!("{:.0} ns", us * 1_000.0)
} else if us < 1_000.0 {
format!("{us:.2} µs")
} else {
format!("{:.2} ms", us / 1_000.0)
}
}
fn atomic_write(dst: &PathBuf, contents: &str) {
let tmp = dst.with_extension("md.tmp");
if let Some(parent) = dst.parent() {
fs::create_dir_all(parent).expect("create_dir_all");
}
fs::write(&tmp, contents).expect("write tmp");
fs::rename(&tmp, dst).expect("rename");
}
fn output_path() -> PathBuf {
let root =
std::env::var_os("CARGO_TARGET_DIR").map_or_else(|| PathBuf::from("target"), PathBuf::from);
root.join("criterion/perf_table/perf_table.md")
}
fn bench_perf_table(c: &mut Criterion) {
let rows: Vec<PerfRow> = vec![
row_point_read_warm(c),
row_point_read_cold(c),
row_single_insert(c),
row_batch_insert_1k(c),
row_batch_insert_10k(c),
row_index_lookup(c),
row_collection_scan(c),
row_concurrent_readers(c),
];
let table = format_table(&rows);
println!("\n{table}");
let dst = output_path();
atomic_write(&dst, &table);
println!("[perf_table] markdown table written to {}", dst.display());
if bench_enforce_enabled() {
enforce_targets(&rows);
}
}
fn enforce_targets(rows: &[PerfRow]) {
for r in rows {
let Some(target_ns) = r.target_ns else {
continue;
};
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let measured_ns = (r.measured_us * 1_000.0) as u128;
let ceiling = target_ns.saturating_mul(ORDER_OF_MAGNITUDE_NS);
if measured_ns > ceiling {
let ratio_hundredths = (measured_ns * 100) / target_ns.max(1);
panic!(
"OBJ_BENCH_ENFORCE=1: row {:?} measured {} ns > 10× target ({} ns); ratio = {}.{:02}×",
r.operation,
measured_ns,
ceiling,
ratio_hundredths / 100,
ratio_hundredths % 100,
);
}
}
}
criterion_group!(benches, bench_perf_table);
criterion_main!(benches);