-
Notifications
You must be signed in to change notification settings - Fork 5
Closed
Description
I've been playing around trying to create a faster .iter_unset_bits(..).count() method (like we discussed before)
This is what i came up with, i have not actually benchmarked it but since it is processing entire words instead of bit-by-bit, it should be substantially faster.
Is this something you want a PR for?
/// Counts the number of set bits.
/// Generates the same result as Vob::iterate_set_bits().count() only much faster.
/// # Examples
///
/// use vob::Vob;
/// let mut v = Vob::new();
/// v.push(false);
/// v.push(true);
/// assert_eq!(v.iter_set_bits(..).count(), v.count_set_bits(..));
///
pub fn count_set_bits<R>(&self, range: R) -> usize
where
R: RangeBounds<usize>,
{
self._count_set_bits(self.process_range(range))
}
/// Counts the number of unset bits.
/// Generates the same result as Vob::iterate_unset_bits().count() only much faster.
/// # Examples
///
/// use vob::Vob;
/// let mut v = Vob::new();
/// v.push(false);
/// v.push(true);
/// assert_eq!(v.iter_unset_bits(..).count(), v.count_unset_bits(..));
///
pub fn count_unset_bits<R>(&self, range: R) -> usize
where
R: RangeBounds<usize>,
{
let range = self.process_range(range);
(range.end - range.start) - self._count_set_bits(range)
}
/// Counts the number of set bits.
/// This method assumes the range is processed with process_range()
fn _count_set_bits(&self, range: Range<usize>) -> usize {
// Early return for empty ranges
if range.start >= range.end {
return 0;
}
let start_word = block_offset::<T>(range.start);
if start_word >= self.len {
return 0;
}
// this -1 is safe since we already tested for range.start & range.end equality
let end_word = blocks_required::<T>(range.end) - 1;
if start_word == end_word {
// Range entirely within one word
let word = self.vec[start_word];
let start_bit = range.start % bits_per_block::<T>();
let end_bit = range.end % bits_per_block::<T>();
// Remove bits before start_bit and bits after end_bit
return count_ones_usize(if end_bit == 0 {
// end_bit = 0 means we want everything from start_bit to end of word
// After the right shift above, we already have what we want
word >> start_bit
} else {
// We want bits from start_bit to end_bit
// After right shift, we need to remove the high bits
(word >> start_bit) << (start_bit + bits_per_block::<T>() - end_bit)
});
}
// First word: shift out bits before start_bit
let start_bit = range.start % bits_per_block::<T>();
let mut count = count_ones_usize(self.vec[start_word] >> start_bit);
// Middle words (unchanged)
for word_idx in (start_word + 1)..end_word {
count += count_ones_usize(self.vec[word_idx]);
}
// Last word: shift out bits after end_bit
let end_bit = range.end % bits_per_block::<T>();
count
+ if end_bit == 0 {
count_ones_usize(self.vec[end_word])
} else {
count_ones_usize(self.vec[end_word] << (bits_per_block::<T>() - end_bit))
}
}
...
/// Convenience function that calls T::count_ones() and converts the result to usize
/// (The conversion is always safe even if T is u128 and usize is u16)
#[inline(always)]
fn count_ones_usize<T: PrimInt>(value: T) -> usize {
use std::convert::TryFrom;
usize::try_from(value.count_ones()).unwrap()
}Issues:
- I created a
count_ones_usize()function only used in _count_set_bits(). Maybe a local function? count_ones_usize()could safely useunwrap_unchecked()n % bits_per_block::<T>()is frequently used, maybe it should be a function likeblock_offset()maybebit_offset()?
Metadata
Metadata
Assignees
Labels
No labels