Skip to content

Commit 82c1cb5

Browse files
weekday-grandine-iopovi
authored andcommitted
Add benchmarks for BlobSidecar serialization and deserialization
Generalize some code in eth2_cache_utils and benches to work on collections containing any type.
1 parent 67777b7 commit 82c1cb5

File tree

6 files changed

+108
-44
lines changed

6 files changed

+108
-44
lines changed

Cargo.lock

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

benches/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ itertools = { workspace = true }
2929
num-integer = { workspace = true }
3030
openssl = { workspace = true }
3131
operation_pools = { workspace = true }
32+
serde = { workspace = true }
3233
serde_json = { workspace = true }
3334
serde_utils = { workspace = true }
3435
sha2 = { workspace = true }

benches/benches/types.rs

Lines changed: 84 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@ use std::sync::Arc;
1010
use allocator as _;
1111
use criterion::{Criterion, Throughput};
1212
use easy_ext::ext;
13-
use eth2_cache_utils::{goerli, mainnet, medalla, LazyBeaconBlocks, LazyBeaconState};
13+
use eth2_cache_utils::{
14+
goerli, mainnet, medalla, LazyBeaconBlocks, LazyBeaconState, LazyBlobSidecars,
15+
};
1416
use itertools::Itertools as _;
15-
use ssz::{SszRead as _, SszWrite as _};
17+
use serde::{Deserialize, Serialize};
18+
use ssz::{SszRead, SszWrite};
1619
use types::{
1720
combined::{BeaconState, SignedBeaconBlock},
1821
config::Config,
22+
deneb::containers::BlobSidecar,
1923
preset::Preset,
2024
};
2125

@@ -73,6 +77,10 @@ fn main() {
7377
&Config::medalla(),
7478
&medalla::BEACON_BLOCKS_UP_TO_SLOT_128,
7579
)
80+
.benchmark_blob_sidecars(
81+
"mainnet Deneb blob sidecars from 32 slots",
82+
&mainnet::DENEB_BLOB_SIDECARS_FROM_32_SLOTS,
83+
)
7684
.final_summary();
7785
}
7886

