Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions roaring/src/bitmap/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,14 @@ impl Iter<'_> {
.next_range_back()
.map(|r| util::join(self.key, *r.start())..=util::join(self.key, *r.end()))
}

/// Read multiple values from the iterator into `dst`.
/// Returns a mutable slice of `dst` that contains the read values.
///
/// This can be significantly faster than calling `next()` repeatedly.
pub(crate) fn next_many<'a>(&mut self, dst: &'a mut [u32]) -> &'a mut [u32] {
self.inner.next_many(self.key, dst)
}
}

impl fmt::Debug for Container {
Expand Down
12 changes: 4 additions & 8 deletions roaring/src/bitmap/inherent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,15 +400,11 @@ impl RoaringBitmap {
pub fn remove(&mut self, value: u32) -> bool {
let (key, index) = util::split(value);
match self.containers.binary_search_by_key(&key, |c| c.key) {
Ok(loc) => {
if self.containers[loc].remove(index) {
if self.containers[loc].is_empty() {
self.containers.remove(loc);
}
true
} else {
false
Ok(loc) if self.containers[loc].remove(index) => {
if self.containers[loc].is_empty() {
self.containers.remove(loc);
}
true
}
_ => false,
}
Expand Down
149 changes: 149 additions & 0 deletions roaring/src/bitmap/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,81 @@ impl Iter<'_> {
pub fn next_range_back(&mut self) -> Option<core::ops::RangeInclusive<u32>> {
next_range_back_impl(&mut self.front, &mut self.containers, &mut self.back)
}

/// Retrieve the next `dst.len()` values from the iterator and write them into `dst`.
///
/// Returns a mutable slice of `dst` that contains the read values. A slice shorter
/// than `dst.len()` is returned if the iterator is exhausted.
///
/// This method is significantly faster than calling `next()` repeatedly due to
/// reduced per-element overhead and better CPU cache utilization.
///
/// # Examples
///
/// ```rust
/// use roaring::RoaringBitmap;
///
/// let bitmap: RoaringBitmap = (0..100).collect();
/// let mut iter = bitmap.iter();
/// let mut buf = [0u32; 32];
///
/// let out = iter.next_many(&mut buf);
/// assert_eq!(out.len(), 32);
/// assert_eq!(out[0], 0);
/// assert_eq!(out[31], 31);
///
/// // Iterate remainder
/// let out = iter.next_many(&mut buf);
/// assert_eq!(out.len(), 32);
/// assert_eq!(out[0], 32);
/// ```
pub fn next_many<'a>(&mut self, dst: &'a mut [u32]) -> &'a mut [u32] {
if dst.is_empty() {
return &mut [];
}

let mut count = 0;

// First drain from the front container iterator if present
if let Some(ref mut front_iter) = self.front {
count += front_iter.next_many(&mut dst[count..]).len();
if count >= dst.len() {
return &mut dst[..count];
}
// Front is exhausted
self.front = None;
}

// Process remaining containers
while count < dst.len() {
let Some(container) = self.containers.next() else {
// No more containers in the middle, try the back
break;
};
let mut container_iter = container.into_iter();
let out = container_iter.next_many(&mut dst[count..]);
count += out.len();

// If container still has values, save it as new front
if !out.is_empty() && container_iter.len() > 0 {
self.front = Some(container_iter);
return &mut dst[..count];
}
}

// Finally, try draining from the back iterator if present
if count < dst.len() {
if let Some(ref mut back_iter) = self.back {
let n = back_iter.next_many(&mut dst[count..]);
count += n.len();
if back_iter.len() == 0 {
self.back = None;
}
}
}

&mut dst[..count]
}
}

impl IntoIter {
Expand Down Expand Up @@ -419,6 +494,80 @@ impl IntoIter {
pub fn next_range_back(&mut self) -> Option<core::ops::RangeInclusive<u32>> {
next_range_back_impl(&mut self.front, &mut self.containers, &mut self.back)
}

/// Retrieve the next `dst.len()` values from the iterator and write them into `dst`.
///
/// Returns a mutable slice of `dst` that contains the read values. A slice shorter
/// than `dst.len()` is returned if the iterator is exhausted.
///
/// This method is significantly faster than calling `next()` repeatedly due to
/// reduced per-element overhead and better CPU cache utilization.
///
/// # Examples
///
/// ```rust
/// use roaring::RoaringBitmap;
///
/// let bitmap: RoaringBitmap = (0..100).collect();
/// let mut iter = bitmap.into_iter();
/// let mut buf = [0u32; 32];
///
/// let out = iter.next_many(&mut buf);
/// assert_eq!(out.len(), 32);
/// assert_eq!(out[0], 0);
/// assert_eq!(out[31], 31);
///
/// // Iterate remainder
/// let out = iter.next_many(&mut buf);
/// assert_eq!(out.len(), 32);
/// assert_eq!(out[0], 32);
/// ```
pub fn next_many<'a>(&mut self, dst: &'a mut [u32]) -> &'a mut [u32] {
if dst.is_empty() {
return &mut [];
}

let mut count = 0;

// First drain from the front container iterator if present
if let Some(ref mut front_iter) = self.front {
count += front_iter.next_many(&mut dst[count..]).len();
if count >= dst.len() {
return &mut dst[..count];
}
// Front is exhausted
self.front = None;
}

// Process remaining containers
while count < dst.len() {
let Some(container) = self.containers.next() else {
// No more containers in the middle, try the back
break;
};
let mut container_iter = container.into_iter();
let out = container_iter.next_many(&mut dst[count..]);
count += out.len();

// If container still has values, save it as new front
if !out.is_empty() && container_iter.len() > 0 {
self.front = Some(container_iter);
return &mut dst[..count];
}
}

// Finally, try draining from the back iterator if present
if count < dst.len() {
if let Some(ref mut back_iter) = self.back {
count += back_iter.next_many(&mut dst[count..]).len();
if back_iter.len() == 0 {
self.back = None;
}
}
}

&mut dst[..count]
}
}

fn size_hint_impl(
Expand Down
51 changes: 51 additions & 0 deletions roaring/src/bitmap/store/bitmap_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use core::fmt::{Display, Formatter};
use core::mem::size_of;
use core::ops::{BitAndAssign, BitOrAssign, BitXorAssign, RangeInclusive, SubAssign};

use crate::bitmap::util;

use super::{ArrayStore, Interval};

#[cfg(not(feature = "std"))]
Expand Down Expand Up @@ -691,6 +693,55 @@ impl<B: Borrow<[u64; BITMAP_LENGTH]>> BitmapIter<B> {
let index = 63 - index_from_left;
Some(64 * key_back + index)
}

/// Read multiple values from the iterator into `dst`.
/// Returns a mutable slice of `dst` that contains the read values.
///
/// This can be significantly faster than calling `next()` repeatedly.
pub fn next_many<'a>(&mut self, high: u16, dst: &'a mut [u32]) -> &'a mut [u32] {
if dst.is_empty() {
return &mut [];
}

let mut count = 0;
let bits = self.bits.borrow();

while count < dst.len() {
// Advance to next non-zero word if current is empty
if self.value == 0 {
if self.key >= self.key_back {
break;
}
loop {
self.key += 1;
if self.key == self.key_back {
self.value = core::mem::replace(&mut self.value_back, 0);
break;
}
// Safety: key is always in bounds
self.value = unsafe { *bits.get_unchecked(self.key as usize) };
if self.value != 0 {
break;
}
}
if self.value == 0 {
break;
}
}

// Extract set bits from current word
let base = self.key * 64;
while self.value != 0 && count < dst.len() {
let bit_pos = self.value.trailing_zeros() as u16;
dst[count] = util::join(high, base + bit_pos);
count += 1;
// Clear the lowest set bit
self.value &= self.value - 1;
}
}

&mut dst[..count]
}
}

