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

perf: avoid cloning HashBuilder input #50

Merged
merged 7 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
107 changes: 107 additions & 0 deletions src/hash_builder/input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use alloc::vec::Vec;
use alloy_primitives::{hex, B256};
use core::fmt;

/// The input of the hash builder.
///
/// Stores [`HashBuilderInputRef`] efficiently by reusing resources.
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(derive_arbitrary::Arbitrary, proptest_derive::Arbitrary))]
pub struct HashBuilderInput {
/// Stores the bytes of either the leaf node value or the hash of adjacent nodes.
buf: Vec<u8>,
/// The kind of the current hash builder input.
kind: HashBuilderInputKind,
}

impl Default for HashBuilderInput {
fn default() -> Self {
Self { buf: Vec::with_capacity(128), kind: HashBuilderInputKind::default() }
}
}

impl fmt::Debug for HashBuilderInput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_ref().fmt(f)
}
}

impl HashBuilderInput {
/// Returns the input as a reference.
#[inline]
pub fn as_ref(&self) -> HashBuilderInputRef<'_> {
match self.kind {
HashBuilderInputKind::Bytes => HashBuilderInputRef::Bytes(&self.buf),
HashBuilderInputKind::Hash => {
debug_assert_eq!(self.buf.len(), 32);
HashBuilderInputRef::Hash(unsafe { self.buf[..].try_into().unwrap_unchecked() })
}
}
}

/// Sets the input from the given bytes.
#[inline]
pub fn set_from_ref(&mut self, input: HashBuilderInputRef<'_>) {
self.buf.clear();
self.buf.extend_from_slice(input.as_slice());
self.kind = input.kind();
}

/// Clears the input.
#[inline]
pub fn clear(&mut self) {
self.buf.clear();
self.kind = HashBuilderInputKind::default();
}
}

/// The kind of the current hash builder input.
#[doc(hidden)] // Not public API.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(derive_arbitrary::Arbitrary, proptest_derive::Arbitrary))]
#[allow(unreachable_pub)]
DaniPopes marked this conversation as resolved.
Show resolved Hide resolved
pub enum HashBuilderInputKind {
DaniPopes marked this conversation as resolved.
Show resolved Hide resolved
/// Value of the leaf node.
#[default]
Bytes,
/// Hash of adjacent nodes.
Hash,
}

/// The input of the hash builder.
pub enum HashBuilderInputRef<'a> {
/// Value of the leaf node.
Bytes(&'a [u8]),
/// Hash of adjacent nodes.
Hash(&'a B256),
}

impl<'a> HashBuilderInputRef<'a> {
/// Returns the input as a slice.
pub const fn as_slice(&self) -> &'a [u8] {
match *self {
HashBuilderInputRef::Bytes(bytes) => bytes,
HashBuilderInputRef::Hash(hash) => hash.as_slice(),
}
}

const fn kind(&self) -> HashBuilderInputKind {
match *self {
HashBuilderInputRef::Bytes(_) => HashBuilderInputKind::Bytes,
HashBuilderInputRef::Hash(_) => HashBuilderInputKind::Hash,
}
}
}

impl fmt::Debug for HashBuilderInputRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match *self {
HashBuilderInputRef::Bytes(_) => "Bytes",
HashBuilderInputRef::Hash(_) => "Hash",
};
let slice = hex::encode_prefixed(self.as_slice());
write!(f, "{name}({slice})")
}
}
50 changes: 30 additions & 20 deletions src/hash_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ use tracing::trace;
#[allow(unused_imports)]
use alloc::vec::Vec;

mod value;
pub use value::HashBuilderValue;
mod input;
pub use input::{HashBuilderInput, HashBuilderInputRef};

