diff --git a/Cargo.lock b/Cargo.lock index 94e2c9c..001ebe9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -798,6 +798,19 @@ dependencies = [ name = "cuprate-constants" version = "0.1.0" +[[package]] +name = "cuprate-criterion-database" +version = "0.0.0" +dependencies = [ + "criterion", + "cuprate-blockchain", + "cuprate-database", + "cuprate-helper", + "function_name", + "rand", + "tempfile", +] + [[package]] name = "cuprate-criterion-example" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 220aa7d..d246746 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ # Binaries "binaries/cuprated", + # Benchmarks "benches/benchmark/bin", "benches/benchmark/lib", @@ -10,6 +11,8 @@ members = [ "benches/criterion/example", "benches/criterion/cuprate-json-rpc", "benches/criterion/cuprate-helper", + "benches/criterion/cuprate-database", + # Consensus "consensus", "consensus/context", diff --git a/benches/criterion/cuprate-database/Cargo.toml b/benches/criterion/cuprate-database/Cargo.toml new file mode 100644 index 0000000..345448b --- /dev/null +++ b/benches/criterion/cuprate-database/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "cuprate-criterion-database" +version = "0.0.0" +edition = "2021" +description = "Criterion benchmarking for cuprate-database" +license = "MIT" +authors = ["hinto-janai"] +repository = "https://github.com/Cuprate/cuprate/tree/main/benches/criterion/cuprate-database" +keywords = ["cuprate", "database", "benchmark"] + +[features] +default = ["heed"] +heed = ["cuprate-database/heed", "cuprate-blockchain/heed"] +redb = ["cuprate-database/redb", "cuprate-blockchain/redb"] + +[dependencies] +# FIXME: +# Some crates/features that are unused here but +# needed in other crates are pulled in, see: +# - +# +# Remove: +# - rand +# - cuprate-blockchain/asynch +# - cuprate-blockchain/tx + +criterion = { workspace = true } +cuprate-database = { path = "../../../storage/database" } +cuprate-blockchain = { path = "../../../storage/blockchain", features = ["service"] } +cuprate-helper = { path = "../../../helper", features = ["asynch", "fs", "thread", "tx"] } + +function_name = { workspace = true } +tempfile = { workspace = true } +rand = { workspace = true, features = ["std", "std_rng"] } + +[[bench]] +name = "main" +harness = false + +[lints] +workspace = true \ No newline at end of file diff --git a/benches/criterion/cuprate-database/README.md b/benches/criterion/cuprate-database/README.md new file mode 100644 index 0000000..6e8314f --- /dev/null +++ b/benches/criterion/cuprate-database/README.md @@ -0,0 +1,48 @@ +# `cuprate-database-benchmark` +This is a benchmarking suite that allows testing/benchmarking `cuprate-database` with [`criterion`](https://bheisler.github.io/criterion.rs/book/criterion_rs.html). + +For more information on `cargo bench` and `criterion`: +- https://doc.rust-lang.org/cargo/commands/cargo-bench.html +- https://bheisler.github.io/criterion.rs/book/criterion_rs.html + + +1. [Usage](#Usage) +1. [File Structure](#file-structure) + - [`src/`](#src) + - [`benches/`](#benches) + +# Usage +Ensure the system is as quiet as possible (no background tasks) before starting and during the benchmarks. + +To start all benchmarks, run: +```bash +cargo bench --package cuprate-database-benchmarks +``` + +# File Structure +A quick reference of the structure of the folders & files in `cuprate-database`. + +Note that `lib.rs/mod.rs` files are purely for re-exporting/visibility/lints, and contain no code. Each sub-directory has a corresponding `mod.rs`. + +## `src/` +The top-level `src/` files. + +The actual `cuprate-database-benchmark` library crate is just used as a helper for the benchmarks within `benches/`. + +| File | Purpose | +|---------------------|---------| +| `helper.rs` | Helper functions + +## `benches/` +The actual benchmarks. + +Each file represents some logical benchmark grouping. + +| File | Purpose | +|-----------------------|---------| +| `db.rs` | `trait Database{Ro,Rw,Iter}` benchmarks +| `db_multi_thread.rs` | Same as `db.rs` but multi-threaded +| `env.rs` | `trait {Env, EnvInner, TxR{o,w}, Tables[Mut]}` benchmarks +| `env_multi_thread.rs` | Same as `env.rs` but multi-threaded +| `service.rs` | `cuprate_database::service` benchmarks +| `storable.rs` | `trait Storable` benchmarks \ No newline at end of file diff --git a/benches/criterion/cuprate-database/benches/db.rs b/benches/criterion/cuprate-database/benches/db.rs new file mode 100644 index 0000000..e0d4945 --- /dev/null +++ b/benches/criterion/cuprate-database/benches/db.rs @@ -0,0 +1,444 @@ +//! Database operations. +//! +//! This module tests the functions from: +//! - [`cuprate_database::DatabaseRo`] +//! - [`cuprate_database::DatabaseRw`] +//! - [`cuprate_database::DatabaseIter`] + +#![allow(unused_crate_dependencies, unused_attributes)] +#![expect(clippy::significant_drop_tightening)] + +use std::time::Instant; + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use function_name::named; + +use cuprate_blockchain::{ + tables::Outputs, + types::{Output, PreRctOutputId}, +}; +use cuprate_database::{DatabaseIter, DatabaseRo, DatabaseRw, Env, EnvInner}; + +use cuprate_criterion_database::{TmpEnv, KEY, VALUE}; + +criterion_group! { + name = benches; + config = Criterion::default(); + targets = + // `DatabaseRo` + ro_get, + ro_len, + ro_first, + ro_last, + ro_is_empty, + ro_contains, + + // `DatabaseRo` with a `TxRw` + rw_get, + rw_len, + rw_first, + rw_last, + rw_is_empty, + rw_contains, + + // `DatabaseIter` + get_range, + iter, + keys, + values, + + // `DatabaseRw` + put, + delete, + pop_first, + pop_last, + take, +} +criterion_main!(benches); + +//---------------------------------------------------------------------------------------------------- DatabaseRo +// Read-only table operations. +// This uses `TxRw + TablesMut` briefly to insert values, then +// uses `TxRo + Tables` for the actual operation. +// +// See further below for using `TxRw + TablesMut` on the same operations. + +/// [`DatabaseRo::get`] +#[named] +fn ro_get(c: &mut Criterion) { + let env = TmpEnv::new().with_key_value(); + let env_inner = env.env.env_inner(); + let tx_ro = env_inner.tx_ro().unwrap(); + let table = env_inner.open_db_ro::(&tx_ro).unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + let _: Output = table.get(black_box(&KEY)).unwrap(); + }); + }); +} + +/// [`DatabaseRo::len`] +#[named] +fn ro_len(c: &mut Criterion) { + let env = TmpEnv::new().with_key_value(); + let env_inner = env.env.env_inner(); + let tx_ro = env_inner.tx_ro().unwrap(); + let table = env_inner.open_db_ro::(&tx_ro).unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + black_box(table.len()).unwrap(); + }); + }); +} + +/// [`DatabaseRo::first`] +#[named] +fn ro_first(c: &mut Criterion) { + let env = TmpEnv::new().with_key_value(); + let env_inner = env.env.env_inner(); + let tx_ro = env_inner.tx_ro().unwrap(); + let table = env_inner.open_db_ro::(&tx_ro).unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + let (_, _): (PreRctOutputId, Output) = black_box(table.first()).unwrap(); + }); + }); +} + +/// [`DatabaseRo::last`] +#[named] +fn ro_last(c: &mut Criterion) { + let env = TmpEnv::new().with_key_value(); + let env_inner = env.env.env_inner(); + let tx_ro = env_inner.tx_ro().unwrap(); + let table = env_inner.open_db_ro::(&tx_ro).unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + let (_, _): (PreRctOutputId, Output) = black_box(table.last()).unwrap(); + }); + }); +} + +/// [`DatabaseRo::is_empty`] +#[named] +fn ro_is_empty(c: &mut Criterion) { + let env = TmpEnv::new().with_key_value(); + let env_inner = env.env.env_inner(); + let tx_ro = env_inner.tx_ro().unwrap(); + let table = env_inner.open_db_ro::(&tx_ro).unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + black_box(table.is_empty()).unwrap(); + }); + }); +} + +/// [`DatabaseRo::contains`] +#[named] +fn ro_contains(c: &mut Criterion) { + let env = TmpEnv::new().with_key_value(); + let env_inner = env.env.env_inner(); + let tx_ro = env_inner.tx_ro().unwrap(); + let table = env_inner.open_db_ro::(&tx_ro).unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + table.contains(black_box(&KEY)).unwrap(); + }); + }); +} + +//---------------------------------------------------------------------------------------------------- DatabaseRo (TxRw) +// These are the same benchmarks as above, but it uses a +// `TxRw` and a `TablesMut` instead to ensure our read/write tables +// using read operations perform the same as normal read-only tables. + +/// [`DatabaseRw::get`] +#[named] +fn rw_get(c: &mut Criterion) { + let env = TmpEnv::new().with_key_value(); + let env_inner = env.env.env_inner(); + let tx_rw = env_inner.tx_rw().unwrap(); + let table = env_inner.open_db_rw::(&tx_rw).unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + let _: Output = table.get(black_box(&KEY)).unwrap(); + }); + }); +} + +/// [`DatabaseRw::len`] +#[named] +fn rw_len(c: &mut Criterion) { + let env = TmpEnv::new().with_key_value(); + let env_inner = env.env.env_inner(); + let tx_rw = env_inner.tx_rw().unwrap(); + let table = env_inner.open_db_rw::(&tx_rw).unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + black_box(table.len()).unwrap(); + }); + }); +} + +/// [`DatabaseRw::first`] +#[named] +fn rw_first(c: &mut Criterion) { + let env = TmpEnv::new().with_key_value(); + let env_inner = env.env.env_inner(); + let tx_rw = env_inner.tx_rw().unwrap(); + let table = env_inner.open_db_rw::(&tx_rw).unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + let (_, _): (PreRctOutputId, Output) = black_box(table.first()).unwrap(); + }); + }); +} + +/// [`DatabaseRw::last`] +#[named] +fn rw_last(c: &mut Criterion) { + let env = TmpEnv::new().with_key_value(); + let env_inner = env.env.env_inner(); + let tx_rw = env_inner.tx_rw().unwrap(); + let table = env_inner.open_db_rw::(&tx_rw).unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + let (_, _): (PreRctOutputId, Output) = black_box(table.last()).unwrap(); + }); + }); +} + +/// [`DatabaseRw::is_empty`] +#[named] +fn rw_is_empty(c: &mut Criterion) { + let env = TmpEnv::new().with_key_value(); + let env_inner = env.env.env_inner(); + let tx_rw = env_inner.tx_rw().unwrap(); + let table = env_inner.open_db_rw::(&tx_rw).unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + black_box(table.is_empty()).unwrap(); + }); + }); +} + +/// [`DatabaseRw::contains`] +#[named] +fn rw_contains(c: &mut Criterion) { + let env = TmpEnv::new().with_key_value(); + let env_inner = env.env.env_inner(); + let tx_rw = env_inner.tx_rw().unwrap(); + let table = env_inner.open_db_rw::(&tx_rw).unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + table.contains(black_box(&KEY)).unwrap(); + }); + }); +} + +//---------------------------------------------------------------------------------------------------- DatabaseIter +/// [`DatabaseIter::get_range`] +#[named] +fn get_range(c: &mut Criterion) { + let env = TmpEnv::new().with_key_value_100(); + let env_inner = env.env.env_inner(); + let tx_ro = env_inner.tx_ro().unwrap(); + let table = env_inner.open_db_ro::(&tx_ro).unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + let range = table.get_range(black_box(..)).unwrap(); + for result in range { + let _: Output = black_box(result.unwrap()); + } + }); + }); +} + +/// [`DatabaseIter::iter`] +#[named] +fn iter(c: &mut Criterion) { + let env = TmpEnv::new().with_key_value_100(); + let env_inner = env.env.env_inner(); + let tx_ro = env_inner.tx_ro().unwrap(); + let table = env_inner.open_db_ro::(&tx_ro).unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + let iter = black_box(table.iter()).unwrap(); + for result in iter { + let _: (PreRctOutputId, Output) = black_box(result.unwrap()); + } + }); + }); +} + +/// [`DatabaseIter::keys`] +#[named] +fn keys(c: &mut Criterion) { + let env = TmpEnv::new().with_key_value_100(); + let env_inner = env.env.env_inner(); + let tx_ro = env_inner.tx_ro().unwrap(); + let table = env_inner.open_db_ro::(&tx_ro).unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + let keys = black_box(table.keys()).unwrap(); + for result in keys { + let _: PreRctOutputId = black_box(result.unwrap()); + } + }); + }); +} + +/// [`DatabaseIter::values`] +#[named] +fn values(c: &mut Criterion) { + let env = TmpEnv::new().with_key_value_100(); + let env_inner = env.env.env_inner(); + let tx_ro = env_inner.tx_ro().unwrap(); + let table = env_inner.open_db_ro::(&tx_ro).unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + let values = black_box(table.values()).unwrap(); + for result in values { + let _: Output = black_box(result.unwrap()); + } + }); + }); +} + +//---------------------------------------------------------------------------------------------------- DatabaseRw +/// [`DatabaseRw::put`] +#[named] +fn put(c: &mut Criterion) { + let env = TmpEnv::new(); + let env_inner = env.env.env_inner(); + let tx_rw = env_inner.tx_rw().unwrap(); + let mut table = env_inner.open_db_rw::(&tx_rw).unwrap(); + + let mut key = KEY; + + c.bench_function(function_name!(), |b| { + b.iter(|| { + table.put(black_box(&key), black_box(&VALUE)).unwrap(); + key.amount += 1; + }); + }); +} + +/// [`DatabaseRw::delete`] +#[named] +fn delete(c: &mut Criterion) { + let env = TmpEnv::new(); + let env_inner = env.env.env_inner(); + let tx_rw = env_inner.tx_rw().unwrap(); + let mut table = env_inner.open_db_rw::(&tx_rw).unwrap(); + + let mut key = KEY; + + c.bench_function(function_name!(), |b| { + b.iter_custom(|iters| { + for _ in 0..iters { + table.put(&key, &VALUE).unwrap(); + key.amount += 1; + } + + key = KEY; + + let start = Instant::now(); + for _ in 0..iters { + table.delete(&key).unwrap(); + key.amount += 1; + } + start.elapsed() + }); + }); +} + +/// [`DatabaseRw::pop_first`] +#[named] +fn pop_first(c: &mut Criterion) { + let env = TmpEnv::new(); + let env_inner = env.env.env_inner(); + let tx_rw = env_inner.tx_rw().unwrap(); + let mut table = env_inner.open_db_rw::(&tx_rw).unwrap(); + + let mut key = KEY; + + c.bench_function(function_name!(), |b| { + b.iter_custom(|iters| { + for _ in 0..iters { + table.put(&key, &VALUE).unwrap(); + key.amount += 1; + } + + key = KEY; + + let start = Instant::now(); + for _ in 0..iters { + table.pop_first().unwrap(); + key.amount += 1; + } + start.elapsed() + }); + }); +} + +/// [`DatabaseRw::pop_last`] +#[named] +fn pop_last(c: &mut Criterion) { + let env = TmpEnv::new(); + let env_inner = env.env.env_inner(); + let tx_rw = env_inner.tx_rw().unwrap(); + let mut table = env_inner.open_db_rw::(&tx_rw).unwrap(); + + let mut key = KEY; + + c.bench_function(function_name!(), |b| { + b.iter_custom(|iters| { + for _ in 0..iters { + table.put(&key, &VALUE).unwrap(); + key.amount += 1; + } + + key = KEY; + + let start = Instant::now(); + for _ in 0..iters { + table.pop_last().unwrap(); + key.amount += 1; + } + start.elapsed() + }); + }); +} + +/// [`DatabaseRw::take`] +#[named] +fn take(c: &mut Criterion) { + let env = TmpEnv::new(); + let env_inner = env.env.env_inner(); + let tx_rw = env_inner.tx_rw().unwrap(); + let mut table = env_inner.open_db_rw::(&tx_rw).unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + table.put(&KEY, &VALUE).unwrap(); + let _: Output = black_box(table.take(&black_box(KEY)).unwrap()); + }); + }); +} diff --git a/benches/criterion/cuprate-database/benches/env.rs b/benches/criterion/cuprate-database/benches/env.rs new file mode 100644 index 0000000..e7213d0 --- /dev/null +++ b/benches/criterion/cuprate-database/benches/env.rs @@ -0,0 +1,183 @@ +//! [`Env`] benchmarks. + +#![allow(unused_crate_dependencies, unused_attributes)] +#![expect(clippy::significant_drop_tightening)] + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use function_name::named; + +use cuprate_blockchain::tables::Outputs; +use cuprate_database::{ + resize::{ResizeAlgorithm, PAGE_SIZE}, + ConcreteEnv, Env, EnvInner, TxRo, TxRw, +}; + +use cuprate_criterion_database::TmpEnv; + +criterion_group! { + name = benches; + config = Criterion::default(); + targets = + // open, + env_inner, + tx_ro, + tx_rw, + open_db_ro, + open_db_rw, + create_db, + resize, + current_map_size, + disk_size_bytes, +} +criterion_main!(benches); + +// FIXME: This function is hard to time due to: +// - heed errors +// - "too many open files" errors +// +// /// [`Env::open`]. +// #[named] +// fn open(c: &mut Criterion) { +// c.bench_function(function_name!(), |b| { +// b.iter_custom(|_| { +// let tempdir = tempfile::tempdir().unwrap(); +// let config = ConfigBuilder::new(tempdir.path().to_path_buf().into()).build(); +// +// let now = std::time::Instant::now(); +// ConcreteEnv::open(config).unwrap(); +// let elapsed = now.elapsed(); +// +// tempdir.close().unwrap(); +// elapsed +// }); +// }); +// } + +/// [`Env::env_inner`]. +#[named] +fn env_inner(c: &mut Criterion) { + let env = TmpEnv::new(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + drop(black_box(env.env.env_inner())); + }); + }); +} + +/// [`EnvInner::tx_ro`]. +#[named] +fn tx_ro(c: &mut Criterion) { + let env = TmpEnv::new(); + let env_inner = env.env.env_inner(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + let tx_ro = black_box(env_inner.tx_ro()).unwrap(); + TxRo::commit(black_box(tx_ro)).unwrap(); + }); + }); +} + +/// [`EnvInner::tx_rw`]. +#[named] +fn tx_rw(c: &mut Criterion) { + let env = TmpEnv::new(); + let env_inner = env.env.env_inner(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + let tx_rw = black_box(env_inner.tx_rw()).unwrap(); + TxRw::commit(black_box(tx_rw)).unwrap(); + }); + }); +} + +/// [`EnvInner::open_db_ro`]. +#[named] +fn open_db_ro(c: &mut Criterion) { + // `with_key_value()` creates the `Outputs` + // table so the `open_db_ro` below doesn't panic. + let env = TmpEnv::new().with_key_value(); + let env_inner = env.env.env_inner(); + let tx_ro = env_inner.tx_ro().unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + env_inner.open_db_ro::(&tx_ro).unwrap(); + }); + }); +} + +/// [`EnvInner::open_db_rw`]. +#[named] +fn open_db_rw(c: &mut Criterion) { + let env = TmpEnv::new(); + let env_inner = env.env.env_inner(); + let tx_rw = env_inner.tx_rw().unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + env_inner.open_db_rw::(&tx_rw).unwrap(); + }); + }); +} + +/// [`EnvInner::create_db`]. +#[named] +fn create_db(c: &mut Criterion) { + let env = TmpEnv::new(); + let env_inner = env.env.env_inner(); + let tx_rw = env_inner.tx_rw().unwrap(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + env_inner.create_db::(&tx_rw).unwrap(); + }); + }); +} + +/// [`Env::resize`]. +#[named] +fn resize(c: &mut Criterion) { + let env = TmpEnv::new(); + + // Resize env.by the OS page size. + let resize = Some(ResizeAlgorithm::FixedBytes(*PAGE_SIZE)); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + // This test is only valid for `Env`'s that need to resize manually. + if ConcreteEnv::MANUAL_RESIZE { + env.env.resize_map(resize); + } + }); + }); +} + +/// [`Env::current_map_size`]. +#[named] +fn current_map_size(c: &mut Criterion) { + let env = TmpEnv::new(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + // This test is only valid for `Env`'s that need to resize manually. + if ConcreteEnv::MANUAL_RESIZE { + black_box(env.env.current_map_size()); + } + }); + }); +} + +/// [`Env::disk_size_bytes`]. +#[named] +fn disk_size_bytes(c: &mut Criterion) { + let env = TmpEnv::new(); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + black_box(env.env.disk_size_bytes()).unwrap(); + }); + }); +} diff --git a/benches/criterion/cuprate-database/benches/main.rs b/benches/criterion/cuprate-database/benches/main.rs new file mode 100644 index 0000000..0dba969 --- /dev/null +++ b/benches/criterion/cuprate-database/benches/main.rs @@ -0,0 +1,11 @@ +#![expect(unused_crate_dependencies)] + +mod db; +mod env; +mod storable; + +criterion::criterion_main! { + db::benches, + env::benches, + storable::benches, +} diff --git a/benches/criterion/cuprate-database/benches/storable.rs b/benches/criterion/cuprate-database/benches/storable.rs new file mode 100644 index 0000000..e2add7d --- /dev/null +++ b/benches/criterion/cuprate-database/benches/storable.rs @@ -0,0 +1,66 @@ +//! [`Storable`] benchmarks. + +#![allow(unused_crate_dependencies, unused_attributes)] + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use function_name::named; + +use cuprate_blockchain::types::{Output, PreRctOutputId}; +use cuprate_database::Storable; + +use cuprate_criterion_database::{KEY, VALUE}; + +criterion_group! { + name = benches; + config = Criterion::default(); + targets = + pre_rct_output_id_as_bytes, + pre_rct_output_id_from_bytes, + output_as_bytes, + output_from_bytes +} +criterion_main!(benches); + +/// [`PreRctOutputId`] cast as bytes. +#[named] +fn pre_rct_output_id_as_bytes(c: &mut Criterion) { + c.bench_function(function_name!(), |b| { + b.iter(|| { + black_box(Storable::as_bytes(black_box(&KEY))); + }); + }); +} + +/// [`PreRctOutputId`] cast from bytes. +#[named] +fn pre_rct_output_id_from_bytes(c: &mut Criterion) { + let bytes = Storable::as_bytes(&KEY); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + let _: PreRctOutputId = black_box(Storable::from_bytes(black_box(bytes))); + }); + }); +} + +/// [`Output`] cast as bytes. +#[named] +fn output_as_bytes(c: &mut Criterion) { + c.bench_function(function_name!(), |b| { + b.iter(|| { + black_box(Storable::as_bytes(black_box(&VALUE))); + }); + }); +} + +/// [`Output`] cast from bytes. +#[named] +fn output_from_bytes(c: &mut Criterion) { + let bytes = Storable::as_bytes(&VALUE); + + c.bench_function(function_name!(), |b| { + b.iter(|| { + let _: Output = black_box(Storable::from_bytes(black_box(bytes))); + }); + }); +} diff --git a/benches/criterion/cuprate-database/src/constants.rs b/benches/criterion/cuprate-database/src/constants.rs new file mode 100644 index 0000000..bcb1919 --- /dev/null +++ b/benches/criterion/cuprate-database/src/constants.rs @@ -0,0 +1,17 @@ +//! General constants. + +use cuprate_blockchain::types::{Output, OutputFlags, PreRctOutputId}; + +/// The (1st) key. +pub const KEY: PreRctOutputId = PreRctOutputId { + amount: 1, + amount_index: 123, +}; + +/// The expected value. +pub const VALUE: Output = Output { + key: [35; 32], + height: 45_761_798, + output_flags: OutputFlags::empty(), + tx_idx: 2_353_487, +}; diff --git a/benches/criterion/cuprate-database/src/lib.rs b/benches/criterion/cuprate-database/src/lib.rs new file mode 100644 index 0000000..2c527dc --- /dev/null +++ b/benches/criterion/cuprate-database/src/lib.rs @@ -0,0 +1,7 @@ +#![allow(unused_crate_dependencies, reason = "used in benchmarks")] + +mod constants; +mod tmp_env; + +pub use constants::{KEY, VALUE}; +pub use tmp_env::TmpEnv; diff --git a/benches/criterion/cuprate-database/src/tmp_env.rs b/benches/criterion/cuprate-database/src/tmp_env.rs new file mode 100644 index 0000000..1a05e66 --- /dev/null +++ b/benches/criterion/cuprate-database/src/tmp_env.rs @@ -0,0 +1,88 @@ +//! An [`Env`] inside a [`TempDir`]. + +use tempfile::TempDir; + +use cuprate_blockchain::tables::Outputs; +use cuprate_database::{ + config::ConfigBuilder, resize::PAGE_SIZE, ConcreteEnv, DatabaseRw, Env, EnvInner, TxRw, +}; + +use crate::constants::{KEY, VALUE}; + +/// A temporary in-memory [`Env`]. +/// +/// This is a [`ConcreteEnv`] that uses [`TempDir`] as the +/// backing file location - this is an in-memory file on Linux. +pub struct TmpEnv { + pub env: ConcreteEnv, + pub tempdir: TempDir, +} + +impl Default for TmpEnv { + fn default() -> Self { + Self::new() + } +} + +impl TmpEnv { + /// Create an `Env` in a temporary directory. + /// + /// The directory is automatically removed after the [`TempDir`] is dropped. + #[expect(clippy::missing_panics_doc)] + pub fn new() -> Self { + let tempdir = tempfile::tempdir().unwrap(); + let path = tempdir.path().to_path_buf().into(); + let config = ConfigBuilder::new(path).low_power().build(); + let env = ConcreteEnv::open(config).unwrap(); + + // Resize to a very large map to prevent resize errors. + if ConcreteEnv::MANUAL_RESIZE { + // SAFETY: no write transactions exist yet. + unsafe { + env.env_inner() + .resize(PAGE_SIZE.get() * 1024 * 1024 * 1024) + .unwrap(); + } + } + + Self { env, tempdir } + } + + /// Inserts [`KEY`] and [`VALUE`] inside the [`Outputs`] table. + #[must_use] + pub fn with_key_value(self) -> Self { + let env_inner = self.env.env_inner(); + let tx_rw = env_inner.tx_rw().unwrap(); + let mut table = env_inner.open_db_rw::(&tx_rw).unwrap(); + + table.put(&KEY, &VALUE).unwrap(); + drop(table); + tx_rw.commit().unwrap(); + + drop(env_inner); + self + } + + /// Inserts [`VALUE`] inside the [`Outputs`] table 100 times. + /// + /// The key is an incrementing [`KEY`], i.e. the keys are + /// `KEY + {0..99}`, each one has [`VALUE`] as the value. + #[must_use] + pub fn with_key_value_100(self) -> Self { + let env_inner = self.env.env_inner(); + let tx_rw = env_inner.tx_rw().unwrap(); + let mut table = env_inner.open_db_rw::(&tx_rw).unwrap(); + + let mut key = KEY; + for _ in 0..100 { + table.put(&key, &VALUE).unwrap(); + key.amount += 1; + } + + drop(table); + tx_rw.commit().unwrap(); + + drop(env_inner); + self + } +} diff --git a/storage/database/src/backend/heed/env.rs b/storage/database/src/backend/heed/env.rs index 568379e..d934a1b 100644 --- a/storage/database/src/backend/heed/env.rs +++ b/storage/database/src/backend/heed/env.rs @@ -78,11 +78,12 @@ impl Drop for ConcreteEnv { // TODO: use tracing. // - let result = self.env.read().unwrap().clear_stale_readers(); - match result { - Ok(n) => println!("LMDB stale readers cleared: {n}"), - Err(e) => println!("LMDB stale reader clear error: {e:?}"), - } + drop(self.env.read().unwrap().clear_stale_readers()); + // let result = self.env.read().unwrap().clear_stale_readers(); + // match result { + // Ok(n) => println!("LMDB stale readers cleared: {n}"), + // Err(e) => println!("LMDB stale reader clear error: {e:?}"), + // } } }