fn advance_to_next_nonzero_word<'a>(
Expand Down
52 changes: 52 additions & 0 deletions roaring/src/bitmap/store/interval_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use core::ops::{
use core::slice::Iter;
use core::{cmp::Ordering, ops::ControlFlow};

use crate::bitmap::util;

use super::{ArrayStore, BitmapStore};

#[derive(PartialEq, Eq, Clone, Debug)]
Expand Down Expand Up @@ -834,6 +836,56 @@ impl<I: SliceIterator<Interval>> RunIter<I> {
let result = self.intervals.as_slice().last()?.end - self.backward_offset;
Some(result)
}

/// Read multiple values from the iterator into `dst`.
/// Returns a mutable slice of `dst` that contains the read values.
///
/// This can be significantly faster than calling `next()` repeatedly
/// because it processes runs in bulk.
pub fn next_many<'a>(&mut self, high: u16, dst: &'a mut [u32]) -> &'a mut [u32] {
if dst.is_empty() {
return &mut [];
}

let mut count = 0;

while count < dst.len() {
let Some(interval) = self.intervals.as_slice().first() else {
break;
};

let end_offset =
if self.intervals.as_slice().len() == 1 { self.backward_offset } else { 0 };

let start = interval.start + self.forward_offset;
let end = interval.end - end_offset;

// How many values can we emit from this interval?
let available = (end - start + 1) as usize;
let to_emit = available.min(dst.len() - count);

// Emit values
for i in 0..to_emit {
dst[count + i] = util::join(high, start + i as u16);
}
count += to_emit;

// Advance within or past this interval
if to_emit == available {
// Consumed entire interval
_ = self.intervals.next();
self.forward_offset = 0;
if self.intervals.as_slice().is_empty() {
self.backward_offset = 0;
}
} else {
// Partial consumption
self.forward_offset += to_emit as u16;
}
}