/// A component used to construct the root hash of the trie.
///
Expand Down Expand Up @@ -45,8 +45,8 @@ pub use value::HashBuilderValue;
#[allow(missing_docs)]
pub struct HashBuilder {
pub key: Nibbles,
pub input: HashBuilderInput,
pub stack: Vec<Vec<u8>>,
pub value: HashBuilderValue,

pub groups: Vec<TrieMask>,
pub tree_masks: Vec<TrieMask>,
Expand Down Expand Up @@ -117,7 +117,7 @@ impl HashBuilder {
if !self.key.is_empty() {
self.update(&key);
}
self.set_key_value(key, value);
self.set_input(key, HashBuilderInputRef::Bytes(value));
}

/// Adds a new branch element and its hash to the trie hash builder.
Expand All @@ -133,7 +133,7 @@ impl HashBuilder {
} else if key.is_empty() {
self.stack.push(word_rlp(&value));
}
self.set_key_value(key, value);
self.set_input(key, HashBuilderInputRef::Hash(&value));
self.stored_in_database = stored_in_database;
}

Expand All @@ -143,7 +143,7 @@ impl HashBuilder {
if !self.key.is_empty() {
self.update(&Nibbles::default());
self.key.clear();
self.value = HashBuilderValue::Bytes(vec![]);
self.input.clear();
}
let root = self.current_root();
if root == EMPTY_ROOT_HASH {
Expand All @@ -154,11 +154,20 @@ impl HashBuilder {
root
}

fn set_key_value<T: Into<HashBuilderValue>>(&mut self, key: Nibbles, value: T) {
trace!(target: "trie::hash_builder", key = ?self.key, value = ?self.value, "old key/value");
#[inline]
fn set_input(&mut self, key: Nibbles, input: HashBuilderInputRef<'_>) {
DaniPopes marked this conversation as resolved.
Show resolved Hide resolved
self.log_input("old input");
self.key = key;
self.value = value.into();
trace!(target: "trie::hash_builder", key = ?self.key, value = ?self.value, "new key/value");
self.input.set_from_ref(input);
self.log_input("new input");
}

fn log_input(&self, msg: &str) {
trace!(target: "trie::hash_builder",
key = ?self.key,
input = ?self.input,
"{msg}",
);
}

fn current_root(&self) -> B256 {
Expand All @@ -181,6 +190,7 @@ impl HashBuilder {
let mut build_extensions = false;
// current / self.key is always the latest added element in the trie
let mut current = self.key.clone();
debug_assert!(!current.is_empty());

trace!(target: "trie::hash_builder", ?current, ?succeeding, "updating merkle tree");

Expand Down Expand Up @@ -235,20 +245,20 @@ impl HashBuilder {

// Concatenate the 2 nodes together
if !build_extensions {
match &self.value {
HashBuilderValue::Bytes(leaf_value) => {
match self.input.as_ref() {
HashBuilderInputRef::Bytes(leaf_value) => {
let leaf_node = LeafNodeRef::new(&short_node_key, leaf_value);
trace!(target: "trie::hash_builder", ?leaf_node, "pushing leaf node");
trace!(target: "trie::hash_builder", rlp = {
self.rlp_buf.clear();
hex::encode(leaf_node.rlp(&mut self.rlp_buf))
}, "leaf node rlp");

self.rlp_buf.clear();
self.stack.push(leaf_node.rlp(&mut self.rlp_buf));
let rlp = leaf_node.rlp(&mut self.rlp_buf);
trace!(target: "trie::hash_builder",
?leaf_node,
rlp = hex::encode(&rlp),
"pushing leaf node",
);
self.stack.push(rlp);
self.retain_proof_from_buf(&current.slice(..len_from));
}
HashBuilderValue::Hash(hash) => {
HashBuilderInputRef::Hash(hash) => {
trace!(target: "trie::hash_builder", ?hash, "pushing branch node hash");
self.stack.push(word_rlp(hash));

Expand Down
49 changes: 0 additions & 49 deletions src/hash_builder/value.rs

This file was deleted.

1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#![cfg_attr(not(feature = "std"), no_std)]

#[macro_use]
#[allow(unused_imports)]
extern crate alloc;

pub mod nodes;
Expand Down
Loading