Skip to content

Commit ad7802f

Browse files
committed
Micro-optimize the heck out of LEB128 reading and writing.
This commit makes the following writing improvements: - Removes the unnecessary `write_to_vec` function. - Reduces the number of conditions per loop from 2 to 1. - Avoids a mask and a shift on the final byte. And the following reading improvements: - Removes an unnecessary type annotation. - Fixes a dangerous unchecked slice access. Imagine a slice `[0x80]` -- the current code will read past the end of the slice some number of bytes. The bounds check at the end will subsequently trigger, unless something bad (like a crash) happens first. The cost of doing bounds check in the loop body is negligible. - Avoids a mask on the final byte. And the following improvements for both reading and writing: - Changes `for` to `loop` for the loops, avoiding an unnecessary condition on each iteration. This also removes the need for `leb128_size`. All of these changes give significant perf wins, up to 5%.
1 parent a19edd6 commit ad7802f

File tree

1 file changed

+14
-50
lines changed

1 file changed

+14
-50
lines changed

src/libserialize/leb128.rs

+14-50
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,14 @@
1-
#[inline]
2-
pub fn write_to_vec(vec: &mut Vec<u8>, byte: u8) {
3-
vec.push(byte);
4-
}
5-
6-
#[cfg(target_pointer_width = "32")]
7-
const USIZE_LEB128_SIZE: usize = 5;
8-
#[cfg(target_pointer_width = "64")]
9-
const USIZE_LEB128_SIZE: usize = 10;
10-
11-
macro_rules! leb128_size {
12-
(u16) => {
13-
3
14-
};
15-
(u32) => {
16-
5
17-
};
18-
(u64) => {
19-
10
20-
};
21-
(u128) => {
22-
19
23-
};
24-
(usize) => {
25-
USIZE_LEB128_SIZE
26-
};
27-
}
28-
291
macro_rules! impl_write_unsigned_leb128 {
302
($fn_name:ident, $int_ty:ident) => {
313
#[inline]
324
pub fn $fn_name(out: &mut Vec<u8>, mut value: $int_ty) {
33-
for _ in 0..leb128_size!($int_ty) {
34-
let mut byte = (value & 0x7F) as u8;
35-
value >>= 7;
36-
if value != 0 {
37-
byte |= 0x80;
38-
}
39-
40-
write_to_vec(out, byte);
41-
42-
if value == 0 {
5+
loop {
6+
if value < 0x80 {
7+
out.push(value as u8);
438
break;
9+
} else {
10+
out.push(((value & 0x7f) | 0x80) as u8);
11+
value >>= 7;
4412
}
4513
}
4614
}
@@ -57,24 +25,20 @@ macro_rules! impl_read_unsigned_leb128 {
5725
($fn_name:ident, $int_ty:ident) => {
5826
#[inline]
5927
pub fn $fn_name(slice: &[u8]) -> ($int_ty, usize) {
60-
let mut result: $int_ty = 0;
28+
let mut result = 0;
6129
let mut shift = 0;
6230
let mut position = 0;
63-
64-
for _ in 0..leb128_size!($int_ty) {
65-
let byte = unsafe { *slice.get_unchecked(position) };
31+
loop {
32+
let byte = slice[position];
6633
position += 1;
67-
result |= ((byte & 0x7F) as $int_ty) << shift;
6834
if (byte & 0x80) == 0 {
69-
break;
35+
result |= (byte as $int_ty) << shift;
36+
return (result, position);
37+
} else {
38+
result |= ((byte & 0x7F) as $int_ty) << shift;
7039
}
7140
shift += 7;
7241
}
73-
74-
// Do a single bounds check at the end instead of for every byte.
75-
assert!(position <= slice.len());
76-
77-
(result, position)
7842
}
7943
};
8044
}
@@ -116,7 +80,7 @@ where
11680

11781
#[inline]
11882
pub fn write_signed_leb128(out: &mut Vec<u8>, value: i128) {
119-
write_signed_leb128_to(value, |v| write_to_vec(out, v))
83+
write_signed_leb128_to(value, |v| out.push(v))
12084
}
12185

12286
#[inline]

0 commit comments

Comments
 (0)