#![forbid(unsafe_code)]
#[cfg(feature = "fast-alloc")]
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
use std::hint::black_box;
use std::time::Duration;
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use obj::{Db, Document, Id, IndexSpec};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
use serde::{Deserialize, Serialize};
mod common;
use common::fresh_db;
const PAYLOAD_BYTES: usize = 480;
const POPULATE_COUNT: usize = 256;
const POPULATE_BATCH: usize = 1_000;
const BATCH_INSERT_COUNT: usize = 64;
const POPULATE_SEED: u64 = 0x0B_0078_5EED_0001;
const LOOKUP_SEED: u64 = 0x0B_0078_5EED_0002;
const BATCH_SEED: u64 = 0x0B_0078_5EED_0003;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct QuickDoc {
key: u64,
payload: Vec<u8>,
}
impl Document for QuickDoc {
const COLLECTION: &'static str = "quick_docs";
const VERSION: u32 = 1;
fn indexes() -> Vec<IndexSpec> {
vec![IndexSpec::unique("by_key", "key").expect("unique spec")]
}
}
fn populate(db: &Db, count: usize) -> (Vec<u64>, Vec<Id>) {
let mut rng = ChaCha8Rng::seed_from_u64(POPULATE_SEED);
let mut keys: Vec<u64> = Vec::with_capacity(count);
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 batch_ids: Vec<Id> = db
.transaction(|tx| {
let coll = tx.collection::<QuickDoc>()?;
let mut out = Vec::with_capacity(batch_end - inserted);
for k in inserted..batch_end {
let key = (k as u64) + 1;
let mut payload = vec![0u8; PAYLOAD_BYTES];
rng.fill(&mut payload[..]);
out.push(coll.insert(QuickDoc { key, payload })?);
keys.push(key);
}
Ok(out)
})
.expect("populate batch");
ids.extend(batch_ids);
inserted = batch_end;
}
(keys, ids)
}
fn build_payloads(n: usize, seed: u64) -> Vec<QuickDoc> {
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(QuickDoc {
key: (i as u64) + 1,
payload,
});
}
out
}
fn point_read_warm(c: &mut Criterion, db: &Db, hot_id: Id) {
let mut group = c.benchmark_group("quick");
group.sample_size(30);
group.warm_up_time(Duration::from_millis(200));
group.measurement_time(Duration::from_millis(700));
group.bench_function("point_read_warm", |b| {
b.iter(|| {
let got: Option<QuickDoc> = db
.read_transaction(|tx| tx.collection::<QuickDoc>()?.get(hot_id))
.expect("read");
black_box(got);
});
});
group.finish();
}
fn index_lookup(c: &mut Criterion, db: &Db, keys: &[u64]) {
let mut rng = ChaCha8Rng::seed_from_u64(LOOKUP_SEED);
let mut group = c.benchmark_group("quick");
group.sample_size(30);
group.warm_up_time(Duration::from_millis(200));
group.measurement_time(Duration::from_millis(700));
group.bench_function("index_lookup", |b| {
b.iter(|| {
let key = keys[rng.random_range(0..keys.len())];
let got: Option<QuickDoc> = db
.find_unique::<QuickDoc>("by_key", key)
.expect("find_unique");
black_box(got);
});
});
group.finish();
}
fn batch_insert_64(c: &mut Criterion) {
let mut group = c.benchmark_group("quick");
group.sample_size(10);
group.warm_up_time(Duration::from_millis(300));
group.measurement_time(Duration::from_secs(1));
group.bench_function("batch_insert_64", |b| {
b.iter_batched(
|| {
(
fresh_db("quick_batch"),
build_payloads(BATCH_INSERT_COUNT, BATCH_SEED),
)
},
|(bench_db, batch)| {
bench_db
.db
.transaction(|tx| {
let coll = tx.collection::<QuickDoc>()?;
for doc in batch {
let _ = coll.insert(doc)?;
}
Ok(())
})
.expect("batch insert");
},
BatchSize::PerIteration,
);
});
group.finish();
}
fn bench_quick(c: &mut Criterion) {
let bench_db = fresh_db("quick");
let (keys, ids) = populate(&bench_db.db, POPULATE_COUNT);
let hot_id = ids[0];
point_read_warm(c, &bench_db.db, hot_id);
index_lookup(c, &bench_db.db, &keys);
batch_insert_64(c);
}
criterion_group!(benches, bench_quick);
criterion_main!(benches);