Skip to content

Commit cc4cdc4

Browse files
committed
chore: add micro-benchmarks for sorting threshold optimization
1 parent 7e6a59b commit cc4cdc4

File tree

3 files changed

+124
-0
lines changed

3 files changed

+124
-0
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/trie/db/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ proptest-arbitrary-interop.workspace = true
4747
serde_json.workspace = true
4848
similar-asserts.workspace = true
4949

50+
criterion.workspace = true
51+
rayon.workspace = true
52+
rand.workspace = true
53+
5054
[features]
5155
metrics = ["reth-trie/metrics"]
5256
serde = [
@@ -68,3 +72,7 @@ test-utils = [
6872
"reth-provider/test-utils",
6973
"reth-trie/test-utils",
7074
]
75+
76+
[[bench]]
77+
name = "sorting_par_exp"
78+
harness = false
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#![allow(missing_docs, unreachable_pub)]
2+
3+
use alloy_primitives::{Address, B256, U256};
4+
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput};
5+
use rand::{rngs::StdRng, Rng, SeedableRng};
6+
use rayon::prelude::*;
7+
8+
/// Synthetic storage data: a list of accounts, each with a list of slots.
9+
type MockStorage = Vec<(Address, Vec<(B256, U256)>)>;
10+
11+
#[derive(Clone, Copy, Debug)]
12+
enum Distribution {
13+
Uniform(usize),
14+
Skewed { light: usize, heavy: usize },
15+
}
16+
17+
/// Generate random synthetic data with a fixed seed for reproducibility.
18+
fn generate_data(num_accounts: usize, dist: Distribution) -> MockStorage {
19+
let mut rng = StdRng::seed_from_u64(42);
20+
21+
(0..num_accounts)
22+
.map(|_| {
23+
let address = Address::random();
24+
let slot_count = match dist {
25+
Distribution::Uniform(n) => n,
26+
Distribution::Skewed { light, heavy } => {
27+
// 5% chance of being a heavy account
28+
if rng.random_bool(0.05) {
29+
heavy
30+
} else {
31+
light
32+
}
33+
}
34+
};
35+
36+
let slots = (0..slot_count)
37+
.map(|_| (B256::random(), U256::from(rng.random::<u64>())))
38+
.collect();
39+
(address, slots)
40+
})
41+
.collect()
42+
}
43+
44+
/// Loop sequentially, sort slots sequentially.
45+
fn process_sequential(mut data: MockStorage) {
46+
for (_addr, slots) in &mut data {
47+
slots.sort_unstable_by_key(|(k, _)| *k);
48+
}
49+
}
50+
51+
/// Accounts in parallel, sort slots sequentially.
52+
fn process_par_iter_accounts(mut data: MockStorage) {
53+
data.par_iter_mut().for_each(|(_addr, slots)| {
54+
slots.sort_unstable_by_key(|(k, _)| *k);
55+
});
56+
}
57+
58+
/// Accounts sequentially, sort slots parallel.
59+
fn process_par_sort_slots(mut data: MockStorage) {
60+
for (_addr, slots) in &mut data {
61+
slots.par_sort_unstable_by_key(|(k, _)| *k);
62+
}
63+
}
64+
65+
fn bench_storage_sort(c: &mut Criterion) {
66+
let mut group = c.benchmark_group("sorting_par_exp");
67+
68+
let scenarios = vec![
69+
// Finding Account Threshold.
70+
("Acc_Low", 100, Distribution::Uniform(4)),
71+
("Acc_Med", 1_000, Distribution::Uniform(4)),
72+
("Acc_Med_High", 2_500, Distribution::Uniform(4)),
73+
("Acc_High", 10_000, Distribution::Uniform(4)),
74+
// Finding Slot Threshold.
75+
("Slots_Med", 10, Distribution::Uniform(100)),
76+
("Slots_High", 10, Distribution::Uniform(5_000)),
77+
("Slots_Massive", 10, Distribution::Uniform(50_000)),
78+
// 10k accounts. Most have 4 slots, 5% have 2k slots.
79+
("Skewed_2.5k", 2_500, Distribution::Skewed { light: 4, heavy: 2_000 }),
80+
("Skewed_10k", 10_000, Distribution::Skewed { light: 4, heavy: 2_000 }),
81+
];
82+
83+
for (name, accounts, dist) in scenarios {
84+
let input = generate_data(accounts, dist);
85+
86+
let total_slots: usize = input.iter().map(|(_, s)| s.len()).sum();
87+
group.throughput(Throughput::Elements(total_slots as u64));
88+
89+
// Sequential
90+
group.bench_with_input(BenchmarkId::new("Sequential", name), &input, |b, data| {
91+
b.iter_batched(|| data.clone(), process_sequential, BatchSize::LargeInput)
92+
});
93+
94+
// Parallel Accounts
95+
group.bench_with_input(BenchmarkId::new("Par_Accounts", name), &input, |b, data| {
96+
b.iter_batched(|| data.clone(), process_par_iter_accounts, BatchSize::LargeInput)
97+
});
98+
99+
// Parallel Slots
100+
if let Distribution::Uniform(s) = dist &&
101+
s >= 100
102+
{
103+
group.bench_with_input(BenchmarkId::new("Par_Inner_Slots", name), &input, |b, data| {
104+
b.iter_batched(|| data.clone(), process_par_sort_slots, BatchSize::LargeInput)
105+
});
106+
}
107+
}
108+
109+
group.finish();
110+
}
111+
112+
criterion_group!(sorting_par_exp, bench_storage_sort);
113+
criterion_main!(sorting_par_exp);

0 commit comments

Comments
 (0)