Merge branch 'blockchain-benches' into criterion

This commit is contained in:
hinto.janai 2024-11-28 09:06:35 -05:00
commit 7b31745db3
No known key found for this signature in database
GPG key ID: D47CE05FA175A499
8 changed files with 287 additions and 0 deletions

15
Cargo.lock generated
View file

@ -820,6 +820,21 @@ dependencies = [
"function_name",
]
[[package]]
name = "cuprate-criterion-blockchain"
version = "0.0.0"
dependencies = [
"criterion",
"cuprate-blockchain",
"cuprate-helper",
"cuprate-test-utils",
"cuprate-types",
"function_name",
"rand",
"serde_json",
"tempfile",
]
[[package]]
name = "cuprate-criterion-example"
version = "0.0.0"

View file

@ -14,6 +14,7 @@ members = [
"benches/criterion/cuprate-database",
"benches/criterion/cuprate-cryptonight",
"benches/criterion/cuprate-rpc-types",
"benches/criterion/cuprate-blockchain",
# Consensus
"consensus",

View file

@ -0,0 +1,28 @@
[package]
name = "cuprate-criterion-blockchain"
version = "0.0.0"
edition = "2021"
description = "Criterion benchmarking for cuprate-blockchain"
license = "MIT"
authors = ["hinto-janai"]
repository = "https://github.com/Cuprate/cuprate/tree/main/benches/criterion/cuprate-blockchain"
keywords = ["cuprate", "blockchain", "criterion", "benchmark"]
[dependencies]
cuprate-blockchain = { path = "../../../storage/blockchain" }
cuprate-test-utils = { path = "../../../test-utils" }
cuprate-types = { path = "../../../types", default-features = false }
cuprate-helper = { path = "../../../helper", features = ["cast"] }
criterion = { workspace = true }
function_name = { workspace = true }
serde_json = { workspace = true, features = ["default"] }
tempfile = { workspace = true }
rand = { workspace = true }
[[bench]]
name = "main"
harness = false
[lints]
workspace = true

View file

@ -0,0 +1,140 @@
//! Benchmarks for [`block`] and [`alt_block`] functions.
#![allow(unused_attributes, unused_crate_dependencies)]
use std::{num::NonZeroU64, time::Instant};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use cuprate_helper::cast::usize_to_u64;
use function_name::named;
use cuprate_blockchain::{
cuprate_database::{Env, EnvInner},
ops::{alt_block, block},
tables::OpenTables,
};
use cuprate_test_utils::data::{BLOCK_V16_TX0, BLOCK_V1_TX2, BLOCK_V9_TX3};
use cuprate_types::{AltBlockInformation, ChainId, VerifiedBlockInformation};
use cuprate_criterion_blockchain::generate_fake_blocks;
criterion_group! {
name = benches;
config = Criterion::default();
targets =
add_block_v1_tx2,
add_block_v9_tx3,
add_block_v16_tx0,
add_alt_block_v1_tx2,
add_alt_block_v9_tx3,
add_alt_block_v16_tx0,
}
criterion_main!(benches);
/// Inner function for benchmarking [`block::add_block`].
#[expect(clippy::significant_drop_tightening)]
fn add_block_inner(c: &mut Criterion, function_name: &str, block: &VerifiedBlockInformation) {
let env = cuprate_criterion_blockchain::TmpEnv::new();
c.bench_function(function_name, |b| {
// We use `iter_custom` because we need to generate an
// appropriate amount of blocks and only time the `add_block`.
b.iter_custom(|count| {
let blocks = black_box(generate_fake_blocks(block, count));
let env_inner = env.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap();
let start = Instant::now();
for block in &blocks {
let block = black_box(block);
black_box(block::add_block(block, &mut tables)).unwrap();
}
start.elapsed()
});
});
}
#[named]
fn add_block_v1_tx2(c: &mut Criterion) {
add_block_inner(c, function_name!(), &BLOCK_V1_TX2);
}
#[named]
fn add_block_v9_tx3(c: &mut Criterion) {
add_block_inner(c, function_name!(), &BLOCK_V9_TX3);
}
#[named]
fn add_block_v16_tx0(c: &mut Criterion) {
add_block_inner(c, function_name!(), &BLOCK_V16_TX0);
}
/// Inner function for benchmarking [`alt_block::add_alt_block`].
#[expect(clippy::significant_drop_tightening)]
fn add_alt_block_inner(c: &mut Criterion, function_name: &str, block: &VerifiedBlockInformation) {
let env = cuprate_criterion_blockchain::TmpEnv::new();
// We must have at least 1 block or else `add_alt_block` will panic.
{
let env_inner = env.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap();
let mut block = BLOCK_V1_TX2.clone();
block.height = 0;
block::add_block(&block, &mut tables).unwrap();
}
c.bench_function(function_name, |b| {
// We use `iter_custom` because we need to generate an
// appropriate amount of blocks and only time the `add_block`.
b.iter_custom(|count| {
// Map the block to a fake alt block.
let blocks = generate_fake_blocks(block, count)
.into_iter()
.enumerate()
.map(|(i, b)| AltBlockInformation {
block: b.block,
block_blob: b.block_blob,
txs: b.txs,
block_hash: b.block_hash,
pow_hash: b.pow_hash,
height: b.height + 1,
weight: b.weight,
long_term_weight: b.long_term_weight,
cumulative_difficulty: b.cumulative_difficulty,
chain_id: ChainId(NonZeroU64::new(usize_to_u64(i) + 1).unwrap()),
})
.collect::<Vec<AltBlockInformation>>();
let env_inner = env.env.env_inner();
let tx_rw = env_inner.tx_rw().unwrap();
let mut tables = env_inner.open_tables_mut(&tx_rw).unwrap();
let start = Instant::now();
for block in &blocks {
let block = black_box(block);
black_box(alt_block::add_alt_block(block, &mut tables)).unwrap();
}
start.elapsed()
});
});
}
#[named]
fn add_alt_block_v1_tx2(c: &mut Criterion) {
add_alt_block_inner(c, function_name!(), &BLOCK_V1_TX2);
}
#[named]
fn add_alt_block_v9_tx3(c: &mut Criterion) {
add_alt_block_inner(c, function_name!(), &BLOCK_V9_TX3);
}
#[named]
fn add_alt_block_v16_tx0(c: &mut Criterion) {
add_alt_block_inner(c, function_name!(), &BLOCK_V16_TX0);
}

View file

@ -0,0 +1,7 @@
#![allow(unused_crate_dependencies)]
mod block;
criterion::criterion_main! {
block::benches,
}

View file

@ -0,0 +1,36 @@
use rand::Rng;
use cuprate_helper::cast::u64_to_usize;
use cuprate_types::VerifiedBlockInformation;
/// Generate fake [`VerifiedBlockInformation`]s.
///
/// This function generates fake blocks,
/// cloning the `base` block `count` amount of times,
/// starting at height `0` and sequentially incrementing,
/// i.e. block height `{0,1,2,...}`.
///
/// Each block has a random [`VerifiedBlockInformation::block_hash`].
///
/// # Hack
/// This is used for these benchmarks because
/// [`cuprate_blockchain::ops::block::add_block`]
/// asserts that blocks with non-sequential heights cannot be added.
/// To get around this, we manually edit the block heights.
///
/// The hash must also be faked as
/// [`cuprate_blockchain::ops::blockchain::chain_height`]
/// which is used for an [`assert`] relies on the `hash -> height` table.
pub fn generate_fake_blocks(
base: &VerifiedBlockInformation,
count: u64,
) -> Vec<VerifiedBlockInformation> {
(0..count)
.map(|height| {
let mut block = base.clone();
block.height = u64_to_usize(height);
block.block_hash = rand::thread_rng().r#gen();
block
})
.collect()
}

View file

@ -0,0 +1,7 @@
#![allow(unused_crate_dependencies, reason = "used in benchmarks")]
mod blocks;
mod tmp_env;
pub use blocks::generate_fake_blocks;
pub use tmp_env::TmpEnv;

View file

@ -0,0 +1,53 @@
//! An [`Env`] inside a [`TempDir`].
use tempfile::TempDir;
use cuprate_blockchain::{
config::ReaderThreads,
cuprate_database::{config::ConfigBuilder, resize::PAGE_SIZE, ConcreteEnv, Env},
};
/// 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 db_config = ConfigBuilder::new(path).low_power().build();
let reader_threads = ReaderThreads::One;
let config = cuprate_blockchain::config::Config {
db_config,
reader_threads,
};
let env = cuprate_blockchain::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 }
}
}