chore(db): microbenchmarking for table serialization and db insertion (#513)

Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
This commit is contained in:
joshieDo
2023-01-31 12:08:51 +08:00
committed by GitHub
parent 6ef48829bd
commit cc43b72835
19 changed files with 722 additions and 64 deletions

6
.github/scripts/compare_iai.sh vendored Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
# This script should be run on the main branch, after running the iai benchmarks on the target branch.
# If the main branch has a better iai performance, exits in error. It ignores L2 differences, since they seem hard to stabilize across runs.
cargo bench --package reth-db --bench iai | tee /dev/tty | awk '/((L1)|(Ins)|(RAM)|(Est))+.*\(\+[1-9]+[0-9]*\..*%\)/{f=1} END{exit f}'

View File

@ -182,3 +182,43 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: Check if documentation builds
run: RUSTDOCFLAGS="-D warnings" cargo doc --all --no-deps --all-features --document-private-items
benchmarks:
# Pin to `20.04` instead of `ubuntu-latest`, until ubuntu-latest migration is complete
# See also <https://github.com/foundry-rs/foundry/issues/3827>
runs-on: ubuntu-20.04
steps:
- name: Install Valgrind
run: |
sudo apt install valgrind
- name: Checkout PR sources
uses: actions/checkout@v3
with:
ref: main
- uses: Swatinem/rust-cache@v1
with:
cache-on-failure: true
- name: Generate test-vectors
uses: actions-rs/cargo@v1
with:
command: run
args: --bin reth -- test-vectors tables
- name: Set main baseline
uses: actions-rs/cargo@v1
with:
command: bench
args: --package reth-db --bench iai
- name: Checkout main sources
uses: actions/checkout@v3
with:
clean: false
- name: Compare PR benchmark
shell: 'script -q -e -c "bash {0}"' # required to workaround /dev/tty not being available
run: |
./.github/scripts/compare_iai.sh

3
.gitignore vendored
View File

@ -15,3 +15,6 @@ target/
# Generated by MacOS
.DS_Store
# Generated test-vectors for DB
testdata/micro/db

3
Cargo.lock generated
View File

@ -3970,6 +3970,7 @@ dependencies = [
"metrics",
"metrics-exporter-prometheus",
"metrics-util",
"proptest",
"reth-consensus",
"reth-db",
"reth-discv4",
@ -4052,6 +4053,7 @@ dependencies = [
"modular-bitfield",
"page_size",
"parity-scale-codec",
"paste",
"postcard",
"proptest",
"proptest-derive",
@ -4063,6 +4065,7 @@ dependencies = [
"reth-primitives",
"secp256k1 0.24.2",
"serde",
"serde_json",
"tempfile",
"test-fuzz",
"thiserror",

View File

@ -8,7 +8,7 @@ readme = "README.md"
[dependencies]
# reth
reth-primitives = { path = "../../crates/primitives" }
reth-primitives = { path = "../../crates/primitives", features = ["arbitrary"] }
reth-db = {path = "../../crates/storage/db", features = ["mdbx", "test-utils"] }
# TODO: Temporary use of the test-utils feature
reth-provider = { path = "../../crates/storage/provider", features = ["test-utils"] }
@ -44,6 +44,9 @@ metrics = "0.20.1"
metrics-exporter-prometheus = { version = "0.11.0", features = ["http-listener"] }
metrics-util = "0.14.0"
# test vectors generation
proptest = "1.0"
# misc
eyre = "0.6.8"
clap = { version = "4.0", features = ["derive", "cargo"] }

View File

@ -2,7 +2,7 @@
use crate::{
db,
dirs::{LogsDir, PlatformPath},
node, p2p, stage, test_eth_chain,
node, p2p, stage, test_eth_chain, test_vectors,
};
use clap::{ArgAction, Args, Parser, Subcommand};
use reth_tracing::{
@ -25,6 +25,7 @@ pub async fn run() -> eyre::Result<()> {
Commands::Db(command) => command.execute().await,
Commands::Stage(command) => command.execute().await,
Commands::P2P(command) => command.execute().await,
Commands::TestVectors(command) => command.execute().await,
}
}
@ -51,6 +52,9 @@ pub enum Commands {
/// Run Ethereum blockchain tests
#[command(name = "test-chain")]
TestEthChain(test_eth_chain::Command),
/// Generate Test Vectors
#[command(name = "test-vectors")]
TestVectors(test_vectors::Command),
}
#[derive(Parser)]

View File

@ -170,6 +170,7 @@ impl Command {
BlockBodies,
BlockOmmers,
TxHashNumber,
PlainStorageState,
PlainAccountState,
BlockTransitionIndex,
TxTransitionIndex,

View File

@ -14,6 +14,7 @@ pub mod p2p;
pub mod prometheus_exporter;
pub mod stage;
pub mod test_eth_chain;
pub mod test_vectors;
pub use reth_staged_sync::utils;
use clap::Args;

View File

@ -0,0 +1,33 @@
//! Command for generating test vectors.
use clap::{Parser, Subcommand};
mod tables;
/// Generate test-vectors for different data types.
#[derive(Debug, Parser)]
pub struct Command {
#[clap(subcommand)]
command: Subcommands,
}
#[derive(Subcommand, Debug)]
/// `reth test-vectors` subcommands
pub enum Subcommands {
/// Generates test vectors for specified tables. If no table is specified, generate for all.
Tables {
/// List of table names. Case-sensitive.
names: Vec<String>,
},
}
impl Command {
/// Execute the command
pub async fn execute(self) -> eyre::Result<()> {
match self.command {
Subcommands::Tables { names } => {
tables::generate_vectors(names)?;
}
}
Ok(())
}
}

View File

@ -0,0 +1,170 @@
use std::collections::HashSet;
use eyre::Result;
use proptest::{
arbitrary::Arbitrary,
prelude::{any_with, ProptestConfig},
strategy::{Strategy, ValueTree},
test_runner::TestRunner,
};
use reth_db::{
table::{DupSort, Table},
tables,
};
use tracing::error;
const VECTORS_FOLDER: &str = "testdata/micro/db";
const PER_TABLE: usize = 1000;
/// Generates test vectors for specified `tables`. If list is empty, then generate for all tables.
pub(crate) fn generate_vectors(mut tables: Vec<String>) -> Result<()> {
let mut runner = TestRunner::new(ProptestConfig::default());
std::fs::create_dir_all(VECTORS_FOLDER)?;
macro_rules! generate_vector {
($table_type:ident, $per_table:expr, TABLE) => {
generate_table_vector::<tables::$table_type>(&mut runner, $per_table)?;
};
($table_type:ident, $per_table:expr, DUPSORT) => {
generate_dupsort_vector::<tables::$table_type>(&mut runner, $per_table)?;
};
}
macro_rules! generate {
([$(($table_type:ident, $per_table:expr, $table_or_dup:tt)),*]) => {
let all_tables = vec![$(stringify!($table_type).to_string(),)*];
if tables.is_empty() {
tables = all_tables;
}
for table in tables {
match table.as_str() {
$(
stringify!($table_type) => {
println!("Generating test vectors for {} <{}>.", stringify!($table_or_dup), tables::$table_type::NAME);
generate_vector!($table_type, $per_table, $table_or_dup);
},
)*
_ => {
error!(target: "reth::cli", "Unknown table: {}", table);
}
}
}
}
}
generate!([
(CanonicalHeaders, PER_TABLE, TABLE),
(HeaderTD, PER_TABLE, TABLE),
(HeaderNumbers, PER_TABLE, TABLE),
(Headers, PER_TABLE, TABLE),
(BlockBodies, PER_TABLE, TABLE),
(BlockOmmers, 100, TABLE),
(TxHashNumber, PER_TABLE, TABLE),
(BlockTransitionIndex, PER_TABLE, TABLE),
(TxTransitionIndex, PER_TABLE, TABLE),
(Transactions, 100, TABLE),
(PlainStorageState, PER_TABLE, DUPSORT),
(PlainAccountState, PER_TABLE, TABLE)
]);
Ok(())
}
/// Generates test-vectors for normal tables. Keys are sorted and not repeated.
fn generate_table_vector<T: Table>(runner: &mut TestRunner, per_table: usize) -> Result<()>
where
T::Key: Arbitrary + serde::Serialize + Ord + std::hash::Hash,
T::Value: Arbitrary + serde::Serialize,
{
let mut rows = vec![];
let mut seen_keys = HashSet::new();
let strat = proptest::collection::vec(
any_with::<(T::Key, T::Value)>((
<T::Key as Arbitrary>::Parameters::default(),
<T::Value as Arbitrary>::Parameters::default(),
)),
per_table - rows.len(),
)
.no_shrink()
.boxed();
while rows.len() < per_table {
// Generate all `per_table` rows: (Key, Value)
rows.extend(
&mut strat
.new_tree(runner)
.map_err(|e| eyre::eyre!("{e}"))?
.current()
.into_iter()
.filter(|e| seen_keys.insert(e.0.clone())),
);
}
// Sort them by `Key`
rows.sort_by(|a, b| a.0.cmp(&b.0));
save_to_file::<T>(rows)
}
/// Generates test-vectors for DUPSORT tables. Each key has multiple (subkey, value). Keys and
/// subkeys are sorted.
fn generate_dupsort_vector<T: Table>(runner: &mut TestRunner, per_table: usize) -> Result<()>
where
T: DupSort,
T::Key: Arbitrary + serde::Serialize + Ord + std::hash::Hash,
T::Value: Arbitrary + serde::Serialize + Ord,
{
let mut rows = vec![];
// We want to control our repeated keys
let mut seen_keys = HashSet::new();
let strat_values = proptest::collection::vec(
any_with::<T::Value>(<T::Value as Arbitrary>::Parameters::default()),
100..300,
)
.no_shrink()
.boxed();
let strat_keys =
any_with::<T::Key>(<T::Key as Arbitrary>::Parameters::default()).no_shrink().boxed();
while rows.len() < per_table {
let key: T::Key = strat_keys.new_tree(runner).map_err(|e| eyre::eyre!("{e}"))?.current();
if !seen_keys.insert(key.clone()) {
continue
}
let mut values: Vec<T::Value> =
strat_values.new_tree(runner).map_err(|e| eyre::eyre!("{e}"))?.current();
values.sort();
for value in values {
rows.push((key.clone(), value));
}
}
// Sort them by `Key`
rows.sort_by(|a, b| a.0.cmp(&b.0));
save_to_file::<T>(rows)
}
/// Save rows to file.
fn save_to_file<T: Table>(rows: Vec<(T::Key, T::Value)>) -> eyre::Result<()>
where
T::Key: serde::Serialize,
T::Value: serde::Serialize,
{
serde_json::to_writer_pretty(
std::io::BufWriter::new(
std::fs::File::create(format!("{VECTORS_FOLDER}/{}.json", T::NAME)).unwrap(),
),
&rows,
)
.map_err(|e| eyre::eyre!({ e }))
}

View File

@ -1,9 +1,10 @@
use super::{H256, U256};
use reth_codecs::Compact;
use serde::Serialize;
use reth_codecs::{derive_arbitrary, Compact};
use serde::{Deserialize, Serialize};
/// Account storage entry.
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize)]
#[derive_arbitrary(compact)]
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
pub struct StorageEntry {
/// Storage key.
pub key: H256,

View File

@ -63,10 +63,14 @@ arbitrary = { version = "1.1.7", features = ["derive"] }
proptest = { version = "1.0" }
proptest-derive = "0.3"
serde_json = "1.0"
paste = "1.0"
[features]
default = ["mdbx"]
test-utils = ["tempfile"]
test-utils = ["tempfile", "arbitrary"]
bench-postcard = ["bench"]
mdbx = ["reth-libmdbx"]
bench = []
@ -79,9 +83,9 @@ arbitrary = [
]
[[bench]]
name = "encoding_crit"
name = "criterion"
harness = false
[[bench]]
name = "encoding_iai"
name = "iai"
harness = false

View File

@ -0,0 +1,280 @@
#![allow(dead_code, unused_imports, non_snake_case)]
use criterion::{
black_box, criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion,
};
use reth_db::cursor::{DbDupCursorRO, DbDupCursorRW};
use std::time::Instant;
criterion_group!(benches, db, serialization);
criterion_main!(benches);
pub fn db(c: &mut Criterion) {
let mut group = c.benchmark_group("tables_db");
group.measurement_time(std::time::Duration::from_millis(200));
group.warm_up_time(std::time::Duration::from_millis(200));
measure_table_db::<CanonicalHeaders>(&mut group);
measure_table_db::<HeaderTD>(&mut group);
measure_table_db::<HeaderNumbers>(&mut group);
measure_table_db::<Headers>(&mut group);
measure_table_db::<BlockBodies>(&mut group);
measure_table_db::<BlockOmmers>(&mut group);
measure_table_db::<TxHashNumber>(&mut group);
measure_table_db::<BlockTransitionIndex>(&mut group);
measure_table_db::<TxTransitionIndex>(&mut group);
measure_table_db::<Transactions>(&mut group);
measure_dupsort_db::<PlainStorageState>(&mut group);
measure_table_db::<PlainAccountState>(&mut group);
}
pub fn serialization(c: &mut Criterion) {
let mut group = c.benchmark_group("tables_serialization");
group.measurement_time(std::time::Duration::from_millis(200));
group.warm_up_time(std::time::Duration::from_millis(200));
measure_table_serialization::<CanonicalHeaders>(&mut group);
measure_table_serialization::<HeaderTD>(&mut group);
measure_table_serialization::<HeaderNumbers>(&mut group);
measure_table_serialization::<Headers>(&mut group);
measure_table_serialization::<BlockBodies>(&mut group);
measure_table_serialization::<BlockOmmers>(&mut group);
measure_table_serialization::<TxHashNumber>(&mut group);
measure_table_serialization::<BlockTransitionIndex>(&mut group);
measure_table_serialization::<TxTransitionIndex>(&mut group);
measure_table_serialization::<Transactions>(&mut group);
measure_table_serialization::<PlainStorageState>(&mut group);
measure_table_serialization::<PlainAccountState>(&mut group);
}
/// Measures `Encode`, `Decode`, `Compress` and `Decompress`.
fn measure_table_serialization<T>(group: &mut BenchmarkGroup<WallTime>)
where
T: Table + Default,
T::Key: Default + Clone + for<'de> serde::Deserialize<'de>,
T::Value: Default + Clone + for<'de> serde::Deserialize<'de>,
{
let input = &load_vectors::<T>();
group.bench_function(format!("{}.KeyEncode", T::NAME), move |b| {
b.iter_with_setup(
|| input.clone(),
|input| {
black_box({
for (k, _, _, _) in input {
k.encode();
}
});
},
)
});
group.bench_function(format!("{}.KeyDecode", T::NAME), |b| {
b.iter_with_setup(
|| input.clone(),
|input| {
black_box({
for (_, k, _, _) in input {
let _ = <T as Table>::Key::decode(k);
}
});
},
)
});
group.bench_function(format!("{}.ValueCompress", T::NAME), move |b| {
b.iter_with_setup(
|| input.clone(),
|input| {
black_box({
for (_, _, v, _) in input {
v.compress();
}
});
},
)
});
group.bench_function(format!("{}.ValueDecompress", T::NAME), |b| {
b.iter_with_setup(
|| input.clone(),
|input| {
black_box({
for (_, _, _, v) in input {
let _ = <T as Table>::Value::decompress(v);
}
});
},
)
});
}
/// Measures `SeqWrite`, `RandomWrite`, `SeqRead` and `RandomRead` using `cursor` and `tx.put`.
fn measure_table_db<T>(group: &mut BenchmarkGroup<WallTime>)
where
T: Table + Default,
T::Key: Default + Clone + for<'de> serde::Deserialize<'de>,
T::Value: Default + Clone + for<'de> serde::Deserialize<'de>,
{
let input = &load_vectors::<T>();
let bench_db_path = Path::new(BENCH_DB_PATH);
group.bench_function(format!("{}.SeqWrite", T::NAME), |b| {
b.iter_with_setup(
|| {
// Reset DB
let _ = std::fs::remove_dir_all(bench_db_path);
(input.clone(), create_test_db_with_path::<WriteMap>(EnvKind::RW, bench_db_path))
},
|(input, db)| {
// Create TX
let tx = db.tx_mut().expect("tx");
let mut crsr = tx.cursor_write::<T>().expect("cursor");
black_box({
for (k, _, v, _) in input {
crsr.append(k, v).expect("submit");
}
tx.inner.commit().unwrap();
});
},
)
});
group.bench_function(format!("{}.RandomWrite", T::NAME), |b| {
b.iter_with_setup(
|| {
// Reset DB
let _ = std::fs::remove_dir_all(bench_db_path);
(input, create_test_db_with_path::<WriteMap>(EnvKind::RW, bench_db_path))
},
|(input, db)| {
// Create TX
let tx = db.tx_mut().expect("tx");
let mut crsr = tx.cursor_write::<T>().expect("cursor");
black_box({
for index in RANDOM_INDEXES {
let (k, _, v, _) = input.get(index).unwrap().clone();
crsr.insert(k, v).expect("submit");
}
tx.inner.commit().unwrap();
});
},
)
});
group.bench_function(format!("{}.SeqRead", T::NAME), |b| {
let db = set_up_db::<T>(bench_db_path, input);
b.iter(|| {
// Create TX
let tx = db.tx().expect("tx");
black_box({
let mut cursor = tx.cursor_read::<T>().expect("cursor");
let walker = cursor.walk(input.first().unwrap().0.clone()).unwrap();
for element in walker {
element.unwrap();
}
});
})
});
group.bench_function(format!("{}.RandomRead", T::NAME), |b| {
let db = set_up_db::<T>(bench_db_path, input);
b.iter(|| {
// Create TX
let tx = db.tx().expect("tx");
black_box({
for index in RANDOM_INDEXES {
let mut cursor = tx.cursor_read::<T>().expect("cursor");
cursor.seek_exact(input.get(index).unwrap().0.clone()).unwrap();
}
});
})
});
}
/// Measures `SeqWrite`, `RandomWrite` and `SeqRead` using `cursor_dup` and `tx.put`.
fn measure_dupsort_db<T>(group: &mut BenchmarkGroup<WallTime>)
where
T: Table + Default + DupSort,
T::Key: Default + Clone + for<'de> serde::Deserialize<'de>,
T::Value: Default + Clone + for<'de> serde::Deserialize<'de>,
T::SubKey: Default + Clone + for<'de> serde::Deserialize<'de>,
{
let input = &load_vectors::<T>();
let bench_db_path = Path::new(BENCH_DB_PATH);
group.bench_function(format!("{}.SeqWrite", T::NAME), |b| {
b.iter_with_setup(
|| {
// Reset DB
let _ = std::fs::remove_dir_all(bench_db_path);
(input.clone(), create_test_db_with_path::<WriteMap>(EnvKind::RW, bench_db_path))
},
|(input, db)| {
// Create TX
let tx = db.tx_mut().expect("tx");
let mut crsr = tx.cursor_dup_write::<T>().expect("cursor");
black_box({
for (k, _, v, _) in input {
crsr.append_dup(k, v).expect("submit");
}
tx.inner.commit().unwrap();
});
},
)
});
group.bench_function(format!("{}.RandomWrite", T::NAME), |b| {
b.iter_with_setup(
|| {
// Reset DB
let _ = std::fs::remove_dir_all(bench_db_path);
(input, create_test_db_with_path::<WriteMap>(EnvKind::RW, bench_db_path))
},
|(input, db)| {
// Create TX
let tx = db.tx_mut().expect("tx");
for index in RANDOM_INDEXES {
let (k, _, v, _) = input.get(index).unwrap().clone();
tx.put::<T>(k, v).unwrap();
}
tx.inner.commit().unwrap();
},
)
});
group.bench_function(format!("{}.SeqRead", T::NAME), |b| {
let db = set_up_db::<T>(bench_db_path, input);
b.iter(|| {
// Create TX
let tx = db.tx().expect("tx");
black_box({
let mut cursor = tx.cursor_dup_read::<T>().expect("cursor");
let walker = cursor
.walk_dup(input.first().unwrap().0.clone(), T::SubKey::default())
.unwrap();
for element in walker {
element.unwrap();
}
});
})
});
// group.bench_function(format!("{}.RandomRead", T::NAME), |b| {});
}
include!("./utils.rs");

View File

@ -1,33 +0,0 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
/// Benchmarks the encoding and decoding of `IntegerList` using criterion.
macro_rules! impl_criterion_encoding_benchmark {
($name:tt) => {
pub fn criterion_benchmark(c: &mut Criterion) {
let mut size = 0;
c.bench_function(stringify!($name), |b| {
b.iter(|| {
let encoded_size =
reth_db::tables::codecs::fuzz::IntegerList::encode_and_decode(black_box(
reth_primitives::IntegerList::default(),
))
.0;
if size == 0 {
size = encoded_size;
}
})
});
println!("Size (bytes): `{size}`");
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
};
}
#[cfg(not(feature = "bench-postcard"))]
impl_criterion_encoding_benchmark!(scale);
#[cfg(feature = "bench-postcard")]
impl_criterion_encoding_benchmark!(postcard);

View File

@ -1,21 +0,0 @@
use iai::{black_box, main};
use reth_db::tables::codecs;
/// Benchmarks the encoding and decoding of `Header` using iai.
macro_rules! impl_iai_encoding_benchmark {
($name:tt) => {
fn $name() {
codecs::fuzz::IntegerList::encode_and_decode(black_box(
reth_primitives::IntegerList::default(),
));
}
main!($name);
};
}
#[cfg(not(feature = "bench-postcard"))]
impl_iai_encoding_benchmark!(scale);
#[cfg(feature = "bench-postcard")]
impl_iai_encoding_benchmark!(postcard);

View File

@ -0,0 +1,79 @@
#![allow(dead_code, unused_imports, non_snake_case)]
use iai::main;
use paste::paste;
macro_rules! impl_iai_inner {
(
$(($name:tt, $mod:tt, $compress:tt, $decompress:tt, $encode:tt, $decode:tt, $seqread:tt, $randread:tt, $seqwrite:tt, $randwrite:tt))+
) => {
$(
mod $mod {
use iai::{black_box};
include!("./utils.rs");
pub fn $compress() {
for (_, _, v, _) in black_box(load_vectors::<reth_db::tables::$name>()) {
black_box(v.compress());
}
}
pub fn $decompress() {
for (_, _, _, comp) in black_box(load_vectors::<reth_db::tables::$name>()) {
let _ = black_box(<reth_db::tables::$name as Table>::Value::decompress(comp));
}
}
pub fn $encode() {
for (k, _, _, _) in black_box(load_vectors::<reth_db::tables::$name>()) {
black_box(k.encode());
}
}
pub fn $decode() {
for (_, enc, _, _) in black_box(load_vectors::<reth_db::tables::$name>()) {
let _ = black_box(<reth_db::tables::$name as Table>::Key::decode(enc));
}
}
pub fn $seqread() {}
pub fn $randread() {}
pub fn $seqwrite() {}
pub fn $randwrite() {}
}
use $mod::*;
)+
main!(
$(
$compress,
$decompress,
$encode,
$decode,
)+
);
};
}
macro_rules! impl_iai {
($($name:tt),+) => {
paste! {
impl_iai_inner!(
$(
( $name, [<$name _mod>], [<$name _ValueCompress>], [<$name _ValueDecompress>], [<$name _ValueEncode>], [<$name _ValueDecode>], [<$name _SeqRead>], [<$name _RandomRead>], [<$name _SeqWrite>], [<$name _RandomWrite>])
)+
);
}
};
}
impl_iai!(
CanonicalHeaders,
HeaderTD,
HeaderNumbers,
Headers,
BlockBodies,
BlockOmmers,
TxHashNumber,
BlockTransitionIndex,
TxTransitionIndex,
Transactions,
PlainStorageState,
PlainAccountState
);

View File

@ -0,0 +1,70 @@
use reth_db::{
cursor::{DbCursorRO, DbCursorRW},
database::Database,
mdbx::{test_utils::create_test_db_with_path, EnvKind, WriteMap},
table::*,
tables::*,
transaction::{DbTx, DbTxMut},
};
use std::path::Path;
/// Path where the DB is initialized for benchmarks.
const BENCH_DB_PATH: &str = "/tmp/reth-benches";
/// Used for RandomRead and RandomWrite benchmarks.
const RANDOM_INDEXES: [usize; 10] = [23, 2, 42, 5, 3, 99, 54, 0, 33, 64];
/// Returns bench vectors in the format: `Vec<(Key, EncodedKey, Value, CompressedValue)>`.
fn load_vectors<T: reth_db::table::Table>() -> Vec<(T::Key, bytes::Bytes, T::Value, bytes::Bytes)>
where
T: Default,
T::Key: Default + Clone + for<'de> serde::Deserialize<'de>,
T::Value: Default + Clone + for<'de> serde::Deserialize<'de>,
{
let list: Vec<(T::Key, T::Value)> = serde_json::from_reader(std::io::BufReader::new(
std::fs::File::open(format!(
"{}/../../../testdata/micro/db/{}.json",
env!("CARGO_MANIFEST_DIR"),
T::NAME
))
.expect("Test vectors not found. They can be generated from the workspace by calling `cargo run --bin reth -- test-vectors tables`."),
))
.unwrap();
list.into_iter()
.map(|(k, v)| {
(
k.clone(),
bytes::Bytes::copy_from_slice(k.encode().as_ref()),
v.clone(),
bytes::Bytes::copy_from_slice(v.compress().as_ref()),
)
})
.collect::<Vec<_>>()
}
/// Sets up a clear database at `bench_db_path`.
fn set_up_db<T>(
bench_db_path: &Path,
pair: &Vec<(<T as Table>::Key, bytes::Bytes, <T as Table>::Value, bytes::Bytes)>,
) -> reth_db::mdbx::Env<WriteMap>
where
T: Table + Default,
T::Key: Default + Clone,
T::Value: Default + Clone,
{
// Reset DB
let _ = std::fs::remove_dir_all(bench_db_path);
let db = create_test_db_with_path::<WriteMap>(EnvKind::RW, bench_db_path);
{
// Prepare data to be read
let tx = db.tx_mut().expect("tx");
for (k, _, v, _) in pair.clone() {
tx.put::<T>(k, v).expect("submit");
}
tx.inner.commit().unwrap();
}
db
}

View File

@ -65,7 +65,7 @@ pub type HeaderHash = H256;
/// element as BlockNumber, helps out with querying/sorting.
///
/// Since it's used as a key, the `BlockNumber` is not compressed when encoding it.
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, Ord, PartialOrd)]
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, Ord, PartialOrd, Hash)]
pub struct BlockNumHash(pub (BlockNumber, BlockHash));
impl std::fmt::Debug for BlockNumHash {

View File

@ -23,6 +23,20 @@ macro_rules! impl_fixed_arbitrary {
Decode::decode(buffer).map_err(|_| arbitrary::Error::IncorrectFormat)
}
}
#[cfg(any(test, feature = "arbitrary"))]
use proptest::strategy::Strategy;
#[cfg(any(test, feature = "arbitrary"))]
impl proptest::prelude::Arbitrary for $name {
type Parameters = ();
type Strategy = proptest::prelude::BoxedStrategy<$name>;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
proptest::collection::vec(proptest::arbitrary::any_with::<u8>(args), $size)
.prop_map(move |vec| Decode::decode(vec).unwrap())
.boxed()
}
}
};
}