Skip to content

Commit 54edd21

Browse files
committed
perf: bump-allocate branch children, drop BranchId indirection
1 parent 574701d commit 54edd21

4 files changed

Lines changed: 63 additions & 69 deletions

File tree

‎crates/mpt/src/node.rs‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
1+
use std::cell::Cell;
2+
13
use revm_primitives::hex;
24

35
pub(crate) type NodeId = u32;
46

5-
/// Index into the owning trie's branch-children arena (`Mpt::branches`).
6-
pub(crate) type BranchId = u32;
7+
/// Children slots of a branch node, allocated in the trie's bump arena.
8+
pub(crate) type BranchChildren<'a> = &'a [Cell<Option<NodeId>>; 16];
79

810
/// Node data for arena-based trie with zero-copy optimization.
911
///
10-
/// Branch children are stored out-of-line in the owning trie's branch arena so this enum stays
11-
/// small: nodes live in a flat `Vec<NodeData>`, and inlining the 16-child array would make every
12-
/// node (mostly leaves and digests) pay the branch variant's size when pushed, copied, or moved.
12+
/// Branch children are stored out-of-line in the trie's bump arena so this enum stays small:
13+
/// nodes live in a flat `Vec<NodeData>`, and inlining the 16-child array would make every node
14+
/// (mostly leaves and digests) pay the branch variant's size when pushed, copied, or moved. The
15+
/// slots are `Cell`s so updates can mutate them through the shared reference without re-borrowing
16+
/// the node list.
1317
#[derive(Clone, Debug, Default, PartialEq, Eq, Ord, PartialOrd)]
1418
pub(crate) enum NodeData<'a> {
1519
#[default]
1620
/// Absence of a node. Encoded as empty string in RLP.
1721
Null,
1822
/// 16-way branch. Each child is optional; the branch's value slot is unused in our state trie
19-
/// and must be empty, enforced during decoding. The id is only meaningful within the owning
20-
/// trie.
21-
Branch(BranchId),
23+
/// and must be empty, enforced during decoding.
24+
Branch(BranchChildren<'a>),
2225
/// Leaf node containing a compact hex-prefix path and a value. Both slices borrow from the
2326
/// input buffer or bump arena. The path encodes the remainder of the key.
2427
Leaf(&'a [u8], &'a [u8]),

‎crates/mpt/src/resolver.rs‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,9 @@ impl MptResolver {
8282
let child_id = self.resolve_internal(&mut item, mpt)?;
8383
childs[i] = if child_id == NULL_NODE_ID { None } else { Some(child_id) };
8484
}
85-
let branch_id = mpt.add_branch(childs);
86-
mpt.add_node_copied(&NodeData::Branch(branch_id))
85+
// The children array is allocated in `mpt`'s own bump, so no copy is needed.
86+
let children = mpt.alloc_branch(childs);
87+
mpt.add_node(NodeData::Branch(children), None)
8788
}
8889
_ => {
8990
return Err(Error::RlpError(alloy_rlp::Error::UnexpectedLength));

‎crates/mpt/src/tests.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,8 @@ fn test_delete_with_unresolved_sibling_errors() {
261261
let mut children: [Option<u32>; 16] = Default::default();
262262
children[0] = Some(leaf_id);
263263
children[1] = Some(digest_id);
264-
let branch_id = trie.add_branch(children);
265-
let branch_node_id = trie.add_node(NodeData::Branch(branch_id), None);
264+
let branch_children = trie.alloc_branch(children);
265+
let branch_node_id = trie.add_node(NodeData::Branch(branch_children), None);
266266

267267
// Set the root to the branch
268268
trie.set_root_id(branch_node_id);

‎crates/mpt/src/trie.rs‎

Lines changed: 47 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::{
1515
encoded_path_eq_nibs, encoded_path_strip_prefix, lcp, prefix_to_nibs,
1616
to_encoded_path_with_bump, to_nibs,
1717
},
18-
node::{BranchId, NodeData, NodeId, NodeRef},
18+
node::{BranchChildren, NodeData, NodeId, NodeRef},
1919
};
2020

2121
/// OpenVM memory alignment word size.
@@ -57,11 +57,6 @@ pub struct Mpt<'a> {
5757
/// List of MPT nodes.
5858
nodes: Vec<NodeData<'a>>,
5959

60-
/// Children arrays of branch nodes, indexed by [`BranchId`]. Kept out of [`NodeData`] so the
61-
/// node list stays compact. Arrays orphaned by structural updates are never reclaimed, just
62-
/// like the nodes themselves.
63-
branches: Vec<[Option<NodeId>; 16]>,
64-
6560
/// Cache. Hashing/encoding often needs "what would this node look like in its parent"
6661
cached_references: Vec<Cell<Option<NodeRef<'a>>>>,
6762

@@ -90,9 +85,6 @@ impl<'a> Mpt<'a> {
9085

9186
Self {
9287
nodes,
93-
// Branch nodes are a small fraction of witness nodes (each resolved branch child is
94-
// its own node, unresolved ones are digest nodes), so 1/8 leaves ample headroom.
95-
branches: Vec::with_capacity(capacity / 8),
9688
rlp_scratch: RefCell::new(Vec::with_capacity(RLP_SCRATCH_INIT_CAPACITY)),
9789
cached_references,
9890
bump,
@@ -181,8 +173,8 @@ impl<'a> Mpt<'a> {
181173
}
182174

183175
match self.nodes[node_id as usize] {
184-
NodeData::Branch(branch_id) => {
185-
for child_id in self.branches[branch_id as usize].into_iter().flatten() {
176+
NodeData::Branch(children) => {
177+
for child_id in children.iter().filter_map(Cell::get) {
186178
self.encode_trie_internal(child_id, out);
187179
}
188180
}
@@ -390,8 +382,8 @@ impl<'a> Mpt<'a> {
390382
return Err(Error::ValueInBranch);
391383
}
392384

393-
let branch_id = self.add_branch(childs);
394-
let node_id = self.add_node(NodeData::Branch(branch_id), Some(node_ref));
385+
let children = self.alloc_branch(childs);
386+
let node_id = self.add_node(NodeData::Branch(children), Some(node_ref));
395387
Ok(node_id)
396388
}
397389
}
@@ -444,11 +436,11 @@ impl<'a> Mpt<'a> {
444436
NodeData::Null => {
445437
out.put_u8(alloy_rlp::EMPTY_STRING_CODE);
446438
}
447-
NodeData::Branch(branch_id) => {
439+
NodeData::Branch(children) => {
448440
encode_header(alloy_rlp::EMPTY_LIST_CODE, payload_length, out);
449-
for child_id in self.branches[*branch_id as usize].iter() {
450-
match child_id {
451-
Some(id) => self.reference_encode(*id, out),
441+
for child_id in children.iter() {
442+
match child_id.get() {
443+
Some(id) => self.reference_encode(id, out),
452444
None => out.put_u8(alloy_rlp::EMPTY_STRING_CODE),
453445
}
454446
}
@@ -498,10 +490,10 @@ impl<'a> Mpt<'a> {
498490
fn payload_length(&self, node_id: NodeId) -> usize {
499491
match &self.nodes[node_id as usize] {
500492
NodeData::Null => 0,
501-
NodeData::Branch(branch_id) => {
502-
1 + self.branches[*branch_id as usize]
493+
NodeData::Branch(children) => {
494+
1 + children
503495
.iter()
504-
.map(|child| child.map_or(1, |id| self.reference_length(id)))
496+
.map(|child| child.get().map_or(1, |id| self.reference_length(id)))
505497
.sum::<usize>()
506498
}
507499
NodeData::Leaf(encoded_path, value) => encoded_path.length() + value.length(),
@@ -613,7 +605,6 @@ impl<'a> Mpt<'a> {
613605
pub fn reserve(&mut self, additional: usize) {
614606
self.nodes.reserve(additional);
615607
self.cached_references.reserve(additional);
616-
self.branches.reserve(additional / 8);
617608
}
618609
}
619610

@@ -627,13 +618,11 @@ impl<'a> Mpt<'a> {
627618
id
628619
}
629620

630-
/// Adds a branch children array to the arena and returns its id, to be stored in a
631-
/// [`NodeData::Branch`] of this trie.
621+
/// Allocates a branch children array in the bump arena, to be stored in a
622+
/// [`NodeData::Branch`].
632623
#[inline]
633-
pub(crate) fn add_branch(&mut self, children: [Option<NodeId>; 16]) -> BranchId {
634-
let id = self.branches.len() as BranchId;
635-
self.branches.push(children);
636-
id
624+
pub(crate) fn alloc_branch(&self, children: [Option<NodeId>; 16]) -> BranchChildren<'a> {
625+
self.bump.alloc(children.map(Cell::new))
637626
}
638627

639628
/// Sets the root node ID. Used by the resolver and by tests to construct tries with specific
@@ -644,13 +633,15 @@ impl<'a> Mpt<'a> {
644633
self.root_id = root_id;
645634
}
646635

647-
/// Adds a new node to the trie, copying any borrowed slices into the trie's bump arena, and
648-
/// returns the new node's ID. A `Branch`'s id must already refer to this trie's branch arena.
636+
/// Adds a new node to the trie, copying any borrowed slices (and branch children) into the
637+
/// trie's bump arena, and returns the new node's ID.
649638
#[cfg(feature = "host")]
650639
pub(crate) fn add_node_copied(&mut self, data: &NodeData<'_>) -> NodeId {
651640
let data = match data {
652641
NodeData::Null => NodeData::Null,
653-
NodeData::Branch(branch_id) => NodeData::Branch(*branch_id),
642+
NodeData::Branch(children) => {
643+
NodeData::Branch(self.alloc_branch(children.each_ref().map(Cell::get)))
644+
}
654645
NodeData::Leaf(prefix, value) => NodeData::Leaf(
655646
self.bump.alloc_slice_copy(prefix),
656647
self.bump.alloc_slice_copy(value),
@@ -672,9 +663,9 @@ impl<'a> Mpt<'a> {
672663
fn get_internal(&self, node_id: NodeId, key_nibs: &[u8]) -> Result<Option<&'a [u8]>, Error> {
673664
match &self.nodes[node_id as usize] {
674665
NodeData::Null => Ok(None),
675-
NodeData::Branch(branch_id) => {
666+
NodeData::Branch(children) => {
676667
if let Some((i, tail)) = key_nibs.split_first() {
677-
match self.branches[*branch_id as usize][*i as usize] {
668+
match children[*i as usize].get() {
678669
Some(id) => self.get_internal(id, tail),
679670
None => Ok(None),
680671
}
@@ -715,14 +706,14 @@ impl<'a> Mpt<'a> {
715706
self.nodes[node_id as usize] = NodeData::Leaf(path, value);
716707
true
717708
}
718-
NodeData::Branch(branch_id) => {
709+
NodeData::Branch(children) => {
719710
if let Some((i, tail)) = key_nibs.split_first() {
720-
match self.branches[branch_id as usize][*i as usize] {
711+
match children[*i as usize].get() {
721712
Some(id) => self.insert_internal(id, tail, value)?,
722713
None => {
723714
let path = to_encoded_path_with_bump(self.bump, tail, true);
724715
let new_leaf_id = self.add_node(NodeData::Leaf(path, value), None);
725-
self.branches[branch_id as usize][*i as usize] = Some(new_leaf_id);
716+
children[*i as usize].set(Some(new_leaf_id));
726717
true
727718
}
728719
}
@@ -760,17 +751,18 @@ impl<'a> Mpt<'a> {
760751
children[self_nibs[common_len] as usize] = Some(leaf1_id);
761752
children[key_nibs[common_len] as usize] = Some(leaf2_id);
762753

763-
let branch_id = self.add_branch(children);
754+
let branch_children = self.alloc_branch(children);
764755
let new_node_data = if common_len > 0 {
765-
let branch_node_id = self.add_node(NodeData::Branch(branch_id), None);
756+
let branch_node_id =
757+
self.add_node(NodeData::Branch(branch_children), None);
766758
let ext_path_slice = to_encoded_path_with_bump(
767759
self.bump,
768760
&self_nibs[..common_len],
769761
false,
770762
);
771763
NodeData::Extension(ext_path_slice, branch_node_id)
772764
} else {
773-
NodeData::Branch(branch_id)
765+
NodeData::Branch(branch_children)
774766
};
775767
self.nodes[node_id as usize] = new_node_data;
776768
true
@@ -804,14 +796,14 @@ impl<'a> Mpt<'a> {
804796
let leaf_id = self.add_node(NodeData::Leaf(leaf_path, value), None);
805797
children[key_nibs[common_len] as usize] = Some(leaf_id);
806798

807-
let branch_id = self.add_branch(children);
799+
let branch_children = self.alloc_branch(children);
808800
let new_node_data = if common_len > 0 {
809-
let branch_node_id = self.add_node(NodeData::Branch(branch_id), None);
801+
let branch_node_id = self.add_node(NodeData::Branch(branch_children), None);
810802
let parent_ext_path_slice =
811803
to_encoded_path_with_bump(self.bump, &self_nibs[..common_len], false);
812804
NodeData::Extension(parent_ext_path_slice, branch_node_id)
813805
} else {
814-
NodeData::Branch(branch_id)
806+
NodeData::Branch(branch_children)
815807
};
816808
self.nodes[node_id as usize] = new_node_data;
817809
true
@@ -833,18 +825,17 @@ impl<'a> Mpt<'a> {
833825
fn delete_internal(&mut self, node_id: NodeId, key_nibs: &[u8]) -> Result<bool, Error> {
834826
let updated = match self.nodes[node_id as usize] {
835827
NodeData::Null => false,
836-
NodeData::Branch(branch_id) => {
828+
NodeData::Branch(children) => {
837829
if let Some((i, tail)) = key_nibs.split_first() {
838-
let child_id = self.branches[branch_id as usize][*i as usize];
839-
match child_id {
830+
match children[*i as usize].get() {
840831
Some(id) => {
841832
if !self.delete_internal(id, tail)? {
842833
return Ok(false);
843834
}
844835

845836
// if the node is now empty, remove it
846837
if matches!(self.nodes[id as usize], NodeData::Null) {
847-
self.branches[branch_id as usize][*i as usize] = None;
838+
children[*i as usize].set(None);
848839
}
849840
}
850841
None => return Ok(false),
@@ -853,14 +844,13 @@ impl<'a> Mpt<'a> {
853844
return Err(Error::ValueInBranch);
854845
}
855846

856-
let mut remaining_iter = self.branches[branch_id as usize]
847+
let mut remaining_iter = children
857848
.iter()
858849
.enumerate()
859-
.filter(|(_, n)| n.is_some());
850+
.filter_map(|(index, n)| n.get().map(|id| (index, id)));
860851

861852
// there will always be at least one remaining node
862853
let (index, child_id) = remaining_iter.next().unwrap();
863-
let child_id = child_id.unwrap();
864854

865855
// if there is only exactly one node left, we need to convert the branch
866856
if remaining_iter.next().is_none() {
@@ -897,7 +887,7 @@ impl<'a> Mpt<'a> {
897887
};
898888
self.nodes[node_id as usize] = new_node_data;
899889
}
900-
// otherwise the children were already updated in place in the branch arena
890+
// otherwise the children were already updated in place through their cells
901891

902892
true
903893
}
@@ -1010,8 +1000,8 @@ impl<'a> Mpt<'a> {
10101000
let child_id = self.decode_from_proof_rlp_internal(&mut item)?;
10111001
childs[i] = if child_id == NULL_NODE_ID { None } else { Some(child_id) };
10121002
}
1013-
let branch_id = self.add_branch(childs);
1014-
self.add_node(NodeData::Branch(branch_id), None)
1003+
let children = self.alloc_branch(childs);
1004+
self.add_node(NodeData::Branch(children), None)
10151005
}
10161006
_ => {
10171007
return Err(Error::RlpError(alloy_rlp::Error::UnexpectedLength));
@@ -1039,8 +1029,8 @@ impl<'a> Mpt<'a> {
10391029
payloads.push(buffer_bytes);
10401030

10411031
match &self.nodes[node_id as usize] {
1042-
NodeData::Branch(branch_id) => {
1043-
for child_id in self.branches[*branch_id as usize].into_iter().flatten() {
1032+
NodeData::Branch(children) => {
1033+
for child_id in children.iter().filter_map(Cell::get) {
10441034
self.payloads_internal(child_id, payloads);
10451035
}
10461036
}
@@ -1063,12 +1053,12 @@ impl Mpt<'_> {
10631053
NodeData::Null => {
10641054
println!("{}Null", indent);
10651055
}
1066-
NodeData::Branch(branch_id) => {
1056+
NodeData::Branch(children) => {
10671057
println!("{}Branch", indent);
1068-
for (i, child) in self.branches[*branch_id as usize].iter().enumerate() {
1069-
if let Some(child_id) = child {
1058+
for (i, child) in children.iter().enumerate() {
1059+
if let Some(child_id) = child.get() {
10701060
println!("{} [{}]:", indent, hex::encode([i as u8]));
1071-
self.print_trie_internal(*child_id, depth + 2);
1061+
self.print_trie_internal(child_id, depth + 2);
10721062
}
10731063
}
10741064
}

0 commit comments

Comments
 (0)