Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,16 @@ arbitrary = { version = "1.0", features = ["derive"], optional = true }
itertools = "0.14.0"

[dev-dependencies]
criterion = "0.7.0"
ethereum_ssz_derive = "0.9.0"
serde_json = "1.0.0"
tree_hash_derive = "0.10.0"

[[bench]]
harness = false
name = "encode_decode"

# FIXME: remove before release
[patch.crates-io]
ethereum_ssz = { git = "https://github.com/sigp/ethereum_ssz", branch = "main" }
ethereum_ssz_derive = { git = "https://github.com/sigp/ethereum_ssz", branch = "main" }
118 changes: 118 additions & 0 deletions benches/encode_decode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use criterion::{criterion_group, criterion_main, Criterion};
use ssz::{Decode, DecodeError, Encode};
use ssz_types::{FixedVector, VariableList};
use std::hint::black_box;
use std::time::Duration;
use typenum::{Unsigned, U1048576, U131072};

#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode)]
#[ssz(struct_behaviour = "transparent")]
pub struct ByteVector<N: Unsigned>(FixedVector<u8, N>);

impl<N: Unsigned> ssz::Decode for ByteVector<N> {
fn is_ssz_fixed_len() -> bool {
true
}

fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
FixedVector::new(bytes.to_vec())
.map(Self)
.map_err(|e| DecodeError::BytesInvalid(format!("{e:?}")))
}

fn ssz_fixed_len() -> usize {
<FixedVector<u8, N> as ssz::Decode>::ssz_fixed_len()
}
}

fn benchmark_fixed_vector(c: &mut Criterion) {
let mut group = c.benchmark_group("fixed_vector");

let fixed_vector_u8 = FixedVector::<u8, U1048576>::new(vec![255u8; 1048576]).unwrap();
let fixed_vector_u64 = FixedVector::<u64, U131072>::new(vec![255u64; 131072]).unwrap();
let fixed_vector_bytes = fixed_vector_u8.as_ssz_bytes();

group.warm_up_time(Duration::from_secs(15));
group.measurement_time(Duration::from_secs(10));

group.bench_function("decode_byte_u8_1m", |b| {
b.iter(|| {
let vector = ByteVector::<U1048576>::from_ssz_bytes(&fixed_vector_bytes).unwrap();
black_box(vector);
});
});

group.bench_function("decode_u8_1m", |b| {
b.iter(|| {
let vector = FixedVector::<u8, U1048576>::from_ssz_bytes(&fixed_vector_bytes).unwrap();
black_box(vector);
});
});

group.bench_function("encode_u8_1m", |b| {
b.iter(|| {
let bytes = fixed_vector_u8.as_ssz_bytes();
black_box(bytes);
});
});

group.bench_function("decode_u64_128k", |b| {
b.iter(|| {
let vector = FixedVector::<u64, U131072>::from_ssz_bytes(&fixed_vector_bytes).unwrap();
black_box(vector);
});
});
group.bench_function("encode_u64_128k", |b| {
b.iter(|| {
let bytes = fixed_vector_u64.as_ssz_bytes();
black_box(bytes);
});
});

group.finish();
}

fn benchmark_variable_list(c: &mut Criterion) {
let mut group = c.benchmark_group("variable_list");

let variable_list_u8 = VariableList::<u8, U1048576>::new(vec![255u8; 1048576]).unwrap();
let variable_list_u64 = VariableList::<u64, U131072>::new(vec![255u64; 131072]).unwrap();
let variable_list_bytes = variable_list_u8.as_ssz_bytes();

group.warm_up_time(Duration::from_secs(15));
group.measurement_time(Duration::from_secs(10));

group.bench_function("decode_u8_1m", |b| {
b.iter(|| {
let vector =
VariableList::<u8, U1048576>::from_ssz_bytes(&variable_list_bytes).unwrap();
black_box(vector);
});
});

group.bench_function("encode_u8_1m", |b| {
b.iter(|| {
let bytes = variable_list_u8.as_ssz_bytes();
black_box(bytes);
});
});

group.bench_function("decode_u64_128k", |b| {
b.iter(|| {
let vector =
VariableList::<u64, U131072>::from_ssz_bytes(&variable_list_bytes).unwrap();
black_box(vector);
});
});
group.bench_function("encode_u64_128k", |b| {
b.iter(|| {
let bytes = variable_list_u64.as_ssz_bytes();
black_box(bytes);
});
});

group.finish();
}

