Skip to content

Commit ded4dfd

Browse files
committed
Speed up ChunkedBitIter
The current implementation is slow because it does an operation for every bit in the set, even zero bits. So if you have a large bitset with many zero bits (which is common) it's very slow. This commit improves the iterator to skip over `Zeros` chunks in a single step, and uses the fast `BitIter` for `Mixed` chunks. It also removes the existing `fold` implementation, which was only there because the old iterator was slow.
1 parent ff78002 commit ded4dfd

File tree

1 file changed

+40
-55
lines changed

1 file changed

+40
-55
lines changed

compiler/rustc_index/src/bit_set.rs

+40-55
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,18 @@ impl<T: Idx> ChunkedBitSet<T> {
613613
}
614614
}
615615

616+
fn chunk_iter(&self, chunk_index: usize) -> ChunkIter<'_> {
617+
match self.chunks.get(chunk_index) {
618+
Some(Zeros(_chunk_domain_size)) => ChunkIter::Zeros,
619+
Some(Ones(chunk_domain_size)) => ChunkIter::Ones(0..*chunk_domain_size as usize),
620+
Some(Mixed(chunk_domain_size, _, ref words)) => {
621+
let num_words = num_words(*chunk_domain_size as usize);
622+
ChunkIter::Mixed(BitIter::new(&words[0..num_words]))
623+
}
624+
None => ChunkIter::Finished,
625+
}
626+
}
627+
616628
bit_relations_inherent_impls! {}
617629
}
618630

@@ -900,78 +912,44 @@ impl<T> Clone for ChunkedBitSet<T> {
900912
}
901913

902914
pub struct ChunkedBitIter<'a, T: Idx> {
903-
index: usize,
904915
bit_set: &'a ChunkedBitSet<T>,
916+
917+
// The index of the current chunk.
918+
chunk_index: usize,
919+
920+
// The sub-iterator for the current chunk.
921+
chunk_iter: ChunkIter<'a>,
905922
}
906923

907924
impl<'a, T: Idx> ChunkedBitIter<'a, T> {
908925
#[inline]
909926
fn new(bit_set: &'a ChunkedBitSet<T>) -> ChunkedBitIter<'a, T> {
910-
ChunkedBitIter { index: 0, bit_set }
927+
ChunkedBitIter { bit_set, chunk_index: 0, chunk_iter: bit_set.chunk_iter(0) }
911928
}
912929
}
913930

914931
impl<'a, T: Idx> Iterator for ChunkedBitIter<'a, T> {
915932
type Item = T;
916-
fn next(&mut self) -> Option<T> {
917-
while self.index < self.bit_set.domain_size() {
918-
let elem = T::new(self.index);
919-
let chunk = &self.bit_set.chunks[chunk_index(elem)];
920-
match &chunk {
921-
Zeros(chunk_domain_size) => {
922-
self.index += *chunk_domain_size as usize;
923-
}
924-
Ones(_chunk_domain_size) => {
925-
self.index += 1;
926-
return Some(elem);
927-
}
928-
Mixed(_chunk_domain_size, _, words) => loop {
929-
let elem = T::new(self.index);
930-
self.index += 1;
931-
let (word_index, mask) = chunk_word_index_and_mask(elem);
932-
if (words[word_index] & mask) != 0 {
933-
return Some(elem);
934-
}
935-
if self.index % CHUNK_BITS == 0 {
936-
break;
937-
}
938-
},
939-
}
940-
}
941-
None
942-
}
943933

944-
fn fold<B, F>(mut self, mut init: B, mut f: F) -> B
945-
where
946-
F: FnMut(B, Self::Item) -> B,
947-
{
948-
// If `next` has already been called, we may not be at the start of a chunk, so we first
949-
// advance the iterator to the start of the next chunk, before proceeding in chunk sized
950-
// steps.
951-
while self.index % CHUNK_BITS != 0 {
952-
let Some(item) = self.next() else { return init };
953-
init = f(init, item);
954-
}
955-
let start_chunk = self.index / CHUNK_BITS;
956-
let chunks = &self.bit_set.chunks[start_chunk..];
957-
for (i, chunk) in chunks.iter().enumerate() {
958-
let base = (start_chunk + i) * CHUNK_BITS;
959-
match chunk {
960-
Zeros(_) => (),
961-
Ones(limit) => {
962-
for j in 0..(*limit as usize) {
963-
init = f(init, T::new(base + j));
934+
fn next(&mut self) -> Option<T> {
935+
loop {
936+
match &mut self.chunk_iter {
937+
ChunkIter::Zeros => {}
938+
ChunkIter::Ones(iter) => {
939+
if let Some(next) = iter.next() {
940+
return Some(T::new(next + self.chunk_index * CHUNK_BITS));
964941
}
965942
}
966-
Mixed(_, _, words) => {
967-
init = BitIter::new(&**words).fold(init, |val, mut item: T| {
968-
item.increment_by(base);
969-
f(val, item)
970-
});
943+
ChunkIter::Mixed(iter) => {
944+
if let Some(next) = iter.next() {
945+
return Some(T::new(next + self.chunk_index * CHUNK_BITS));
946+
}
971947
}
948+
ChunkIter::Finished => return None,
972949
}
950+
self.chunk_index += 1;
951+
self.chunk_iter = self.bit_set.chunk_iter(self.chunk_index);
973952
}
974-
init
975953
}
976954
}
977955

@@ -1023,6 +1001,13 @@ impl Chunk {
10231001
}
10241002
}
10251003

1004+
enum ChunkIter<'a> {
1005+
Zeros,
1006+
Ones(Range<usize>),
1007+
Mixed(BitIter<'a, usize>),
1008+
Finished,
1009+
}
1010+
10261011
// Applies a function to mutate a bitset, and returns true if any
10271012
// of the applications return true
10281013
fn sequential_update<T: Idx>(

0 commit comments

Comments
 (0)