Skip to content

Commit 61ab7c7

Browse files
authored
perf: clean up slice fn (#51)
1 parent 7edb0cd commit 61ab7c7

1 file changed

Lines changed: 51 additions & 48 deletions

File tree

src/nibbles.rs

Lines changed: 51 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ static INCREMENT_VALUES: [U256; 65] = {
8989
#[derive(Default, Clone, Copy, Eq)]
9090
pub struct Nibbles {
9191
/// Nibbles length.
92-
pub(crate) length: usize,
92+
pub(crate) len: usize,
9393
/// The nibbles themselves,
9494
/// stored as a 256-bit unsigned integer with most significant bits set first.
9595
pub(crate) nibbles: U256,
@@ -117,7 +117,7 @@ impl PartialEq for Nibbles {
117117
{
118118
arr == other_arr
119119
} else {
120-
self.length == other.length && self.nibbles == other.nibbles
120+
self.len == other.len && self.nibbles == other.nibbles
121121
}
122122
}
123123
}
@@ -128,7 +128,7 @@ impl core::hash::Hash for Nibbles {
128128
if let Some(arr) = self.as_array() {
129129
arr.hash(state);
130130
} else {
131-
self.length.hash(state);
131+
self.len.hash(state);
132132
self.nibbles.hash(state);
133133
}
134134
}
@@ -295,7 +295,7 @@ impl Nibbles {
295295
/// ```
296296
#[inline]
297297
pub const fn new() -> Self {
298-
Self { length: 0, nibbles: U256::ZERO }
298+
Self { len: 0, nibbles: U256::ZERO }
299299
}
300300

301301
/// Creates a new [`Nibbles`] instance from the given iterator over nibbles, without checking
@@ -422,8 +422,8 @@ impl Nibbles {
422422
/// assert_eq!(nibbles.to_vec(), vec![0x0A, 0x0B, 0x0C, 0x0D]);
423423
/// ```
424424
pub unsafe fn unpack_unchecked(data: &[u8]) -> Self {
425-
let length = data.len() * 2;
426-
debug_assert!(length <= NIBBLES);
425+
let len = data.len() * 2;
426+
debug_assert!(len <= NIBBLES);
427427

428428
cfg_if! {
429429
if #[cfg(target_endian = "little")] {
@@ -454,15 +454,15 @@ impl Nibbles {
454454
}
455455
}
456456

457-
Self { length, nibbles }
457+
Self { len, nibbles }
458458
}
459459

460460
/// Converts a fixed 32 byte array into a [`Nibbles`] instance. Similar to [`Nibbles::unpack`],
461461
/// but is not `unsafe`.
462462
#[inline]
463463
pub const fn unpack_array(data: &[u8; 32]) -> Self {
464464
let nibbles = U256::from_be_bytes(*data);
465-
Self { length: 64, nibbles }
465+
Self { len: NIBBLES, nibbles }
466466
}
467467

468468
/// Packs the nibbles into the given slice.
@@ -841,9 +841,9 @@ impl Nibbles {
841841
/// Returns the total number of nibbles in this [`Nibbles`].
842842
#[inline]
843843
pub const fn len(&self) -> usize {
844-
let len = self.length;
845-
debug_assert!(len <= 64);
846-
unsafe { core::hint::assert_unchecked(len <= 64) };
844+
let len = self.len;
845+
debug_assert!(len <= NIBBLES);
846+
unsafe { core::hint::assert_unchecked(len <= NIBBLES) };
847847
len
848848
}
849849

@@ -885,8 +885,8 @@ impl Nibbles {
885885
let result = self.increment()?;
886886

887887
// truncate to position of last non-zero Nibble
888-
let length = NIBBLES - (result.nibbles.trailing_zeros() / 4);
889-
Some(Self { length, nibbles: result.nibbles })
888+
let len = NIBBLES - (result.nibbles.trailing_zeros() / 4);
889+
Some(Self { len, nibbles: result.nibbles })
890890
}
891891

892892
/// Creates new nibbles containing the nibbles in the specified range `[start, end)`
@@ -898,36 +898,22 @@ impl Nibbles {
898898
/// The caller must ensure that `start <= end` and `end <= self.len()`.
899899
#[inline]
900900
pub fn slice_unchecked(&self, start: usize, end: usize) -> Self {
901-
// Fast path for empty slice
902-
if end == 0 || end <= start {
901+
#[cfg(debug_assertions)]
902+
self.slice_check(start, end);
903+
let len = end - start;
904+
if len == 0 {
903905
return Self::new();
904906
}
905-
906-
// Fast path for full slice
907-
let slice_to_end = end == self.len();
908-
if start == 0 && slice_to_end {
907+
if len == self.len() {
909908
return *self;
910909
}
911-
912-
let nibble_len = end - start;
913-
914-
// Optimize for common case where start == 0
915-
let nibbles = if start == 0 {
916-
// When slicing from the beginning, we can just apply the mask and avoid XORing
917-
self.nibbles & SLICE_MASKS[end]
918-
} else {
919-
// For middle and to_end cases, always shift first
920-
let shifted = self.nibbles << (start * 4);
921-
if slice_to_end {
922-
// When slicing to the end, no mask needed after shift
923-
shifted
924-
} else {
925-
// For middle slices, apply end mask after shift
926-
shifted & SLICE_MASKS[end - start]
927-
}
928-
};
929-
930-
Self { length: nibble_len, nibbles }
910+
let mask = SLICE_MASKS[len];
911+
let mut nibbles = self.nibbles;
912+
if start != 0 {
913+
nibbles <<= start * 4;
914+
}
915+
nibbles &= mask;
916+
Self { len, nibbles }
931917
}
932918

933919
/// Creates new nibbles containing the nibbles in the specified range.
@@ -947,15 +933,22 @@ impl Nibbles {
947933
Bound::Excluded(&idx) => idx,
948934
Bound::Unbounded => self.len(),
949935
};
950-
assert!(start <= end, "Cannot slice with a start index greater than the end index");
951-
assert!(
952-
end <= self.len(),
953-
"Cannot slice with an end index greater than the length of the nibbles"
954-
);
936+
self.slice_check(start, end);
937+
// Extra hint to remove the bounds check in `slice_unchecked`.
938+
// SAFETY: `start <= end <= self.len() <= NIBBLES`
939+
unsafe { core::hint::assert_unchecked(end - start <= NIBBLES) };
955940

956941
self.slice_unchecked(start, end)
957942
}
958943

944+
#[inline]
945+
#[cfg_attr(debug_assertions, track_caller)]
946+
const fn slice_check(&self, start: usize, end: usize) {
947+
if !(start <= end && end <= self.len()) {
948+
panic_invalid_slice(start, end, self.len());
949+
}
950+
}
951+
959952
/// Join two nibble sequences together.
960953
#[inline]
961954
pub fn join(&self, other: &Self) -> Self {
@@ -983,7 +976,7 @@ impl Nibbles {
983976
#[inline]
984977
pub const fn push_unchecked(&mut self, nibble: u8) {
985978
let len = self.len();
986-
self.length = len + 1;
979+
self.len = len + 1;
987980
let _ = self.len(); // Assert invariant.
988981

989982
let nibble_val = (nibble & 0x0F) as u64;
@@ -1028,7 +1021,7 @@ impl Nibbles {
10281021
}
10291022
}
10301023

1031-
self.length -= 1;
1024+
self.len -= 1;
10321025
Some(nibble)
10331026
}
10341027

@@ -1040,7 +1033,7 @@ impl Nibbles {
10401033
}
10411034

10421035
self.nibbles |= other.nibbles >> self.bit_len();
1043-
self.length += other.length;
1036+
self.len += other.len;
10441037
}
10451038

10461039
/// Extend the current nibbles with another byte slice.
@@ -1050,6 +1043,7 @@ impl Nibbles {
10501043
}
10511044

10521045
#[inline]
1046+
#[cfg_attr(debug_assertions, track_caller)]
10531047
fn extend_check(&self, other_len: usize) {
10541048
assert!(
10551049
self.len() + other_len <= NIBBLES,
@@ -1074,7 +1068,7 @@ impl Nibbles {
10741068
other <<= (U256::BYTES - len_bytes) * 8;
10751069
}
10761070
self.nibbles |= other >> self.bit_len();
1077-
self.length += len_bytes * 2;
1071+
self.len += len_bytes * 2;
10781072
}
10791073

10801074
/// Truncates the current nibbles to the given length.
@@ -1249,6 +1243,15 @@ fn panic_invalid_index(len: usize, i: usize) -> ! {
12491243
panic!("index out of bounds: {i} for nibbles of length {len}");
12501244
}
12511245

1246+
#[cold]
1247+
#[inline(never)]
1248+
#[cfg_attr(debug_assertions, track_caller)]
1249+
const fn panic_invalid_slice(start: usize, end: usize, len: usize) -> ! {
1250+
assert!(start <= end, "Cannot slice with a start index greater than the end index");
1251+
assert!(end <= len, "Cannot slice with an end index greater than the length of the nibbles");
1252+
unreachable!()
1253+
}
1254+
12521255
/// Internal container for owned/borrowed byte slices.
12531256
enum ByteContainer<'a, const N: usize> {
12541257
/// Borrowed variant holds a reference to a slice of bytes.

0 commit comments

Comments
 (0)