criterion_group!(benches, benchmark_fixed_vector, benchmark_variable_list);
criterion_main!(benches);
47 changes: 40 additions & 7 deletions src/fixed_vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::Error;
use serde::Deserialize;
use serde_derive::Serialize;
use std::marker::PhantomData;
use std::mem;
use std::ops::{Deref, DerefMut, Index, IndexMut};
use std::slice::SliceIndex;
use tree_hash::Hash256;
Expand Down Expand Up @@ -275,6 +276,13 @@ impl<T, N: Unsigned> ssz::TryFromIter<T> for FixedVector<T, N> {
}
}

#[inline(always)]
pub fn from_ssz_bytes_u8_only<N: Unsigned>(
bytes: &[u8],
) -> Result<FixedVector<u8, N>, ssz::DecodeError> {
Ok(FixedVector::new(bytes.to_vec()).unwrap())
}

impl<T, N: Unsigned> ssz::Decode for FixedVector<T, N>
where
T: ssz::Decode,
Expand All @@ -299,6 +307,25 @@ where
len: 0,
expected: 1,
})
} else if mem::size_of::<T>() == 1 && mem::align_of::<T>() == 1 {
if bytes.len() != fixed_len {
return Err(ssz::DecodeError::BytesInvalid(format!(
"FixedVector of {} items has {} items",
fixed_len,
bytes.len(),
)));
}

// Safety: We've verified T is u8, so Vec<T> is Vec<u8>
// and bytes.to_vec() produces Vec<u8>
let vec_u8 = bytes.to_vec();
let vec_t = unsafe { std::mem::transmute::<Vec<u8>, Vec<T>>(vec_u8) };
Self::new(vec_t).map_err(|e| {
ssz::DecodeError::BytesInvalid(format!(
"Wrong number of FixedVector elements: {:?}",
e
))
})
} else if T::is_ssz_fixed_len() {
let num_items = bytes
.len()
Expand All @@ -312,13 +339,19 @@ where
)));
}

let vec = bytes.chunks(T::ssz_fixed_len()).try_fold(
Vec::with_capacity(num_items),
|mut vec, chunk| {
vec.push(T::from_ssz_bytes(chunk)?);
Ok(vec)
},
)?;
if bytes.len() != num_items * T::ssz_fixed_len() {
return Err(ssz::DecodeError::BytesInvalid(format!(
"FixedVector of {} items has {} bytes",
num_items,
bytes.len()
)));
}

let mut vec = Vec::with_capacity(num_items);
for chunk in bytes.chunks_exact(T::ssz_fixed_len()) {
vec.push(T::from_ssz_bytes(chunk)?);
}

Self::new(vec).map_err(|e| {
ssz::DecodeError::BytesInvalid(format!(
"Wrong number of FixedVector elements: {:?}",
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
//! ```
#[macro_use]
mod fixed_vector;
pub mod fixed_vector;
pub mod serde_utils;
mod tree_hash;
mod variable_list;
Expand Down
37 changes: 30 additions & 7 deletions src/variable_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,26 @@ where
return Ok(Self::default());
}

if std::mem::size_of::<T>() == 1 && std::mem::align_of::<T>() == 1 {
if bytes.len() > max_len {
return Err(ssz::DecodeError::BytesInvalid(format!(
"VariableList of {} items exceeds maximum of {}",
bytes.len(),
max_len
)));
}

// Safety: We've verified T has the same memory layout as u8, so Vec<T> *is* Vec<u8>.
let vec_u8 = bytes.to_vec();
let vec_t = unsafe { std::mem::transmute::<Vec<u8>, Vec<T>>(vec_u8) };
return Self::new(vec_t).map_err(|e| {
ssz::DecodeError::BytesInvalid(format!(
"Wrong number of VariableList elements: {:?}",
e
))
});
}

if T::is_ssz_fixed_len() {
let num_items = bytes
.len()
Expand All @@ -298,13 +318,16 @@ where
)));
}

bytes.chunks(T::ssz_fixed_len()).try_fold(
Vec::with_capacity(num_items),
|mut vec, chunk| {
vec.push(T::from_ssz_bytes(chunk)?);
Ok(vec)
},
)
let mut vec = Vec::with_capacity(num_items);
for chunk in bytes.chunks_exact(T::ssz_fixed_len()) {
vec.push(T::from_ssz_bytes(chunk)?);
}
Self::new(vec).map_err(|e| {
ssz::DecodeError::BytesInvalid(format!(
"Wrong number of VariableList elements: {:?}",
e
))
})
} else {
ssz::decode_list_of_variable_length_items(bytes, Some(max_len))
}?
Expand Down
Loading