Skip to content

Commit

Permalink
value: special-case a bunch of primitive numeric types in from_bits
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
apoelstra committed Feb 10, 2025
1 parent 36d7fb9 commit d7a9d2c
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 18 deletions.
11 changes: 11 additions & 0 deletions src/bit_encoding/bititer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ pub enum u2 {
_3,
}

impl From<u2> for u8 {
fn from(s: u2) -> u8 {
match s {
u2::_0 => 0,
u2::_1 => 1,
u2::_2 => 2,
u2::_3 => 3,
}
}
}

/// Bitwise iterator formed from a wrapped bytewise iterator. Bytes are
/// interpreted big-endian, i.e. MSB is returned first
#[derive(Debug)]
Expand Down
66 changes: 48 additions & 18 deletions src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::dag::{Dag, DagLike};
use crate::types::{CompleteBound, Final};
use crate::BitIter;

use crate::{BitCollector, EarlyEndOfStreamError};
use crate::{BitCollector, EarlyEndOfStreamError, Tmr};
use core::{fmt, iter};
use std::collections::VecDeque;
use std::hash::Hash;
Expand Down Expand Up @@ -950,27 +950,57 @@ impl Value {

let mut stack = vec![State::ProcessType(ty)];
let mut result_stack = vec![];
while let Some(state) = stack.pop() {
'stack_loop: while let Some(state) = stack.pop() {
match state {
State::ProcessType(ty) => match ty.bound() {
CompleteBound::Unit => result_stack.push(Value::unit()),
CompleteBound::Sum(ref l, ref r) => {
if !bits.next().ok_or(EarlyEndOfStreamError)? {
P::read_left_padding(bits, l, r)?;
stack.push(State::DoSumL(Arc::clone(r)));
stack.push(State::ProcessType(l));
} else {
P::read_right_padding(bits, l, r)?;
stack.push(State::DoSumR(Arc::clone(l)));
stack.push(State::ProcessType(r));
State::ProcessType(ty) if ty.tmr() == Tmr::POWERS_OF_TWO[0] => {
result_stack.push(Value::u1(bits.read_bit()?.into()));
}
State::ProcessType(ty) if ty.tmr() == Tmr::POWERS_OF_TWO[1] => {
result_stack.push(Value::u2(bits.read_u2()?.into()));
}
State::ProcessType(ty) if ty.tmr() == Tmr::POWERS_OF_TWO[2] => {
let u4 = (u8::from(bits.read_u2()?) << 2) + u8::from(bits.read_u2()?);
result_stack.push(Value::u4(u4));
}
State::ProcessType(ty) => {
// The POWERS_OF_TWO array is somewhat misnamed; the ith index contains
// the TMR of TWO^(2^n). So e.g. the 0th index is 2 (a bit), the 1st is
// u2, then u4, and the 3rd is u8.
for (logn, tmr) in Tmr::POWERS_OF_TWO.iter().skip(3).enumerate() {
if ty.tmr() == *tmr {
let mut blob = Vec::with_capacity(1 << logn);
for _ in 0..blob.capacity() {
blob.push(bits.read_u8()?);
}
result_stack.push(Value {
inner: blob.into(),
bit_offset: 0,
ty: Final::two_two_n(logn + 3),
});
continue 'stack_loop;
}
}
CompleteBound::Product(ref l, ref r) => {
stack.push(State::DoProduct);
stack.push(State::ProcessType(r));
stack.push(State::ProcessType(l));

match ty.bound() {
CompleteBound::Unit => result_stack.push(Value::unit()),
CompleteBound::Sum(ref l, ref r) => {
if !bits.next().ok_or(EarlyEndOfStreamError)? {
P::read_left_padding(bits, l, r)?;
stack.push(State::DoSumL(Arc::clone(r)));
stack.push(State::ProcessType(l));
} else {
P::read_right_padding(bits, l, r)?;
stack.push(State::DoSumR(Arc::clone(l)));
stack.push(State::ProcessType(r));
}
}
CompleteBound::Product(ref l, ref r) => {
stack.push(State::DoProduct);
stack.push(State::ProcessType(r));
stack.push(State::ProcessType(l));
}
}
},
}
State::DoSumL(r) => {
let val = result_stack.pop().unwrap();
result_stack.push(Value::left(val, r));
Expand Down

0 comments on commit d7a9d2c

Please sign in to comment.