&mut dst[..count]
}
}

impl<I: SliceIterator<Interval>> Iterator for RunIter<I> {
Expand Down
40 changes: 40 additions & 0 deletions roaring/src/bitmap/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub(crate) use interval_store::{IntervalStore, RunIterBorrowed, RunIterOwned};
pub(crate) use interval_store::{RUN_ELEMENT_BYTES, RUN_NUM_BYTES};

use crate::bitmap::container::ARRAY_LIMIT;
use crate::bitmap::util;

#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
Expand Down Expand Up @@ -1038,6 +1039,45 @@ impl Iterator for Iter<'_> {
}
}

impl Iter<'_> {
/// Read multiple values from the iterator into `dst`.
/// Returns a mutable slice of `dst` that contains the read values.
///
/// This can be significantly faster than calling `next()` repeatedly.
pub fn next_many<'a>(&mut self, high: u16, dst: &'a mut [u32]) -> &'a mut [u32] {
match self {
Iter::Array(inner) => {
let remaining = inner.as_slice();
let n = remaining.len().min(dst.len());
dst[..n]
.iter_mut()
.zip(&remaining[..n])
.for_each(|(o, low)| *o = util::join(high, *low));
if n > 0 {
_ = inner.nth(n - 1);
}
&mut dst[..n]
}
Iter::Vec(inner) => {
let remaining = inner.as_slice();
let n = remaining.len().min(dst.len());
dst[..n]
.iter_mut()
.zip(&remaining[..n])
.for_each(|(o, low)| *o = util::join(high, *low));
if n > 0 {
_ = inner.nth(n - 1);
}
&mut dst[..n]
}
Iter::BitmapBorrowed(inner) => inner.next_many(high, dst),
Iter::BitmapOwned(inner) => inner.next_many(high, dst),
Iter::RunBorrowed(inner) => inner.next_many(high, dst),
Iter::RunOwned(inner) => inner.next_many(high, dst),
}
}
}

impl DoubleEndedIterator for Iter<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
match self {
Expand Down
Loading