@@ -120,35 +128,82 @@ impl Criterion {
120128
config: &Config,
121129
blocks: &LazyBeaconBlocks<P>,
122130
) -> &mut Self {
123-
let ssz_bytes = LazyCell::new(|| blocks_to_ssz(blocks.force()));
124-
let json_bytes = LazyCell::new(|| blocks_to_json_directly(blocks.force()));
131+
let ssz_bytes = LazyCell::new(|| slice_to_ssz(blocks.force()));
132+
let json_bytes = LazyCell::new(|| slice_to_json_directly(blocks.force()));
125133

126134
self.benchmark_group(group_name)
127135
.throughput(Throughput::Elements(blocks.count()))
128136
.bench_function("from SSZ", |bencher| {
129137
let ssz_bytes = ssz_bytes.iter().map(Vec::as_slice);
130138

131-
bencher.iter_with_large_drop(|| blocks_from_ssz::<P>(config, ssz_bytes.clone()))
139+
bencher.iter_with_large_drop(|| {
140+
vec_from_ssz::<Config, Arc<SignedBeaconBlock<P>>>(config, ssz_bytes.clone())
141+
})
132142
})
133143
.bench_function("to SSZ", |bencher| {
134144
let blocks = blocks.force();
135145

136-
bencher.iter_with_large_drop(|| blocks_to_ssz(blocks))
146+
bencher.iter_with_large_drop(|| slice_to_ssz(blocks))
137147
})
138148
.bench_function("from JSON", |bencher| {
139149
let json_bytes = json_bytes.iter().map(Vec::as_slice);
140150

141-
bencher.iter_with_large_drop(|| blocks_from_json::<P>(json_bytes.clone()))
151+
bencher.iter_with_large_drop(|| {
152+
vec_from_json::<Arc<SignedBeaconBlock<P>>>(json_bytes.clone())
153+
})
142154
})
143155
.bench_function("to JSON directly", |bencher| {
144156
let blocks = blocks.force();
145157

146-
bencher.iter_with_large_drop(|| blocks_to_json_directly(blocks))
158+
bencher.iter_with_large_drop(|| slice_to_json_directly(blocks))
147159
})
148160
.bench_function("to JSON via serde_utils::stringify", |bencher| {
149161
let blocks = blocks.force();
150162

151-
bencher.iter_with_large_drop(|| blocks_to_json_via_stringify(blocks))
163+
bencher.iter_with_large_drop(|| slice_to_json_via_stringify(blocks))
164+
});
165+
166+
self
167+
}
168+
169+
fn benchmark_blob_sidecars<P: Preset>(
170+
&mut self,
171+
group_name: &str,
172+
blob_sidecars: &LazyBlobSidecars<P>,
173+
) -> &mut Self {
174+
let ssz_bytes = LazyCell::new(|| slice_to_ssz(blob_sidecars.force()));
175+
let json_bytes = LazyCell::new(|| slice_to_json_directly(blob_sidecars.force()));
176+
177+
self.benchmark_group(group_name)
178+
.throughput(Throughput::Elements(blob_sidecars.count()))
179+
.bench_function("from SSZ", |bencher| {
180+
let ssz_bytes = ssz_bytes.iter().map(Vec::as_slice);
181+
182+
bencher.iter_with_large_drop(|| {
183+
vec_from_ssz::<(), Arc<BlobSidecar<P>>>(&(), ssz_bytes.clone())
184+
})
185+
})
186+
.bench_function("to SSZ", |bencher| {
187+
let blob_sidecars = blob_sidecars.force();
188+
189+
bencher.iter_with_large_drop(|| slice_to_ssz(blob_sidecars))
190+
})
191+
.bench_function("from JSON", |bencher| {
192+
let json_bytes = json_bytes.iter().map(Vec::as_slice);
193+
194+
bencher.iter_with_large_drop(|| {
195+
vec_from_json::<Arc<BlobSidecar<P>>>(json_bytes.clone())
196+
})
197+
})
198+
.bench_function("to JSON directly", |bencher| {
199+
let blob_sidecars = blob_sidecars.force();
200+
201+
bencher.iter_with_large_drop(|| slice_to_json_directly(blob_sidecars))
202+
})
203+
.bench_function("to JSON via serde_utils::stringify", |bencher| {
204+
let blob_sidecars = blob_sidecars.force();
205+
206+
bencher.iter_with_large_drop(|| slice_to_json_via_stringify(blob_sidecars))
152207
});
153208

154209
self
@@ -175,47 +230,46 @@ fn state_to_json_via_stringify(state: &BeaconState<impl Preset>) -> Vec<u8> {
175230
.expect("state should be serializable to JSON")
176231
}
177232

178-
fn blocks_from_ssz<'bytes, P: Preset>(
179-
config: &Config,
233+
fn vec_from_ssz<'bytes, C, T: SszRead<C>>(
234+
context: &C,
180235
bytes: impl IntoIterator<Item = &'bytes [u8]>,
181-
) -> Vec<Arc<SignedBeaconBlock<P>>> {
236+
) -> Vec<T> {
182237
bytes
183238
.into_iter()
184-
.map(|bytes| Arc::from_ssz(config, bytes))
239+
.map(|bytes| T::from_ssz(context, bytes))
185240
.try_collect()
186-
.expect("blocks have already been successfully deserialized")
241+
.expect("iterator items have already been successfully deserialized from SSZ")
187242
}
188243

