Skip to content

Commit ecdd374

Browse files
committed
Auto merge of rust-lang#97863 - JakobDegen:bitset-choice, r=nnethercote
`BitSet` related perf improvements This commit makes two changes: 1. Changes `MaybeLiveLocals` to use `ChunkedBitSet` 2. Overrides the `fold` method for the iterator for `ChunkedBitSet` I have local benchmarks verifying that each of these changes individually yield significant perf improvements to rust-lang#96451 . I'm hoping this will be true outside of that context too. If that is not the case, I'll try to gate things on where they help as needed r? `@nnethercote` who I believe was working on closely related things, cc `@tmiasko` because of the destprop pr
2 parents 0423e06 + bc7cd2f commit ecdd374

File tree

5 files changed

+141
-23
lines changed

5 files changed

+141
-23
lines changed

compiler/rustc_index/src/bit_set.rs

+78-5
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,48 @@ impl<T: Idx> BitRelations<HybridBitSet<T>> for ChunkedBitSet<T> {
681681
}
682682
}
683683

684+
impl<T: Idx> BitRelations<ChunkedBitSet<T>> for BitSet<T> {
685+
fn union(&mut self, other: &ChunkedBitSet<T>) -> bool {
686+
sequential_update(|elem| self.insert(elem), other.iter())
687+
}
688+
689+
fn subtract(&mut self, _other: &ChunkedBitSet<T>) -> bool {
690+
unimplemented!("implement if/when necessary");
691+
}
692+
693+
fn intersect(&mut self, other: &ChunkedBitSet<T>) -> bool {
694+
assert_eq!(self.domain_size(), other.domain_size);
695+
let mut changed = false;
696+
for (i, chunk) in other.chunks.iter().enumerate() {
697+
let mut words = &mut self.words[i * CHUNK_WORDS..];
698+
if words.len() > CHUNK_WORDS {
699+
words = &mut words[..CHUNK_WORDS];
700+
}
701+
match chunk {
702+
Chunk::Zeros(..) => {
703+
for word in words {
704+
if *word != 0 {
705+
changed = true;
706+
*word = 0;
707+
}
708+
}
709+
}
710+
Chunk::Ones(..) => (),
711+
Chunk::Mixed(_, _, data) => {
712+
for (i, word) in words.iter_mut().enumerate() {
713+
let new_val = *word & data[i];
714+
if new_val != *word {
715+
changed = true;
716+
*word = new_val;
717+
}
718+
}
719+
}
720+
}
721+
}
722+
changed
723+
}
724+
}
725+
684726
impl<T> Clone for ChunkedBitSet<T> {
685727
fn clone(&self) -> Self {
686728
ChunkedBitSet {
@@ -743,6 +785,41 @@ impl<'a, T: Idx> Iterator for ChunkedBitIter<'a, T> {
743785
}
744786
None
745787
}
788+
789+
fn fold<B, F>(mut self, mut init: B, mut f: F) -> B
790+
where
791+
F: FnMut(B, Self::Item) -> B,
792+
{
793+
// If `next` has already been called, we may not be at the start of a chunk, so we first
794+
// advance the iterator to the start of the next chunk, before proceeding in chunk sized
795+
// steps.
796+
while self.index % CHUNK_BITS != 0 {
797+
let Some(item) = self.next() else {
798+
return init
799+
};
800+
init = f(init, item);
801+
}
802+
let start_chunk = self.index / CHUNK_BITS;
803+
let chunks = &self.bitset.chunks[start_chunk..];
804+
for (i, chunk) in chunks.iter().enumerate() {
805+
let base = (start_chunk + i) * CHUNK_BITS;
806+
match chunk {
807+
Chunk::Zeros(_) => (),
808+
Chunk::Ones(limit) => {
809+
for j in 0..(*limit as usize) {
810+
init = f(init, T::new(base + j));
811+
}
812+
}
813+
Chunk::Mixed(_, _, words) => {
814+
init = BitIter::new(&**words).fold(init, |val, mut item: T| {
815+
item.increment_by(base);
816+
f(val, item)
817+
});
818+
}
819+
}
820+
}
821+
init
822+
}
746823
}
747824

748825
impl Chunk {
@@ -799,11 +876,7 @@ fn sequential_update<T: Idx>(
799876
mut self_update: impl FnMut(T) -> bool,
800877
it: impl Iterator<Item = T>,
801878
) -> bool {
802-
let mut changed = false;
803-
for elem in it {
804-
changed |= self_update(elem);
805-
}
806-
changed
879+
it.fold(false, |changed, elem| self_update(elem) | changed)
807880
}
808881

809882
// Optimization of intersection for SparseBitSet that's generic

compiler/rustc_index/src/bit_set/tests.rs

+57-13
Original file line numberDiff line numberDiff line change
@@ -342,38 +342,82 @@ fn chunked_bitset() {
342342
b10000b.assert_valid();
343343
}
344344

345+
fn with_elements_chunked(elements: &[usize], domain_size: usize) -> ChunkedBitSet<usize> {
346+
let mut s = ChunkedBitSet::new_empty(domain_size);
347+
for &e in elements {
348+
assert!(s.insert(e));
349+
}
350+
s
351+
}
352+
353+
fn with_elements_standard(elements: &[usize], domain_size: usize) -> BitSet<usize> {
354+
let mut s = BitSet::new_empty(domain_size);
355+
for &e in elements {
356+
assert!(s.insert(e));
357+
}
358+
s
359+
}
360+
361+
#[test]
362+
fn chunked_bitset_into_bitset_operations() {
363+
let a = vec![1, 5, 7, 11, 15, 2000, 3000];
364+
let b = vec![3, 4, 11, 3000, 4000];
365+
let aub = vec![1, 3, 4, 5, 7, 11, 15, 2000, 3000, 4000];
366+
let aib = vec![11, 3000];
367+
368+
let b = with_elements_chunked(&b, 9876);
369+
370+
let mut union = with_elements_standard(&a, 9876);
371+
assert!(union.union(&b));
372+
assert!(!union.union(&b));
373+
assert!(union.iter().eq(aub.iter().copied()));
374+
375+
let mut intersection = with_elements_standard(&a, 9876);
376+
assert!(intersection.intersect(&b));
377+
assert!(!intersection.intersect(&b));
378+
assert!(intersection.iter().eq(aib.iter().copied()));
379+
}
380+
345381
#[test]
346382
fn chunked_bitset_iter() {
347-
fn with_elements(elements: &[usize], domain_size: usize) -> ChunkedBitSet<usize> {
348-
let mut s = ChunkedBitSet::new_empty(domain_size);
349-
for &e in elements {
350-
s.insert(e);
383+
fn check_iter(bit: &ChunkedBitSet<usize>, vec: &Vec<usize>) {
384+
// Test collecting via both `.next()` and `.fold()` calls, to make sure both are correct
385+
let mut collect_next = Vec::new();
386+
let mut bit_iter = bit.iter();
387+
while let Some(item) = bit_iter.next() {
388+
collect_next.push(item);
351389
}
352-
s
390+
assert_eq!(vec, &collect_next);
391+
392+
let collect_fold = bit.iter().fold(Vec::new(), |mut v, item| {
393+
v.push(item);
394+
v
395+
});
396+
assert_eq!(vec, &collect_fold);
353397
}
354398

355399
// Empty
356400
let vec: Vec<usize> = Vec::new();
357-
let bit = with_elements(&vec, 9000);
358-
assert_eq!(vec, bit.iter().collect::<Vec<_>>());
401+
let bit = with_elements_chunked(&vec, 9000);
402+
check_iter(&bit, &vec);
359403

360404
// Filled
361405
let n = 10000;
362406
let vec: Vec<usize> = (0..n).collect();
363-
let bit = with_elements(&vec, n);
364-
assert_eq!(vec, bit.iter().collect::<Vec<_>>());
407+
let bit = with_elements_chunked(&vec, n);
408+
check_iter(&bit, &vec);
365409

366410
// Filled with trailing zeros
367411
let n = 10000;
368412
let vec: Vec<usize> = (0..n).collect();
369-
let bit = with_elements(&vec, 2 * n);
370-
assert_eq!(vec, bit.iter().collect::<Vec<_>>());
413+
let bit = with_elements_chunked(&vec, 2 * n);
414+
check_iter(&bit, &vec);
371415

372416
// Mixed
373417
let n = 12345;
374418
let vec: Vec<usize> = vec![0, 1, 2, 2010, 2047, 2099, 6000, 6002, 6004];
375-
let bit = with_elements(&vec, n);
376-
assert_eq!(vec, bit.iter().collect::<Vec<_>>());
419+
let bit = with_elements_chunked(&vec, n);
420+
check_iter(&bit, &vec);
377421
}
378422

379423
#[test]

compiler/rustc_mir_dataflow/src/impls/liveness.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ impl MaybeLiveLocals {
3030
}
3131

3232
impl<'tcx> AnalysisDomain<'tcx> for MaybeLiveLocals {
33-
type Domain = BitSet<Local>;
33+
type Domain = ChunkedBitSet<Local>;
3434
type Direction = Backward;
3535

3636
const NAME: &'static str = "liveness";
3737

3838
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
3939
// bottom = not live
40-
BitSet::new_empty(body.local_decls.len())
40+
ChunkedBitSet::new_empty(body.local_decls.len())
4141
}
4242

4343
fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {

compiler/rustc_mir_dataflow/src/rustc_peek.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use rustc_span::symbol::sym;
22
use rustc_span::Span;
33

4-
use rustc_index::bit_set::BitSet;
4+
use rustc_index::bit_set::ChunkedBitSet;
55
use rustc_middle::mir::MirPass;
66
use rustc_middle::mir::{self, Body, Local, Location};
77
use rustc_middle::ty::{self, Ty, TyCtxt};
@@ -271,7 +271,7 @@ impl<'tcx> RustcPeekAt<'tcx> for MaybeLiveLocals {
271271
&self,
272272
tcx: TyCtxt<'tcx>,
273273
place: mir::Place<'tcx>,
274-
flow_state: &BitSet<Local>,
274+
flow_state: &ChunkedBitSet<Local>,
275275
call: PeekCall,
276276
) {
277277
info!(?place, "peek_at");

compiler/rustc_mir_transform/src/generator.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,8 @@ fn locals_live_across_suspend_points<'tcx>(
495495
let loc = Location { block, statement_index: data.statements.len() };
496496

497497
liveness.seek_to_block_end(block);
498-
let mut live_locals = liveness.get().clone();
498+
let mut live_locals: BitSet<_> = BitSet::new_empty(body.local_decls.len());
499+
live_locals.union(liveness.get());
499500

500501
if !movable {
501502
// The `liveness` variable contains the liveness of MIR locals ignoring borrows.

0 commit comments

Comments
 (0)