perf: use stack-allocated [u8; 65] for StoredNibblesSubKey encoding (#22314)

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Brian Picciano
2026-02-18 14:35:20 +01:00
committed by GitHub
parent 251f83ab0b
commit 8d97ab63c6
3 changed files with 70 additions and 5 deletions

View File

@@ -0,0 +1,5 @@
---
reth-db-api: patch
---
Changed `StoredNibblesSubKey` encoding to use a stack-allocated `[u8; 65]` array instead of a heap-allocated `Vec<u8>`, avoiding unnecessary heap allocation.

View File

@@ -138,13 +138,10 @@ impl Decode for StoredNibbles {
}
impl Encode for StoredNibblesSubKey {
type Encoded = Vec<u8>;
type Encoded = [u8; 65];
// Delegate to the Compact implementation
fn encode(self) -> Self::Encoded {
let mut buf = Vec::with_capacity(65);
self.to_compact(&mut buf);
buf
self.to_compact_array()
}
}

View File

@@ -90,6 +90,22 @@ impl From<StoredNibblesSubKey> for Nibbles {
}
}
impl StoredNibblesSubKey {
/// Encodes the nibbles into a fixed-size `[u8; 65]` array.
///
/// The first 64 bytes contain the nibble values (right-padded with zeros),
/// and the 65th byte stores the actual nibble count.
pub fn to_compact_array(&self) -> [u8; 65] {
assert!(self.0.len() <= 64);
let mut buf = [0u8; 65];
for (i, nibble) in self.0.iter().enumerate() {
buf[i] = nibble;
}
buf[64] = self.0.len() as u8;
buf
}
}
#[cfg(any(test, feature = "reth-codec"))]
impl reth_codecs::Compact for StoredNibblesSubKey {
fn to_compact<B>(&self, buf: &mut B) -> usize
@@ -187,4 +203,51 @@ mod tests {
let deserialized: StoredNibblesSubKey = serde_json::from_str(&serialized).unwrap();
assert_eq!(subkey, deserialized);
}
#[test]
fn test_stored_nibbles_subkey_compact_array_empty() {
let subkey = StoredNibblesSubKey::from(vec![]);
let arr = subkey.to_compact_array();
assert_eq!(arr, [0u8; 65]);
}
#[test]
fn test_stored_nibbles_subkey_compact_array_full() {
let nibbles: Vec<u8> = (0..64).map(|i| i % 16).collect();
let subkey = StoredNibblesSubKey::from(nibbles.clone());
let arr = subkey.to_compact_array();
assert_eq!(&arr[..64], &nibbles[..]);
assert_eq!(arr[64], 64);
}
#[test]
fn test_stored_nibbles_subkey_compact_array_roundtrip() {
let subkey = StoredNibblesSubKey::from(vec![0x0A, 0x0B, 0x0C]);
let arr = subkey.to_compact_array();
let (recovered, rest) = StoredNibblesSubKey::from_compact(&arr, 65);
assert_eq!(recovered, subkey);
assert!(rest.is_empty());
}
#[test]
fn test_stored_nibbles_subkey_compact_array_matches_to_compact() {
for len in [0, 1, 2, 31, 32, 33, 63, 64] {
let nibbles: Vec<u8> = (0..len).map(|i| (i % 16) as u8).collect();
let subkey = StoredNibblesSubKey::from(nibbles);
let arr = subkey.to_compact_array();
let mut compact_buf = BytesMut::with_capacity(65);
subkey.to_compact(&mut compact_buf);
assert_eq!(&arr[..], &compact_buf[..], "mismatch at nibble length {len}");
}
}
#[test]
fn test_stored_nibbles_subkey_compact_array_padding_is_zero() {
let subkey = StoredNibblesSubKey::from(vec![0x0F; 10]);
let arr = subkey.to_compact_array();
assert!(arr[10..64].iter().all(|&b| b == 0), "padding bytes must be zero");
}
}