264 lines
8.8 KiB
Rust
264 lines
8.8 KiB
Rust
//! Compare the storage adapters on identical workloads.
|
|
//!
|
|
//! Three workloads:
|
|
//!
|
|
//! - `insert_n`: one transaction, N inserts, commit. Measures the per-row
|
|
//! write path plus tx commit overhead.
|
|
//! - `scan_full`: `storage.scan(name)`. Measures materialized scan cost.
|
|
//! - `scan_iter_drain`: drain `storage.scan_iter(name)` with `.count()`.
|
|
//! Measures the streaming-or-materialize-then-yield cost.
|
|
//!
|
|
//! Adapters: memory always; sqlite, redb, fjall, and lmdb gated on their
|
|
//! respective features. Geomerge is intentionally excluded: it requires a
|
|
//! pre-loaded theory and its insert semantics (foreign-key references, law
|
|
//! validation at commit) differ enough that side-by-side comparison would
|
|
//! mislead.
|
|
//!
|
|
//! Run with `cargo bench -p storage --all-features` for the full table.
|
|
|
|
#![allow(clippy::unwrap_used, clippy::expect_used)]
|
|
|
|
use criterion::{BatchSize, BenchmarkId, Criterion, black_box, criterion_group, criterion_main};
|
|
use storage::value::Value;
|
|
use storage::{MemoryStorage, Storage};
|
|
|
|
#[cfg(feature = "fjall")]
|
|
use storage::adapters::fjall::FjallStorage;
|
|
#[cfg(feature = "lmdb")]
|
|
use storage::adapters::lmdb::LmdbStorage;
|
|
#[cfg(feature = "redb")]
|
|
use storage::adapters::redb::RedbStorage;
|
|
#[cfg(feature = "sqlite")]
|
|
use storage::adapters::sqlite::SqliteStorage;
|
|
|
|
const ROW_COUNTS: &[usize] = &[100, 1_000, 10_000];
|
|
|
|
fn make_row(i: usize) -> Vec<Value> {
|
|
vec![Value::Int(i as i64), Value::Int((i * 2) as i64)]
|
|
}
|
|
|
|
fn populate(storage: &mut dyn Storage, n: usize) {
|
|
storage.create_relation("rel", 2).unwrap();
|
|
let mut tx = storage.transaction().unwrap();
|
|
for i in 0..n {
|
|
tx.insert("rel", make_row(i)).unwrap();
|
|
}
|
|
tx.commit().unwrap();
|
|
}
|
|
|
|
fn run_insert_workload(storage: &mut dyn Storage, n: usize) {
|
|
storage.create_relation("rel", 2).unwrap();
|
|
let mut tx = storage.transaction().unwrap();
|
|
for i in 0..n {
|
|
tx.insert("rel", make_row(i)).unwrap();
|
|
}
|
|
tx.commit().unwrap();
|
|
}
|
|
|
|
fn bench_insert_n(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("insert_n");
|
|
for &n in ROW_COUNTS {
|
|
group.bench_with_input(BenchmarkId::new("memory", n), &n, |b, &n| {
|
|
b.iter_batched(
|
|
MemoryStorage::new,
|
|
|mut s| {
|
|
run_insert_workload(&mut s, n);
|
|
black_box(s);
|
|
},
|
|
BatchSize::SmallInput,
|
|
);
|
|
});
|
|
|
|
#[cfg(feature = "sqlite")]
|
|
group.bench_with_input(BenchmarkId::new("sqlite-mem", n), &n, |b, &n| {
|
|
b.iter_batched(
|
|
|| SqliteStorage::open(":memory:").unwrap(),
|
|
|mut s| {
|
|
run_insert_workload(&mut s, n);
|
|
black_box(s);
|
|
},
|
|
BatchSize::SmallInput,
|
|
);
|
|
});
|
|
|
|
#[cfg(feature = "redb")]
|
|
group.bench_with_input(BenchmarkId::new("redb", n), &n, |b, &n| {
|
|
b.iter_batched(
|
|
|| {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let storage = RedbStorage::open(dir.path().join("db")).unwrap();
|
|
(storage, dir)
|
|
},
|
|
|(mut s, dir)| {
|
|
run_insert_workload(&mut s, n);
|
|
black_box((s, dir));
|
|
},
|
|
BatchSize::SmallInput,
|
|
);
|
|
});
|
|
|
|
#[cfg(feature = "fjall")]
|
|
group.bench_with_input(BenchmarkId::new("fjall", n), &n, |b, &n| {
|
|
b.iter_batched(
|
|
|| {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let storage = FjallStorage::open(dir.path()).unwrap();
|
|
(storage, dir)
|
|
},
|
|
|(mut s, dir)| {
|
|
run_insert_workload(&mut s, n);
|
|
black_box((s, dir));
|
|
},
|
|
BatchSize::SmallInput,
|
|
);
|
|
});
|
|
|
|
#[cfg(feature = "lmdb")]
|
|
group.bench_with_input(BenchmarkId::new("lmdb", n), &n, |b, &n| {
|
|
b.iter_batched(
|
|
|| {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let storage = LmdbStorage::open(dir.path()).unwrap();
|
|
(storage, dir)
|
|
},
|
|
|(mut s, dir)| {
|
|
run_insert_workload(&mut s, n);
|
|
black_box((s, dir));
|
|
},
|
|
BatchSize::SmallInput,
|
|
);
|
|
});
|
|
}
|
|
group.finish();
|
|
}
|
|
|
|
fn bench_scan_full(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("scan_full");
|
|
for &n in ROW_COUNTS {
|
|
group.bench_with_input(BenchmarkId::new("memory", n), &n, |b, &n| {
|
|
let mut s = MemoryStorage::new();
|
|
populate(&mut s, n);
|
|
b.iter(|| {
|
|
let rows = s.scan("rel").unwrap();
|
|
black_box(rows);
|
|
});
|
|
});
|
|
|
|
#[cfg(feature = "sqlite")]
|
|
group.bench_with_input(BenchmarkId::new("sqlite-mem", n), &n, |b, &n| {
|
|
let mut s = SqliteStorage::open(":memory:").unwrap();
|
|
populate(&mut s, n);
|
|
b.iter(|| {
|
|
let rows = s.scan("rel").unwrap();
|
|
black_box(rows);
|
|
});
|
|
});
|
|
|
|
#[cfg(feature = "redb")]
|
|
group.bench_with_input(BenchmarkId::new("redb", n), &n, |b, &n| {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let mut s = RedbStorage::open(dir.path().join("db")).unwrap();
|
|
populate(&mut s, n);
|
|
b.iter(|| {
|
|
let rows = s.scan("rel").unwrap();
|
|
black_box(rows);
|
|
});
|
|
drop(dir);
|
|
});
|
|
|
|
#[cfg(feature = "fjall")]
|
|
group.bench_with_input(BenchmarkId::new("fjall", n), &n, |b, &n| {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let mut s = FjallStorage::open(dir.path()).unwrap();
|
|
populate(&mut s, n);
|
|
b.iter(|| {
|
|
let rows = s.scan("rel").unwrap();
|
|
black_box(rows);
|
|
});
|
|
drop(dir);
|
|
});
|
|
|
|
#[cfg(feature = "lmdb")]
|
|
group.bench_with_input(BenchmarkId::new("lmdb", n), &n, |b, &n| {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let mut s = LmdbStorage::open(dir.path()).unwrap();
|
|
populate(&mut s, n);
|
|
b.iter(|| {
|
|
let rows = s.scan("rel").unwrap();
|
|
black_box(rows);
|
|
});
|
|
drop(dir);
|
|
});
|
|
}
|
|
group.finish();
|
|
}
|
|
|
|
fn bench_scan_iter_drain(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("scan_iter_drain");
|
|
for &n in ROW_COUNTS {
|
|
group.bench_with_input(BenchmarkId::new("memory", n), &n, |b, &n| {
|
|
let mut s = MemoryStorage::new();
|
|
populate(&mut s, n);
|
|
b.iter(|| {
|
|
let count = s.scan_iter("rel").unwrap().filter_map(Result::ok).count();
|
|
black_box(count);
|
|
});
|
|
});
|
|
|
|
#[cfg(feature = "sqlite")]
|
|
group.bench_with_input(BenchmarkId::new("sqlite-mem", n), &n, |b, &n| {
|
|
let mut s = SqliteStorage::open(":memory:").unwrap();
|
|
populate(&mut s, n);
|
|
b.iter(|| {
|
|
let count = s.scan_iter("rel").unwrap().filter_map(Result::ok).count();
|
|
black_box(count);
|
|
});
|
|
});
|
|
|
|
#[cfg(feature = "redb")]
|
|
group.bench_with_input(BenchmarkId::new("redb", n), &n, |b, &n| {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let mut s = RedbStorage::open(dir.path().join("db")).unwrap();
|
|
populate(&mut s, n);
|
|
b.iter(|| {
|
|
let count = s.scan_iter("rel").unwrap().filter_map(Result::ok).count();
|
|
black_box(count);
|
|
});
|
|
drop(dir);
|
|
});
|
|
|
|
#[cfg(feature = "fjall")]
|
|
group.bench_with_input(BenchmarkId::new("fjall", n), &n, |b, &n| {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let mut s = FjallStorage::open(dir.path()).unwrap();
|
|
populate(&mut s, n);
|
|
b.iter(|| {
|
|
let count = s.scan_iter("rel").unwrap().filter_map(Result::ok).count();
|
|
black_box(count);
|
|
});
|
|
drop(dir);
|
|
});
|
|
|
|
#[cfg(feature = "lmdb")]
|
|
group.bench_with_input(BenchmarkId::new("lmdb", n), &n, |b, &n| {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let mut s = LmdbStorage::open(dir.path()).unwrap();
|
|
populate(&mut s, n);
|
|
b.iter(|| {
|
|
let count = s.scan_iter("rel").unwrap().filter_map(Result::ok).count();
|
|
black_box(count);
|
|
});
|
|
drop(dir);
|
|
});
|
|
}
|
|
group.finish();
|
|
}
|
|
|
|
criterion_group!(
|
|
benches,
|
|
bench_insert_n,
|
|
bench_scan_full,
|
|
bench_scan_iter_drain
|
|
);
|
|
criterion_main!(benches);
|