189-
fn blocks_to_ssz(blocks: &[Arc<SignedBeaconBlock<impl Preset>>]) -> Vec<Vec<u8>> {
190-
blocks
191-
.iter()
192-
.map(Arc::to_ssz)
193-
.try_collect()
194-
.expect("blocks can be serialized because they have already been serialized to a file")
244+
fn slice_to_ssz(slice: &[impl SszWrite]) -> Vec<Vec<u8>> {
245+
slice.iter().map(SszWrite::to_ssz).try_collect().expect(
246+
"slice elements can be serialized to SSZ because \
247+
they have already been serialized to a file",
248+
)
195249
}
196250

197-
fn blocks_from_json<'bytes, P: Preset>(
251+
fn vec_from_json<'bytes, T: Deserialize<'bytes>>(
198252
bytes: impl IntoIterator<Item = &'bytes [u8]>,
199-
) -> Vec<Arc<SignedBeaconBlock<P>>> {
253+
) -> Vec<T> {
200254
bytes
201255
.into_iter()
202256
.map(serde_json::from_slice)
203257
.try_collect()
204-
.expect("blocks should be deserializable from JSON")
258+
.expect("iterator items should be deserializable from JSON")
205259
}
206260

207-
fn blocks_to_json_directly(blocks: &[Arc<SignedBeaconBlock<impl Preset>>]) -> Vec<Vec<u8>> {
208-
blocks
261+
fn slice_to_json_directly(slice: &[impl Serialize]) -> Vec<Vec<u8>> {
262+
slice
209263
.iter()
210264
.map(serde_json::to_vec)
211265
.try_collect()
212-
.expect("blocks should be serializable to JSON")
266+
.expect("slice elements should be serializable to JSON")
213267
}
214268

215-
fn blocks_to_json_via_stringify(blocks: &[Arc<SignedBeaconBlock<impl Preset>>]) -> Vec<Vec<u8>> {
216-
blocks
269+
fn slice_to_json_via_stringify(slice: &[impl Serialize]) -> Vec<Vec<u8>> {
270+
slice
217271
.iter()
218-
.map(|block| serde_utils::stringify(block).and_then(|json| serde_json::to_vec(&json)))
272+
.map(|element| serde_utils::stringify(element).and_then(|json| serde_json::to_vec(&json)))
219273
.try_collect()
220-
.expect("blocks should be serializable to JSON")
274+
.expect("slice elements should be serializable to JSON")
221275
}

eth2_cache_utils/src/generic.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ use types::{
1616
traits::SignedBeaconBlock as _,
1717
};
1818

19+
pub type LazyBeaconBlocks<P> = LazyVec<Arc<SignedBeaconBlock<P>>>;
20+
pub type LazyBlobSidecars<P> = LazyVec<Arc<BlobSidecar<P>>>;
21+
1922
// `LazyLock` implements `core::ops::Deref`, which is more confusing than useful.
2023
// Explicit forcing is better.
2124

@@ -51,20 +54,17 @@ impl<P: Preset> LazyBeaconBlock<P> {
5154
}
5255
}
5356

54-
pub struct LazyBeaconBlocks<P: Preset> {
57+
pub struct LazyVec<T> {
5558
expected_count: u64,
56-
blocks: LazyLock<Vec<Arc<SignedBeaconBlock<P>>>>,
59+
elements: LazyLock<Vec<T>>,
5760
}
5861

59-
impl<P: Preset> LazyBeaconBlocks<P> {
62+
impl<T> LazyVec<T> {
6063
#[must_use]
61-
pub(crate) const fn new(
62-
expected_count: u64,
63-
thunk: fn() -> Vec<Arc<SignedBeaconBlock<P>>>,
64-
) -> Self {
64+
pub(crate) const fn new(expected_count: u64, thunk: fn() -> Vec<T>) -> Self {
6565
Self {
6666
expected_count,
67-
blocks: LazyLock::new(thunk),
67+
elements: LazyLock::new(thunk),
6868
}
6969
}
7070

@@ -73,13 +73,13 @@ impl<P: Preset> LazyBeaconBlocks<P> {
7373
self.expected_count
7474
}
7575

76-
pub fn force(&self) -> &[Arc<SignedBeaconBlock<P>>] {
77-
let blocks = LazyLock::force(&self.blocks);
78-
let actual_count = u64::try_from(blocks.len()).expect("block count should fit in u64");
76+
pub fn force(&self) -> &[T] {
77+
let elements = LazyLock::force(&self.elements);
78+
let actual_count = u64::try_from(elements.len()).expect("count should fit in u64");
7979

8080
assert_eq!(actual_count, self.expected_count);
8181

82-
blocks
82+
elements
8383
}
8484
}
8585

eth2_cache_utils/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//! [`eth2-cache`]: ../../../eth2-cache/
77
//! [`predefined_chains`]: ../predefined_chains/index.html
88
9-
pub use generic::{LazyBeaconBlock, LazyBeaconBlocks, LazyBeaconState};
9+
pub use generic::{LazyBeaconBlock, LazyBeaconBlocks, LazyBeaconState, LazyBlobSidecars};
1010

1111
pub mod goerli;
1212
pub mod holesky;

eth2_cache_utils/src/mainnet.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use types::{
1010
preset::Mainnet,
1111
};
1212

13-
use crate::generic::{self, LazyBeaconBlock, LazyBeaconBlocks, LazyBeaconState};
13+
use crate::generic::{self, LazyBeaconBlock, LazyBeaconBlocks, LazyBeaconState, LazyBlobSidecars};
1414

1515
const CASE: Case = Case {
1616
case_path_relative_to_workspace_root: "eth2-cache/mainnet",
@@ -64,6 +64,14 @@ pub static ALTAIR_BEACON_BLOCKS_FROM_8192_SLOTS: LazyBeaconBlocks<Mainnet> =
6464
pub static CAPELLA_BEACON_BLOCKS_FROM_244816_SLOTS: LazyBeaconBlocks<Mainnet> =
6565
LazyBeaconBlocks::new(127, || beacon_blocks(7_834_112..=7_834_240, 7));
6666

67+
pub static DENEB_BLOB_SIDECARS_FROM_32_SLOTS: LazyBlobSidecars<Mainnet> =
68+
LazyBlobSidecars::new(129, || {
69+
blob_sidecars(9_481_344..=9_481_393, 7)
70+
.into_values()
71+
.flatten()
72+
.collect()
73+
});
74+
6775
#[must_use]
6876
pub fn beacon_blocks(
6977
slots: RangeInclusive<Slot>,

0 commit comments

Comments
 (0)