Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manually implement SSZ for Checkpoint #6657

Closed
wants to merge 2 commits into from

Conversation

paulhauner
Copy link
Member

Issue Addressed

NA

Proposed Changes

Manually implements SSZ for Checkpoint. We're presently using the ssz_derive macros and they have some overhead due to their generic-ness.

My benchmarks indicate a >20% reduction in ssz::Decode times. Although we're only saving some nanoseconds, this is a very commonly used struct and it's appealing to have it nice and lean.

The complexity introduced is very minimal; it's very easy to reason about the implementation I've included.

Benchmarks

These benchmarks are iterations of 1,000,000 decodes of a Checkpoint struct. There's an additional assert! statement included in the benchmark to prevent the compile from optimising anything out.

Before

2.646833ms
2.6645ms
2.648917ms
2.648375ms
2.636334ms
2.636625ms
2.64875ms
2.656542ms
2.636625ms
2.635208ms

After

1.926208ms
1.923208ms
1.921291ms
1.920208ms
2.00575ms
1.9165ms
1.91875ms
1.926542ms
1.921125ms
1.961834ms

Additional Info

NA

@paulhauner
Copy link
Member Author

Here's the SSZ decode implementation, for reference:

fn from_ssz_bytes(bytes: &[u8]) -> std::result::Result<Self, ssz::DecodeError> {
    if <Self as ssz::Decode>::is_ssz_fixed_len() {
        if bytes.len() != <Self as ssz::Decode>::ssz_fixed_len() {
            return Err(ssz::DecodeError::InvalidByteLength {
                len: bytes.len(),
                expected: <Self as ssz::Decode>::ssz_fixed_len(),
            });
        }
        let mut start: usize = 0;
        let mut end = start;
        let epoch = {
            start = end;
            end = end
                .checked_add(<Epoch as ssz::Decode>::ssz_fixed_len())
                .ok_or_else(|| ssz::DecodeError::OutOfBoundsByte {
                    i: usize::max_value(),
                })?;
            let slice = bytes
                .get(start..end)
                .ok_or_else(|| ssz::DecodeError::InvalidByteLength {
                    len: bytes.len(),
                    expected: end,
                })?;
            <Epoch as ssz::Decode>::from_ssz_bytes(slice)?
        };
        let root = {
            start = end;
            end = end
                .checked_add(<Hash256 as ssz::Decode>::ssz_fixed_len())
                .ok_or_else(|| ssz::DecodeError::OutOfBoundsByte {
                    i: usize::max_value(),
                })?;
            let slice = bytes
                .get(start..end)
                .ok_or_else(|| ssz::DecodeError::InvalidByteLength {
                    len: bytes.len(),
                    expected: end,
                })?;
            <Hash256 as ssz::Decode>::from_ssz_bytes(slice)?
        };
        Ok(Self { epoch, root })
    } else {
        let mut builder = ssz::SszDecoderBuilder::new(bytes);
        builder.register_type::<Epoch>()?;
        builder.register_type::<Hash256>()?;
        let mut decoder = builder.build()?;
        let epoch = decoder.decode_next()?;
        let root = decoder.decode_next()?;
        Ok(Self { epoch, root })
    }
}

@paulhauner
Copy link
Member Author

Closing this in favour of implementing the optimisation at the derive macro level: sigp/ethereum_ssz#40

@paulhauner paulhauner closed this Dec 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
work-in-progress PR is a work-in-progress
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant