Skip to content

Commit be8d437

Browse files
committed
value: special-case a bunch of primitive numeric types in from_bits
In practice it is overwhelmingly common to be parsing basic numeric types like u64, and minor variations on these (e.g. options of them). In particular this happens if your types come from the source or target of pretty-much any jet. In these cases we can just copy bits directly from the iterator (which with BitIter is extremely fast; we actually copy bytes) and rather than constructing types, we can just copy them from the precomp list. This gives us a massive speedup for large bitstrings. In particular you can see that to parse a 64k blob tow takes about 45us, where before it took about 200milliseconds. This is about a 4700x speedup. test value::benches::bench_value_create_64k ... bench: 875,039.00 ns/iter (+/- 5,007.70) test value::benches::bench_value_create_64k_compact ... bench: 45,258.88 ns/iter (+/- 169.22) test value::benches::bench_value_create_64k_padded ... bench: 45,291.30 ns/iter (+/- 120.04) test value::benches::bench_value_create_deep_some ... bench: 517,785.20 ns/iter (+/- 5,332.20) test value::benches::bench_value_create_deep_some_compact ... bench: 215,135.87 ns/iter (+/- 2,219.73) test value::benches::bench_value_create_deep_some_padded ... bench: 215,144.95 ns/iter (+/- 2,020.55) test value::benches::bench_value_create_u2048 ... bench: 548,674.60 ns/iter (+/- 4,192.62) test value::benches::bench_value_create_u2048_compact ... bench: 2,915.23 ns/iter (+/- 14.31) test value::benches::bench_value_create_u2048_padded ... bench: 2,916.31 ns/iter (+/- 13.82) test value::benches::bench_value_create_u64 ... bench: 12.48 ns/iter (+/- 0.05) test value::benches::bench_value_create_u64_compact ... bench: 72.94 ns/iter (+/- 0.89) test value::benches::bench_value_create_u64_padded ... bench: 71.93 ns/iter (+/- 0.72) test value::benches::bench_value_display_64k ... bench: 9,685,460.80 ns/iter (+/- 48,509.31) test value::benches::bench_value_display_deep_some ... bench: 309,437.20 ns/iter (+/- 5,402.57) test value::benches::bench_value_display_u2024 ... bench: 597,713.20 ns/iter (+/- 3,601.65) test value::benches::bench_value_display_u64 ... bench: 2,437.24 ns/iter (+/- 16.67)
1 parent 674b272 commit be8d437

File tree

2 files changed

+59
-18
lines changed

2 files changed

+59
-18
lines changed

src/bit_encoding/bititer.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ pub enum u2 {
6969
_3,
7070
}
7171

72+
impl From<u2> for u8 {
73+
fn from(s: u2) -> u8 {
74+
match s {
75+
u2::_0 => 0,
76+
u2::_1 => 1,
77+
u2::_2 => 2,
78+
u2::_3 => 3,
79+
}
80+
}
81+
}
82+
7283
/// Bitwise iterator formed from a wrapped bytewise iterator. Bytes are
7384
/// interpreted big-endian, i.e. MSB is returned first
7485
#[derive(Debug)]

src/value.rs

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::dag::{Dag, DagLike};
99
use crate::types::{CompleteBound, Final};
1010
use crate::BitIter;
1111

12-
use crate::{BitCollector, EarlyEndOfStreamError};
12+
use crate::{BitCollector, EarlyEndOfStreamError, Tmr};
1313
use core::{cmp, fmt, iter};
1414
use std::collections::VecDeque;
1515
use std::hash::Hash;
@@ -959,27 +959,57 @@ impl Value {
959959

960960
let mut stack = vec![State::ProcessType(ty)];
961961
let mut result_stack = vec![];
962-
while let Some(state) = stack.pop() {
962+
'stack_loop: while let Some(state) = stack.pop() {
963963
match state {
964-
State::ProcessType(ty) => match ty.bound() {
965-
CompleteBound::Unit => result_stack.push(Value::unit()),
966-
CompleteBound::Sum(ref l, ref r) => {
967-
if !bits.next().ok_or(EarlyEndOfStreamError)? {
968-
P::read_left_padding(bits, l, r)?;
969-
stack.push(State::DoSumL(Arc::clone(r)));
970-
stack.push(State::ProcessType(l));
971-
} else {
972-
P::read_right_padding(bits, l, r)?;
973-
stack.push(State::DoSumR(Arc::clone(l)));
974-
stack.push(State::ProcessType(r));
964+
State::ProcessType(ty) if ty.tmr() == Tmr::POWERS_OF_TWO[0] => {
965+
result_stack.push(Value::u1(bits.read_bit()?.into()));
966+
}
967+
State::ProcessType(ty) if ty.tmr() == Tmr::POWERS_OF_TWO[1] => {
968+
result_stack.push(Value::u2(bits.read_u2()?.into()));
969+
}
970+
State::ProcessType(ty) if ty.tmr() == Tmr::POWERS_OF_TWO[2] => {
971+
let u4 = (u8::from(bits.read_u2()?) << 2) + u8::from(bits.read_u2()?);
972+
result_stack.push(Value::u4(u4));
973+
}
974+
State::ProcessType(ty) => {
975+
// The POWERS_OF_TWO array is somewhat misnamed; the ith index contains
976+
// the TMR of TWO^(2^n). So e.g. the 0th index is 2 (a bit), the 1st is
977+
// u2, then u4, and the 3rd is u8.
978+
for (logn, tmr) in Tmr::POWERS_OF_TWO.iter().skip(3).enumerate() {
979+
if ty.tmr() == *tmr {
980+
let mut blob = Vec::with_capacity(1 << logn);
981+
for _ in 0..blob.capacity() {
982+
blob.push(bits.read_u8()?);
983+
}
984+
result_stack.push(Value {
985+
inner: blob.into(),
986+
bit_offset: 0,
987+
ty: Final::two_two_n(logn + 3),
988+
});
989+
continue 'stack_loop;
975990
}
976991
}
977-
CompleteBound::Product(ref l, ref r) => {
978-
stack.push(State::DoProduct);
979-
stack.push(State::ProcessType(r));
980-
stack.push(State::ProcessType(l));
992+
993+
match ty.bound() {
994+
CompleteBound::Unit => result_stack.push(Value::unit()),
995+
CompleteBound::Sum(ref l, ref r) => {
996+
if !bits.next().ok_or(EarlyEndOfStreamError)? {
997+
P::read_left_padding(bits, l, r)?;
998+
stack.push(State::DoSumL(Arc::clone(r)));
999+
stack.push(State::ProcessType(l));
1000+
} else {
1001+
P::read_right_padding(bits, l, r)?;
1002+
stack.push(State::DoSumR(Arc::clone(l)));
1003+
stack.push(State::ProcessType(r));
1004+
}
1005+
}
1006+
CompleteBound::Product(ref l, ref r) => {
1007+
stack.push(State::DoProduct);
1008+
stack.push(State::ProcessType(r));
1009+
stack.push(State::ProcessType(l));
1010+
}
9811011
}
982-
},
1012+
}
9831013
State::DoSumL(r) => {
9841014
let val = result_stack.pop().unwrap();
9851015
result_stack.push(Value::left(val, r));

0 commit comments

Comments
 (0)