mirror of
https://codeberg.org/icewind/bitbuffer.git
synced 2026-06-03 16:44:06 +02:00
inline for more compile time checks
This commit is contained in:
parent
8342290cc6
commit
833213f640
2 changed files with 138 additions and 68 deletions
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use bitstream_reader::{BitBuffer, LittleEndian};
|
use bitstream_reader::{BigEndian, BitBuffer, Endianness, LittleEndian};
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
fn read_perf(buffer: &BitBuffer<LittleEndian>) -> u16 {
|
fn read_perf<E: Endianness>(buffer: &BitBuffer<E>) -> u16 {
|
||||||
let size = 5;
|
let size = 5;
|
||||||
let mut pos = 0;
|
let mut pos = 0;
|
||||||
let len = buffer.bit_len();
|
let len = buffer.bit_len();
|
||||||
|
|
@ -14,14 +14,13 @@ fn read_perf(buffer: &BitBuffer<LittleEndian>) -> u16 {
|
||||||
if pos + size > len {
|
if pos + size > len {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
let data = buffer.read_int::<u16>(pos, size).unwrap();
|
let data = buffer.read_int::<u64>(pos, size).unwrap() as u16;
|
||||||
result = result.wrapping_add(data);
|
result = result.wrapping_add(data);
|
||||||
pos += size;
|
pos += size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn perf_non_padded(b: &mut Bencher) {
|
fn perf_le(b: &mut Bencher) {
|
||||||
let data = vec![1u8; 1024 * 1024 * 10];
|
let data = vec![1u8; 1024 * 1024 * 10];
|
||||||
let buffer = BitBuffer::new(data, LittleEndian);
|
let buffer = BitBuffer::new(data, LittleEndian);
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
|
|
@ -30,3 +29,56 @@ fn perf_non_padded(b: &mut Bencher) {
|
||||||
test::black_box(data);
|
test::black_box(data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn perf_be(b: &mut Bencher) {
|
||||||
|
let data = vec![1u8; 1024 * 1024 * 10];
|
||||||
|
let buffer = BitBuffer::new(data, BigEndian);
|
||||||
|
b.iter(|| {
|
||||||
|
let data = read_perf(&buffer);
|
||||||
|
assert_eq!(data, 0);
|
||||||
|
test::black_box(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn perf_f32(b: &mut Bencher) {
|
||||||
|
let data = vec![1u8; 1024 * 1024 * 10];
|
||||||
|
let buffer = BitBuffer::new(data, BigEndian);
|
||||||
|
b.iter(|| {
|
||||||
|
let mut pos = 0;
|
||||||
|
let len = buffer.bit_len();
|
||||||
|
let mut result: f32 = 0.0;
|
||||||
|
loop {
|
||||||
|
if pos + 32 > len {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let num = buffer.read_float::<f32>(pos).unwrap();
|
||||||
|
result += num;
|
||||||
|
pos += 32;
|
||||||
|
}
|
||||||
|
assert_eq!(result, 0.00000000000000000000000000000006170106);
|
||||||
|
test::black_box(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn perf_f64(b: &mut Bencher) {
|
||||||
|
let data = vec![1u8; 1024 * 1024 * 10];
|
||||||
|
let buffer = BitBuffer::new(data, BigEndian);
|
||||||
|
b.iter(|| {
|
||||||
|
let mut pos = 0;
|
||||||
|
let len = buffer.bit_len();
|
||||||
|
let mut result: f64 = 0.0;
|
||||||
|
loop {
|
||||||
|
if pos + 64 > len {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let num = buffer.read_float::<f64>(pos).unwrap();
|
||||||
|
result += num;
|
||||||
|
pos += 64;
|
||||||
|
}
|
||||||
|
assert_eq!(result, 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010156250477904244);
|
||||||
|
test::black_box(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::cmp::min;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::mem::size_of;
|
use std::mem::{size_of, transmute};
|
||||||
use std::ops::BitOrAssign;
|
use std::ops::BitOrAssign;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
|
@ -88,11 +88,12 @@ where
|
||||||
|
|
||||||
fn read_usize(&self, position: usize, count: usize) -> usize {
|
fn read_usize(&self, position: usize, count: usize) -> usize {
|
||||||
let byte_index = position / 8;
|
let byte_index = position / 8;
|
||||||
let bit_offset = position - byte_index * 8;
|
let bit_offset = position & 7;
|
||||||
|
let usize_bit_size = size_of::<usize>() * 8;
|
||||||
let raw_container: &usize = unsafe {
|
let raw_container: &usize = unsafe {
|
||||||
// this is safe here because we already have checks that we don't read past the slice
|
// this is safe here because we already have checks that we don't read past the slice
|
||||||
let ptr = self.bytes.as_ptr().add(byte_index);
|
let ptr = self.bytes.as_ptr().add(byte_index);
|
||||||
std::mem::transmute(ptr)
|
transmute(ptr)
|
||||||
};
|
};
|
||||||
let container = if E::is_le() {
|
let container = if E::is_le() {
|
||||||
usize::from_le(*raw_container)
|
usize::from_le(*raw_container)
|
||||||
|
|
@ -102,7 +103,7 @@ where
|
||||||
let shifted = if E::is_le() {
|
let shifted = if E::is_le() {
|
||||||
container >> bit_offset
|
container >> bit_offset
|
||||||
} else {
|
} else {
|
||||||
container >> (USIZE_SIZE * 8 - bit_offset - count)
|
container >> (usize_bit_size - bit_offset - count)
|
||||||
};
|
};
|
||||||
let mask = !(usize::max_value() << count);
|
let mask = !(usize::max_value() << count);
|
||||||
shifted & mask
|
shifted & mask
|
||||||
|
|
@ -182,26 +183,14 @@ where
|
||||||
T: PrimInt + BitOrAssign + IsSigned + UncheckedPrimitiveInt,
|
T: PrimInt + BitOrAssign + IsSigned + UncheckedPrimitiveInt,
|
||||||
{
|
{
|
||||||
let type_bit_size = size_of::<T>() * 8;
|
let type_bit_size = size_of::<T>() * 8;
|
||||||
|
let usize_bit_size = size_of::<usize>() * 8;
|
||||||
|
|
||||||
// by splitting of the count check and the actual reading
|
|
||||||
// we can inline the count check (which get's resolved at compile time if a constant count is used)
|
|
||||||
// without having to pay the potentional cost of inlining a larger function
|
|
||||||
if type_bit_size < count {
|
if type_bit_size < count {
|
||||||
return Err(ReadError::TooManyBits {
|
return Err(ReadError::TooManyBits {
|
||||||
requested: count,
|
requested: count,
|
||||||
max: type_bit_size,
|
max: type_bit_size,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
self.read_int_no_count_check(position, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_int_no_count_check<T>(&self, position: usize, count: usize) -> Result<T>
|
|
||||||
where
|
|
||||||
T: PrimInt + BitOrAssign + IsSigned + UncheckedPrimitiveInt,
|
|
||||||
{
|
|
||||||
let value = {
|
|
||||||
let type_bit_size = size_of::<T>() * 8;
|
|
||||||
let usize_bit_size = size_of::<usize>() * 8;
|
|
||||||
|
|
||||||
if position + count > self.bit_len {
|
if position + count > self.bit_len {
|
||||||
if position > self.bit_len {
|
if position > self.bit_len {
|
||||||
|
|
@ -218,17 +207,42 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
let bit_offset = position & 7;
|
let bit_offset = position & 7;
|
||||||
if size_of::<usize>() > size_of::<T>() || count + bit_offset < usize_bit_size {
|
|
||||||
|
let fit_usize = count + bit_offset < usize_bit_size;
|
||||||
|
let value = if fit_usize {
|
||||||
|
self.read_fit_usize(position, count)
|
||||||
|
} else {
|
||||||
|
self.read_no_fit_usize(position, count)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(if count == type_bit_size && fit_usize {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
self.make_signed(value, count)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn read_fit_usize<T>(&self, position: usize, count: usize) -> T
|
||||||
|
where
|
||||||
|
T: PrimInt + BitOrAssign + IsSigned + UncheckedPrimitiveInt,
|
||||||
|
{
|
||||||
|
let type_bit_size = size_of::<T>() * 8;
|
||||||
let raw = self.read_usize(position, count);
|
let raw = self.read_usize(position, count);
|
||||||
let max_signed_value = (1 << (type_bit_size - 1)) - 1;
|
let max_signed_value = (1 << (type_bit_size - 1)) - 1;
|
||||||
if T::is_signed() && raw > max_signed_value {
|
if T::is_signed() && raw > max_signed_value {
|
||||||
return Ok(T::zero() - T::from_unchecked(raw & max_signed_value));
|
T::zero() - T::from_unchecked(raw & max_signed_value)
|
||||||
} else {
|
} else {
|
||||||
T::from_unchecked(raw)
|
T::from_unchecked(raw)
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
fn read_no_fit_usize<T>(&self, position: usize, count: usize) -> T
|
||||||
|
where
|
||||||
|
T: PrimInt + BitOrAssign + IsSigned + UncheckedPrimitiveInt,
|
||||||
|
{
|
||||||
let mut left_to_read = count;
|
let mut left_to_read = count;
|
||||||
let mut partial = T::zero();
|
let mut acc = T::zero();
|
||||||
let max_read = (size_of::<usize>() - 1) * 8;
|
let max_read = (size_of::<usize>() - 1) * 8;
|
||||||
let mut read_pos = position;
|
let mut read_pos = position;
|
||||||
let mut bit_offset = 0;
|
let mut bit_offset = 0;
|
||||||
|
|
@ -237,27 +251,30 @@ where
|
||||||
let read = min(min(left_to_read, max_read), bits_left);
|
let read = min(min(left_to_read, max_read), bits_left);
|
||||||
let data = T::from_unchecked(self.read_usize(read_pos, read));
|
let data = T::from_unchecked(self.read_usize(read_pos, read));
|
||||||
if E::is_le() {
|
if E::is_le() {
|
||||||
partial |= data << bit_offset;
|
acc |= data << bit_offset;
|
||||||
} else {
|
} else {
|
||||||
partial = partial << read;
|
acc = acc << read;
|
||||||
partial |= data;
|
acc |= data;
|
||||||
}
|
}
|
||||||
bit_offset += read;
|
bit_offset += read;
|
||||||
read_pos += read;
|
read_pos += read;
|
||||||
left_to_read -= read;
|
left_to_read -= read;
|
||||||
}
|
}
|
||||||
|
|
||||||
partial
|
acc
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
|
fn make_signed<T>(&self, value: T, count: usize) -> T
|
||||||
|
where
|
||||||
|
T: PrimInt + BitOrAssign + IsSigned + UncheckedPrimitiveInt,
|
||||||
|
{
|
||||||
if T::is_signed() {
|
if T::is_signed() {
|
||||||
let sign_bit = value >> (count - 1) & T::one();
|
let sign_bit = value >> (count - 1) & T::one();
|
||||||
let absolute_value = value & !(T::max_value() << (count - 1));
|
let absolute_value = value & !(T::max_value() << (count - 1));
|
||||||
let sign = T::one() - sign_bit - sign_bit;
|
let sign = T::one() - sign_bit - sign_bit;
|
||||||
Ok(absolute_value * sign)
|
absolute_value * sign
|
||||||
} else {
|
} else {
|
||||||
Ok(value)
|
value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -426,7 +443,8 @@ where
|
||||||
where
|
where
|
||||||
T: Float + UncheckedPrimitiveFloat,
|
T: Float + UncheckedPrimitiveFloat,
|
||||||
{
|
{
|
||||||
if position + size_of::<T>() * 8 > self.bit_len {
|
let type_bit_size = size_of::<T>() * 8;
|
||||||
|
if position + type_bit_size > self.bit_len {
|
||||||
if position > self.bit_len {
|
if position > self.bit_len {
|
||||||
return Err(ReadError::IndexOutOfBounds {
|
return Err(ReadError::IndexOutOfBounds {
|
||||||
pos: position,
|
pos: position,
|
||||||
|
|
@ -442,13 +460,13 @@ where
|
||||||
|
|
||||||
if size_of::<T>() == 4 {
|
if size_of::<T>() == 4 {
|
||||||
let int = if size_of::<T>() < USIZE_SIZE {
|
let int = if size_of::<T>() < USIZE_SIZE {
|
||||||
self.read_usize(position, 32) as u32
|
self.read_fit_usize::<u32>(position, 32)
|
||||||
} else {
|
} else {
|
||||||
self.read_int::<u32>(position, 32)?
|
self.read_no_fit_usize::<u32>(position, 32)
|
||||||
};
|
};
|
||||||
Ok(T::from_f32_unchecked(f32::from_bits(int)))
|
Ok(T::from_f32_unchecked(f32::from_bits(int)))
|
||||||
} else {
|
} else {
|
||||||
let int = self.read_int::<u64>(position, 64)?;
|
let int = self.read_no_fit_usize::<u64>(position, 64);
|
||||||
Ok(T::from_f64_unchecked(f64::from_bits(int)))
|
Ok(T::from_f64_unchecked(f64::from_bits(int)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue