use std::{fmt::Display, path::Path};
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use oxgraph_db::{
CheckpointPolicy, Db, DbError, ElementId, Int, Key, PropertyFamily, PropertyKeyId,
PropertySubject, PropertyType, Text,
};
const BASE_SIZES: [usize; 3] = [1_000, 10_000, 100_000];
fn unwrap<T, E: Display>(result: Result<T, E>, context: &str) -> T {
match result {
Ok(value) => value,
Err(error) => panic!("{context}: {error}"),
}
}
fn bench_path(name: &str) -> std::path::PathBuf {
std::env::temp_dir().join(format!("oxgraph-db-commit-{name}-{}", std::process::id()))
}
fn clean(path: &Path) {
match std::fs::remove_dir_all(path) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(error) => panic!("remove benchmark fixture: {error}"),
}
}
fn create_database(path: &Path, element_count: usize) -> Result<Db, DbError> {
clean(path);
let mut database = Db::create(path)?;
database.set_checkpoint_policy(CheckpointPolicy::Manual);
database.write(|writer| {
let rank_key =
writer.register_property_key("rank", PropertyFamily::Element, PropertyType::Integer)?;
for index in 0..element_count {
let element = writer.create_element()?;
writer.set(
PropertySubject::Element(element),
Key::<Int>::from_id(rank_key),
i64::try_from(index)
.map_err(|_error| DbError::Query(oxgraph_db::QueryError::ValueOutOfRange))?,
)?;
}
Ok(())
})?;
database.compact()?;
database.set_checkpoint_policy(CheckpointPolicy::Manual);
Ok(database)
}
fn database_or_panic(name: &str, element_count: usize) -> Db {
let path = bench_path(name);
unwrap(
create_database(&path, element_count),
"benchmark fixture should build",
)
}
fn commit_one_element(database: &mut Db) {
unwrap(
database.write(|writer| {
writer.create_element()?;
Ok(())
}),
"transaction should commit",
);
}
fn bench_commit_single_element(c: &mut Criterion) {
let mut group = c.benchmark_group("db_commit_single_element_over_base");
for size in BASE_SIZES {
let mut database = database_or_panic("single", size);
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _size| {
b.iter(|| commit_one_element(&mut database));
});
}
group.finish();
}
fn rollback_empty_write(database: &mut Db) {
let _ =
database.write(|_writer| Err::<(), DbError>(DbError::Query(oxgraph_db::QueryError::Empty)));
}
fn bench_begin_write_rollback(c: &mut Criterion) {
let mut group = c.benchmark_group("db_begin_write_rollback_over_base");
for size in BASE_SIZES {
let mut database = database_or_panic("rollback", size);
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _size| {
b.iter(|| rollback_empty_write(&mut database));
});
}
group.finish();
}
fn create_unfolded_database(path: &Path, element_count: usize) -> Result<Db, DbError> {
clean(path);
let mut database = Db::create(path)?;
database.set_checkpoint_policy(CheckpointPolicy::Manual);
database.write(|writer| {
let rank_key =
writer.register_property_key("rank", PropertyFamily::Element, PropertyType::Integer)?;
for index in 0..element_count {
let element = writer.create_element()?;
writer.set(
PropertySubject::Element(element),
Key::<Int>::from_id(rank_key),
i64::try_from(index)
.map_err(|_error| DbError::Query(oxgraph_db::QueryError::ValueOutOfRange))?,
)?;
}
Ok(())
})?;
Ok(database)
}
fn bench_write_over_unfolded_overlay(c: &mut Criterion) {
let mut group = c.benchmark_group("db_write_over_unfolded_overlay");
group.sample_size(20);
for size in BASE_SIZES {
let path = bench_path(&format!("unfolded-{size}"));
let mut database = unwrap(
create_unfolded_database(&path, size),
"benchmark fixture should build",
);
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _size| {
b.iter(|| commit_one_element(&mut database));
});
}
group.finish();
}
fn create_text_database(
path: &Path,
element_count: usize,
) -> Result<(Db, Vec<ElementId>, PropertyKeyId), DbError> {
clean(path);
let mut database = Db::create(path)?;
database.set_checkpoint_policy(CheckpointPolicy::Manual);
let (handles, _outcome) = database.write(|writer| {
let name_key =
writer.register_property_key("name", PropertyFamily::Element, PropertyType::Text)?;
let mut elements = Vec::with_capacity(element_count);
for _index in 0..element_count {
elements.push(writer.create_element()?);
}
Ok((elements, name_key))
})?;
database.compact()?;
database.set_checkpoint_policy(CheckpointPolicy::Manual);
Ok((database, handles.0, handles.1))
}
fn bench_commit_text_properties(c: &mut Criterion) {
let mut group = c.benchmark_group("db_commit_text_properties");
group.sample_size(20);
let path = bench_path("text");
let (mut database, elements, name_key) = unwrap(
create_text_database(&path, 1_000),
"benchmark fixture should build",
);
let mut round: u64 = 0;
let padding = "x".repeat(47);
group.bench_function("1k_values_64b", |b| {
b.iter(|| {
round += 1;
commit_text_round(&mut database, &elements, name_key, round, &padding);
});
});
group.finish();
}
fn commit_text_round(
database: &mut Db,
elements: &[ElementId],
name_key: PropertyKeyId,
round: u64,
padding: &str,
) {
unwrap(
database.write(|writer| {
for (index, element) in elements.iter().enumerate() {
writer.set(
PropertySubject::Element(*element),
Key::<Text>::from_id(name_key),
format!("{round:08}-{index:06}-{padding}"),
)?;
}
Ok(())
}),
"text transaction should commit",
);
}
criterion_group!(
benches,
bench_commit_single_element,
bench_begin_write_rollback,
bench_write_over_unfolded_overlay,
bench_commit_text_properties
);
criterion_main!(benches);