Skip to content

Commit 95ab2d3

Browse files
CopilotSteake
andcommitted
Add comprehensive storage benchmarks
Co-authored-by: Steake <530040+Steake@users.noreply.github.com>
1 parent 70fdfb6 commit 95ab2d3

2 files changed

Lines changed: 336 additions & 0 deletions

File tree

crates/bitcell-state/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,8 @@ hex.workspace = true
1919
[dev-dependencies]
2020
proptest.workspace = true
2121
tempfile = "3.23.0"
22+
criterion.workspace = true
23+
24+
[[bench]]
25+
name = "storage_bench"
26+
harness = false
Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
2+
use bitcell_state::{Account, StorageManager};
3+
use tempfile::TempDir;
4+
5+
fn bench_block_storage(c: &mut Criterion) {
6+
let temp_dir = TempDir::new().unwrap();
7+
let storage = StorageManager::new(temp_dir.path()).unwrap();
8+
9+
let mut group = c.benchmark_group("block_storage");
10+
11+
// Benchmark single block storage
12+
group.bench_function("store_header", |b| {
13+
let mut height = 0u64;
14+
b.iter(|| {
15+
let hash = format!("hash_{:032}", height);
16+
let header = format!("header_data_{}", height);
17+
storage.store_header(
18+
black_box(height),
19+
black_box(hash.as_bytes()),
20+
black_box(header.as_bytes())
21+
).unwrap();
22+
height += 1;
23+
});
24+
});
25+
26+
// Benchmark block retrieval by height
27+
// First, store some blocks
28+
for i in 0..1000 {
29+
let hash = format!("hash_{:032}", i);
30+
let header = format!("header_data_{}", i);
31+
storage.store_header(i, hash.as_bytes(), header.as_bytes()).unwrap();
32+
}
33+
34+
group.bench_function("get_header_by_height", |b| {
35+
let mut height = 0u64;
36+
b.iter(|| {
37+
let result = storage.get_header_by_height(black_box(height % 1000)).unwrap();
38+
height += 1;
39+
result
40+
});
41+
});
42+
43+
group.bench_function("get_header_by_hash", |b| {
44+
let mut height = 0u64;
45+
b.iter(|| {
46+
let hash = format!("hash_{:032}", height % 1000);
47+
let result = storage.get_header_by_hash(black_box(hash.as_bytes())).unwrap();
48+
height += 1;
49+
result
50+
});
51+
});
52+
53+
group.finish();
54+
}
55+
56+
fn bench_transaction_indexing(c: &mut Criterion) {
57+
let temp_dir = TempDir::new().unwrap();
58+
let storage = StorageManager::new(temp_dir.path()).unwrap();
59+
60+
let mut group = c.benchmark_group("transaction_indexing");
61+
group.throughput(Throughput::Elements(1));
62+
63+
// Benchmark single transaction storage
64+
group.bench_function("store_transaction", |b| {
65+
let mut tx_num = 0u64;
66+
b.iter(|| {
67+
let tx_hash = format!("tx_hash_{:032}", tx_num);
68+
let sender = format!("sender_{:034}", tx_num % 100);
69+
let tx_data = format!("tx_data_{}", tx_num);
70+
71+
storage.store_transaction(
72+
black_box(tx_hash.as_bytes()),
73+
black_box(sender.as_bytes()),
74+
black_box(tx_data.as_bytes()),
75+
black_box(tx_num)
76+
).unwrap();
77+
tx_num += 1;
78+
});
79+
});
80+
81+
// Benchmark batch transaction storage
82+
for batch_size in [10, 50, 100].iter() {
83+
group.throughput(Throughput::Elements(*batch_size as u64));
84+
group.bench_with_input(
85+
BenchmarkId::new("store_transactions_batch", batch_size),
86+
batch_size,
87+
|b, &size| {
88+
let mut start_num = 0u64;
89+
b.iter(|| {
90+
let mut batch = Vec::with_capacity(size);
91+
for i in 0..size {
92+
let tx_num = start_num + i as u64;
93+
let tx_hash = format!("tx_hash_{:032}", tx_num);
94+
let sender = format!("sender_{:034}", tx_num % 100);
95+
let tx_data = format!("tx_data_{}", tx_num);
96+
97+
// Note: We need to keep these strings alive for the batch
98+
batch.push((tx_hash, sender, tx_data));
99+
}
100+
101+
let batch_refs: Vec<(&[u8], &[u8], &[u8], u64)> = batch
102+
.iter()
103+
.enumerate()
104+
.map(|(i, (h, s, d))| {
105+
(h.as_bytes(), s.as_bytes(), d.as_bytes(), start_num + i as u64)
106+
})
107+
.collect();
108+
109+
storage.store_transactions_batch(batch_refs).unwrap();
110+
start_num += size as u64;
111+
});
112+
}
113+
);
114+
}
115+
116+
// Store transactions for retrieval benchmarks
117+
for i in 0..10000 {
118+
let tx_hash = format!("tx_hash_{:032}", i);
119+
let sender = format!("sender_{:034}", i % 100);
120+
let tx_data = format!("tx_data_{}", i);
121+
storage.store_transaction(
122+
tx_hash.as_bytes(),
123+
sender.as_bytes(),
124+
tx_data.as_bytes(),
125+
i
126+
).unwrap();
127+
}
128+
129+
// Benchmark transaction retrieval by hash
130+
group.throughput(Throughput::Elements(1));
131+
group.bench_function("get_transaction", |b| {
132+
let mut tx_num = 0u64;
133+
b.iter(|| {
134+
let tx_hash = format!("tx_hash_{:032}", tx_num % 10000);
135+
let result = storage.get_transaction(black_box(tx_hash.as_bytes())).unwrap();
136+
tx_num += 1;
137+
result
138+
});
139+
});
140+
141+
// Benchmark getting transactions by sender
142+
group.bench_function("get_transactions_by_sender", |b| {
143+
let mut sender_id = 0u64;
144+
b.iter(|| {
145+
let sender = format!("sender_{:034}", sender_id % 100);
146+
let result = storage.get_transactions_by_sender(
147+
black_box(sender.as_bytes()),
148+
black_box(0)
149+
).unwrap();
150+
sender_id += 1;
151+
result
152+
});
153+
});
154+
155+
// Benchmark with limit
156+
group.bench_function("get_transactions_by_sender_limit_10", |b| {
157+
let mut sender_id = 0u64;
158+
b.iter(|| {
159+
let sender = format!("sender_{:034}", sender_id % 100);
160+
let result = storage.get_transactions_by_sender(
161+
black_box(sender.as_bytes()),
162+
black_box(10)
163+
).unwrap();
164+
sender_id += 1;
165+
result
166+
});
167+
});
168+
169+
group.finish();
170+
}
171+
172+
fn bench_state_snapshots(c: &mut Criterion) {
173+
let temp_dir = TempDir::new().unwrap();
174+
let storage = StorageManager::new(temp_dir.path()).unwrap();
175+
176+
let mut group = c.benchmark_group("state_snapshots");
177+
178+
// Benchmark snapshot creation with various sizes
179+
for data_size in [1024, 10240, 102400].iter() {
180+
group.throughput(Throughput::Bytes(*data_size as u64));
181+
group.bench_with_input(
182+
BenchmarkId::new("create_snapshot", data_size),
183+
data_size,
184+
|b, &size| {
185+
let mut height = 0u64;
186+
let state_root = vec![0u8; 32];
187+
let accounts_data = vec![0u8; size];
188+
189+
b.iter(|| {
190+
storage.create_snapshot(
191+
black_box(height),
192+
black_box(&state_root),
193+
black_box(&accounts_data)
194+
).unwrap();
195+
height += 1;
196+
});
197+
}
198+
);
199+
}
200+
201+
// Store snapshots for retrieval benchmarks
202+
for i in 0..100 {
203+
let state_root = vec![i as u8; 32];
204+
let accounts_data = vec![i as u8; 10240];
205+
storage.create_snapshot(i * 1000, &state_root, &accounts_data).unwrap();
206+
}
207+
208+
// Benchmark snapshot retrieval
209+
group.throughput(Throughput::Elements(1));
210+
group.bench_function("get_latest_snapshot", |b| {
211+
b.iter(|| {
212+
storage.get_latest_snapshot().unwrap()
213+
});
214+
});
215+
216+
group.bench_function("get_snapshot", |b| {
217+
let mut idx = 0u64;
218+
b.iter(|| {
219+
let height = (idx % 100) * 1000;
220+
let result = storage.get_snapshot(black_box(height)).unwrap();
221+
idx += 1;
222+
result
223+
});
224+
});
225+
226+
group.finish();
227+
}
228+
229+
fn bench_account_operations(c: &mut Criterion) {
230+
let temp_dir = TempDir::new().unwrap();
231+
let storage = StorageManager::new(temp_dir.path()).unwrap();
232+
233+
let mut group = c.benchmark_group("account_operations");
234+
235+
// Benchmark account storage
236+
group.bench_function("store_account", |b| {
237+
let mut account_id = 0u64;
238+
b.iter(|| {
239+
let address = {
240+
let mut addr = [0u8; 33];
241+
addr[0..8].copy_from_slice(&account_id.to_le_bytes());
242+
addr
243+
};
244+
let account = Account {
245+
balance: 1000 + account_id,
246+
nonce: account_id,
247+
};
248+
249+
storage.store_account(black_box(&address), black_box(&account)).unwrap();
250+
account_id += 1;
251+
});
252+
});
253+
254+
// Store accounts for retrieval benchmarks
255+
for i in 0u64..1000 {
256+
let address = {
257+
let mut addr = [0u8; 33];
258+
addr[0..8].copy_from_slice(&i.to_le_bytes());
259+
addr
260+
};
261+
let account = Account {
262+
balance: 1000 + i,
263+
nonce: i,
264+
};
265+
storage.store_account(&address, &account).unwrap();
266+
}
267+
268+
// Benchmark account retrieval
269+
group.bench_function("get_account", |b| {
270+
let mut account_id = 0u64;
271+
b.iter(|| {
272+
let address = {
273+
let mut addr = [0u8; 33];
274+
addr[0..8].copy_from_slice(&(account_id % 1000).to_le_bytes());
275+
addr
276+
};
277+
let result = storage.get_account(black_box(&address)).unwrap();
278+
account_id += 1;
279+
result
280+
});
281+
});
282+
283+
group.finish();
284+
}
285+
286+
fn bench_pruning(c: &mut Criterion) {
287+
let mut group = c.benchmark_group("pruning");
288+
group.sample_size(10); // Pruning is expensive, use fewer samples
289+
290+
// Benchmark simple pruning
291+
for block_count in [100, 500, 1000].iter() {
292+
group.bench_with_input(
293+
BenchmarkId::new("prune_old_blocks", block_count),
294+
block_count,
295+
|b, &count| {
296+
b.iter_batched(
297+
|| {
298+
// Setup: Create fresh database with blocks
299+
let temp_dir = TempDir::new().unwrap();
300+
let storage = StorageManager::new(temp_dir.path()).unwrap();
301+
302+
for i in 0..count {
303+
let hash = format!("hash_{:032}", i);
304+
let header = format!("header_{}", i);
305+
storage.store_header(i, hash.as_bytes(), header.as_bytes()).unwrap();
306+
}
307+
308+
(storage, temp_dir)
309+
},
310+
|(storage, _temp_dir)| {
311+
// Benchmark: Prune keeping last 50 blocks
312+
storage.prune_old_blocks(black_box(50)).unwrap();
313+
},
314+
criterion::BatchSize::LargeInput
315+
);
316+
}
317+
);
318+
}
319+
320+
group.finish();
321+
}
322+
323+
criterion_group!(
324+
benches,
325+
bench_block_storage,
326+
bench_transaction_indexing,
327+
bench_state_snapshots,
328+
bench_account_operations,
329+
bench_pruning
330+
);
331+
criterion_main!(benches);

0 commit comments

Comments
 (0)