mod common;
use std::{
fs,
io::{self, Write},
time::{Duration, Instant},
};
use criterion::{
BenchmarkGroup, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main,
measurement::WallTime,
};
use liteboxfs::{Connection, CreateOptions, FileKind, Owner};
use rand::{RngCore, SeedableRng, rngs::SmallRng};
use common::{KIB, TEST_INPUTS};
const TEST_SAMPLE_SIZE: usize = 20;
const TEST_CHUNK_SIZE: usize = 32 * KIB;
fn write_sqlite(group: &mut BenchmarkGroup<'_, WallTime>, mut conn: rusqlite::Connection) {
let mut rng = SmallRng::from_os_rng();
conn.execute_batch(
r#"
CREATE TABLE blocks (
id INTEGER PRIMARY KEY,
hash BLOB NOT NULL,
data BLOB NOT NULL
);
"#,
)
.unwrap();
for input in TEST_INPUTS {
group.throughput(Throughput::Bytes(input.len as u64));
group.bench_with_input(
BenchmarkId::new("theoretical maximum write throughput", input.to_string()),
&input.len,
|b, len| {
b.iter_custom(|iters| {
let mut total = Duration::from_secs(0);
for _ in 0..iters {
let tx = conn.transaction().unwrap();
let mut stmt = tx
.prepare("INSERT INTO blocks (hash, data) VALUES (?, ?);")
.unwrap();
let mut source = vec![0u8; *len];
rng.fill_bytes(&mut source);
let start = Instant::now();
for chunk in source.chunks(TEST_CHUNK_SIZE) {
let hash = blake3::hash(chunk);
stmt.execute(rusqlite::params![hash.as_bytes(), chunk])
.unwrap();
}
total += start.elapsed();
drop(stmt);
tx.rollback().unwrap();
}
total
})
},
);
}
}
fn write_litebox(group: &mut BenchmarkGroup<'_, WallTime>, mut conn: Connection) {
let mut rng = SmallRng::from_os_rng();
for input in TEST_INPUTS {
group.throughput(Throughput::Bytes(input.len as u64));
group.bench_with_input(
BenchmarkId::new("actual write throughput", input.to_string()),
&input.len,
|b, len| {
b.iter_custom(|iters| {
let mut total = Duration::from_secs(0);
for _ in 0..iters {
let mut tx = conn.tx().unwrap();
let mut fs = tx.fs().unwrap();
let mut file = fs
.create("test.txt", FileKind::Regular, Owner::ROOT)
.unwrap();
let mut source = vec![0u8; *len];
rng.fill_bytes(&mut source);
let start = Instant::now();
io::copy(&mut source.as_slice(), &mut file).unwrap();
file.flush().unwrap();
total += start.elapsed();
drop(file);
drop(fs);
tx.rollback().unwrap();
}
total
});
},
);
}
}
fn write_in_memory(c: &mut Criterion) {
let mut group = c.benchmark_group("write performance (in-memory)");
group.sample_size(TEST_SAMPLE_SIZE);
let conn = rusqlite::Connection::open_in_memory().unwrap();
write_sqlite(&mut group, conn);
let conn = Connection::open_in_memory(&CreateOptions::default()).unwrap();
write_litebox(&mut group, conn);
group.finish();
}
fn write_in_memory_chunking(c: &mut Criterion) {
let mut group = c.benchmark_group("write performance (in-memory, chunking)");
group.sample_size(TEST_SAMPLE_SIZE);
let opts = CreateOptions::default().chunking(true);
let conn = Connection::open_in_memory(&opts).unwrap();
write_litebox(&mut group, conn);
group.finish();
}
fn write_on_disk(c: &mut Criterion) {
let mut group = c.benchmark_group("write performance (on-disk)");
group.sample_size(TEST_SAMPLE_SIZE);
let conn = rusqlite::Connection::open("bench.sqlite").unwrap();
write_sqlite(&mut group, conn);
let conn = Connection::create_new("bench.litebox", &CreateOptions::default()).unwrap();
write_litebox(&mut group, conn);
group.finish();
fs::remove_file("bench.sqlite").ok();
fs::remove_file("bench.litebox").ok();
}
fn write_on_disk_chunking(c: &mut Criterion) {
let mut group = c.benchmark_group("write performance (on-disk, chunking)");
group.sample_size(TEST_SAMPLE_SIZE);
let opts = CreateOptions::default().chunking(true);
let conn = Connection::create_new("bench.litebox", &opts).unwrap();
write_litebox(&mut group, conn);
group.finish();
fs::remove_file("bench.litebox").ok();
}
criterion_group!(
write,
write_in_memory,
write_on_disk,
write_in_memory_chunking,
write_on_disk_chunking
);
criterion_main!(write);