Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 9c05587

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 46242af commit 9c05587

File tree

1 file changed

+42
-55
lines changed

1 file changed

+42
-55
lines changed

compiler/rustc_index/src/bit_set.rs

Lines changed: 42 additions & 55 deletions
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

@@ -675,6 +687,7 @@ impl<T: Idx> BitRelations<ChunkedBitSet<T>> for ChunkedBitSet<T> {
675687
changed
676688
}
677689

690+
// njn: add test for this
678691
fn subtract(&mut self, other: &ChunkedBitSet<T>) -> bool {
679692
assert_eq!(self.domain_size, other.domain_size);
680693
debug_assert_eq!(self.chunks.len(), other.chunks.len());
@@ -755,6 +768,7 @@ impl<T: Idx> BitRelations<ChunkedBitSet<T>> for ChunkedBitSet<T> {
755768
changed
756769
}
757770

771+
// njn: add test for this
758772
fn intersect(&mut self, other: &ChunkedBitSet<T>) -> bool {
759773
assert_eq!(self.domain_size, other.domain_size);
760774
debug_assert_eq!(self.chunks.len(), other.chunks.len());
@@ -900,78 +914,44 @@ impl<T> Clone for ChunkedBitSet<T> {
900914
}
901915

902916
pub struct ChunkedBitIter<'a, T: Idx> {
903-
index: usize,
904917
bit_set: &'a ChunkedBitSet<T>,
918+
919+
// The index of the current chunk.
920+
chunk_index: usize,
921+
922+
// The sub-iterator for the current chunk.
923+
chunk_iter: ChunkIter<'a>,
905924
}
906925

907926
impl<'a, T: Idx> ChunkedBitIter<'a, T> {
908927
#[inline]
909928
fn new(bit_set: &'a ChunkedBitSet<T>) -> ChunkedBitIter<'a, T> {
910-
ChunkedBitIter { index: 0, bit_set }
929+
ChunkedBitIter { bit_set, chunk_index: 0, chunk_iter: bit_set.chunk_iter(0) }
911930
}
912931
}
913932

914933
impl<'a, T: Idx> Iterator for ChunkedBitIter<'a, T> {
915934
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-
}
943935

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));
936+
fn next(&mut self) -> Option<T> {
937+
loop {
938+
match &mut self.chunk_iter {
939+
ChunkIter::Zeros => {}
940+
ChunkIter::Ones(iter) => {
941+
if let Some(next) = iter.next() {
942+
return Some(T::new(next + self.chunk_index * CHUNK_BITS));
964943
}
965944
}
966-
Mixed(_, _, words) => {
967-
init = BitIter::new(&**words).fold(init, |val, mut item: T| {
968-
item.increment_by(base);
969-
f(val, item)
970-
});
945+
ChunkIter::Mixed(iter) => {
946+
if let Some(next) = iter.next() {
947+
return Some(T::new(next + self.chunk_index * CHUNK_BITS));
948+
}
971949
}
950+
ChunkIter::Finished => return None,
972951
}
952+
self.chunk_index += 1;
953+
self.chunk_iter = self.bit_set.chunk_iter(self.chunk_index);
973954
}
974-
init
975955
}
976956
}
977957

@@ -1023,6 +1003,13 @@ impl Chunk {
10231003
}
10241004
}
10251005

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

0 commit comments

Comments
 (0)