Skip to content

Commit 4cf514a

Browse files
author
Markus Westerlind
committed
perf: Only process changed obligations in ObligationForest
This rewrites most of ObligationForest forest so to avoid iterating through the entire set of obligations on each `select` attempt. Instead only the obligations that can actually make progress are processed. This gives great speedups in benchmarks such as `inflate` which create a large number of pending obligations. To support this the unification tables where extended to to keep track of which type inference variables that has actually changed at each step. Which then lets `ObligationForest` get a list of only the changed variables at each step which it can map back to its obligations. In addition to this primary change, many of the other iterations in `ObligationForest` were refactored to only process lists of the nodes that they actually are interested in. The extra bookkeeping needed for this was possible without the primary change but were a performance regressions there as they slowed down the main loop. As the main loop is no longer the main issue these optimizations could be re-applied.
1 parent 85bbd7d commit 4cf514a

File tree

18 files changed

+1321
-450
lines changed

18 files changed

+1321
-450
lines changed

Cargo.lock

Lines changed: 340 additions & 136 deletions
Large diffs are not rendered by default.

compiler/rustc_data_structures/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ pub mod flock;
7373
pub mod fx;
7474
pub mod graph;
7575
pub mod jobserver;
76+
pub mod logged_unification_table;
7677
pub mod macros;
7778
pub mod map_in_place;
7879
pub mod obligation_forest;
@@ -83,13 +84,15 @@ pub mod small_c_str;
8384
pub mod snapshot_map;
8485
pub mod stable_map;
8586
pub mod svh;
87+
pub mod unify_log;
8688
pub use ena::snapshot_vec;
8789
pub mod sorted_map;
8890
pub mod stable_set;
8991
#[macro_use]
9092
pub mod stable_hasher;
9193
mod atomic_ref;
9294
pub mod fingerprint;
95+
pub mod modified_set;
9396
pub mod profiling;
9497
pub mod sharded;
9598
pub mod stack;
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
use std::ops::Range;
2+
3+
use rustc_index::vec::{Idx, IndexVec};
4+
5+
use crate::modified_set as ms;
6+
use crate::unify as ut;
7+
use crate::unify_log as ul;
8+
9+
pub struct LoggedUnificationTable<K: ut::UnifyKey, I: Idx = K> {
10+
relations: ut::UnificationTable<ut::InPlace<K>>,
11+
unify_log: ul::UnifyLog<I>,
12+
modified_set: ms::ModifiedSet<I>,
13+
reference_counts: IndexVec<I, u32>,
14+
}
15+
16+
impl<K, I> LoggedUnificationTable<K, I>
17+
where
18+
K: ut::UnifyKey + From<I>,
19+
I: Idx + From<K>,
20+
{
21+
pub fn new() -> Self {
22+
Self {
23+
relations: ut::UnificationTable::new(),
24+
unify_log: ul::UnifyLog::new(),
25+
modified_set: ms::ModifiedSet::new(),
26+
reference_counts: IndexVec::new(),
27+
}
28+
}
29+
30+
pub fn unify(&mut self, a: I, b: I)
31+
where
32+
K::Value: ut::UnifyValue<Error = ut::NoError>,
33+
{
34+
self.unify_var_var(a, b).unwrap();
35+
}
36+
37+
pub fn instantiate(&mut self, vid: I, ty: K::Value) -> K
38+
where
39+
K::Value: ut::UnifyValue<Error = ut::NoError>,
40+
{
41+
if !self.unify_log.get(vid).is_empty()
42+
|| self.reference_counts.get(vid).map_or(false, |c| *c != 0)
43+
{
44+
self.modified_set.set(vid);
45+
}
46+
let vid = vid.into();
47+
debug_assert!(self.relations.find(vid) == vid);
48+
self.relations.union_value(vid, ty);
49+
50+
vid
51+
}
52+
53+
pub fn find(&mut self, vid: I) -> K {
54+
self.relations.find(vid)
55+
}
56+
57+
pub fn unify_var_value(
58+
&mut self,
59+
vid: I,
60+
value: K::Value,
61+
) -> Result<(), <K::Value as ut::UnifyValue>::Error> {
62+
let vid = self.find(vid).into();
63+
if !self.unify_log.get(vid).is_empty()
64+
|| self.reference_counts.get(vid).map_or(false, |c| *c != 0)
65+
{
66+
self.modified_set.set(vid);
67+
}
68+
self.relations.unify_var_value(vid, value)
69+
}
70+
71+
pub fn unify_var_var(&mut self, a: I, b: I) -> Result<(), <K::Value as ut::UnifyValue>::Error> {
72+
let a_root = self.relations.find(a);
73+
let b_root = self.relations.find(b);
74+
if a_root == b_root {
75+
return Ok(());
76+
}
77+
78+
self.relations.unify_var_var(a_root, b_root)?;
79+
80+
if a_root == self.relations.find(a_root) {
81+
self.unify_log.unify(a_root.into(), b_root.into());
82+
} else {
83+
self.unify_log.unify(b_root.into(), a_root.into());
84+
}
85+
Ok(())
86+
}
87+
88+
pub fn union_value(&mut self, vid: I, value: K::Value)
89+
where
90+
K::Value: ut::UnifyValue<Error = ut::NoError>,
91+
{
92+
let vid = self.find(vid).into();
93+
self.instantiate(vid, value);
94+
}
95+
96+
pub fn probe_value(&mut self, vid: I) -> K::Value {
97+
self.relations.probe_value(vid)
98+
}
99+
100+
#[inline(always)]
101+
pub fn inlined_probe_value(&mut self, vid: I) -> K::Value {
102+
self.relations.inlined_probe_value(vid)
103+
}
104+
105+
pub fn new_key(&mut self, value: K::Value) -> K {
106+
self.relations.new_key(value)
107+
}
108+
109+
pub fn len(&self) -> usize {
110+
self.relations.len()
111+
}
112+
113+
pub fn snapshot(&mut self) -> Snapshot<K, I> {
114+
Snapshot {
115+
snapshot: self.relations.snapshot(),
116+
unify_log_snapshot: self.unify_log.snapshot(),
117+
modified_snapshot: self.modified_set.snapshot(),
118+
}
119+
}
120+
121+
pub fn rollback_to(&mut self, s: Snapshot<K, I>) {
122+
let Snapshot { snapshot, unify_log_snapshot, modified_snapshot } = s;
123+
self.relations.rollback_to(snapshot);
124+
self.unify_log.rollback_to(unify_log_snapshot);
125+
self.modified_set.rollback_to(modified_snapshot);
126+
}
127+
128+
pub fn commit(&mut self, s: Snapshot<K, I>) {
129+
let Snapshot { snapshot, unify_log_snapshot, modified_snapshot } = s;
130+
self.relations.commit(snapshot);
131+
self.unify_log.commit(unify_log_snapshot);
132+
self.modified_set.commit(modified_snapshot);
133+
}
134+
135+
pub fn vars_since_snapshot(&mut self, s: &Snapshot<K, I>) -> Range<K> {
136+
self.relations.vars_since_snapshot(&s.snapshot)
137+
}
138+
139+
pub fn register(&mut self) -> ms::Offset<I> {
140+
self.modified_set.register()
141+
}
142+
143+
pub fn deregister(&mut self, offset: ms::Offset<I>) {
144+
self.modified_set.deregister(offset);
145+
}
146+
147+
pub fn watch_variable(&mut self, index: I) {
148+
self.reference_counts.ensure_contains_elem(index, || 0);
149+
self.reference_counts[index] += 1;
150+
}
151+
152+
pub fn unwatch_variable(&mut self, index: I) {
153+
self.reference_counts[index] -= 1;
154+
}
155+
156+
pub fn drain_modified_set(&mut self, offset: &ms::Offset<I>, mut f: impl FnMut(I) -> bool) {
157+
let unify_log = &self.unify_log;
158+
self.modified_set.drain(offset, |vid| {
159+
for &unified_vid in unify_log.get(vid) {
160+
f(unified_vid);
161+
}
162+
163+
f(vid)
164+
})
165+
}
166+
}
167+
168+
pub struct Snapshot<K: ut::UnifyKey, I: Idx = K> {
169+
snapshot: ut::Snapshot<ut::InPlace<K>>,
170+
unify_log_snapshot: ul::Snapshot<I>,
171+
modified_snapshot: ms::Snapshot<I>,
172+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
use std::{collections::VecDeque, marker::PhantomData};
2+
3+
use rustc_index::{bit_set::BitSet, vec::Idx};
4+
5+
#[derive(Clone, Debug)]
6+
pub struct ModifiedSet<T: Idx> {
7+
modified: VecDeque<T>,
8+
snapshots: usize,
9+
modified_set: BitSet<T>,
10+
undo_offsets: Vec<usize>,
11+
offsets: Vec<usize>,
12+
}
13+
14+
impl<T: Idx> Default for ModifiedSet<T> {
15+
fn default() -> Self {
16+
Self {
17+
modified: Default::default(),
18+
snapshots: 0,
19+
modified_set: BitSet::new_empty(0),
20+
offsets: Vec::new(),
21+
undo_offsets: Vec::new(),
22+
}
23+
}
24+
}
25+
26+
impl<T: Idx> ModifiedSet<T> {
27+
pub fn new() -> Self {
28+
Self::default()
29+
}
30+
31+
pub fn set(&mut self, index: T) {
32+
if index.index() >= self.modified_set.domain_size() {
33+
self.modified_set.resize(index.index() + 1);
34+
}
35+
if self.modified_set.insert(index) {
36+
self.modified.push_back(index);
37+
}
38+
}
39+
40+
pub fn drain(&mut self, offset: &Offset<T>, mut f: impl FnMut(T) -> bool) {
41+
let offset = &mut self.offsets[offset.index];
42+
for &index in self.modified.iter().skip(*offset) {
43+
if f(index) {}
44+
}
45+
*offset = self.modified.len();
46+
}
47+
48+
pub fn snapshot(&mut self) -> Snapshot<T> {
49+
self.snapshots += 1;
50+
let offsets_start = self.undo_offsets.len();
51+
self.undo_offsets.extend_from_slice(&self.offsets);
52+
Snapshot {
53+
modified_len: self.modified.len(),
54+
offsets_start,
55+
offsets_len: self.offsets.len(),
56+
_marker: PhantomData,
57+
}
58+
}
59+
60+
pub fn rollback_to(&mut self, snapshot: Snapshot<T>) {
61+
self.snapshots -= 1;
62+
for &index in self.modified.iter().skip(snapshot.modified_len) {
63+
self.modified_set.remove(index);
64+
}
65+
self.modified.truncate(snapshot.modified_len);
66+
let mut offsets = self.offsets.iter_mut();
67+
for (offset, &saved_offset) in offsets.by_ref().zip(
68+
&self.undo_offsets
69+
[snapshot.offsets_start..snapshot.offsets_start + snapshot.offsets_len],
70+
) {
71+
*offset = saved_offset;
72+
}
73+
for offset in offsets {
74+
*offset = self.modified.len().min(*offset);
75+
}
76+
self.undo_offsets.truncate(snapshot.offsets_start);
77+
78+
if self.snapshots == 0 {
79+
let min = self.offsets.iter().copied().min().unwrap_or(0);
80+
// Any indices still in `modified` may not have been instantiated, so if we observe them again
81+
// we need to notify any listeners again
82+
for index in self.modified.drain(..min) {
83+
self.modified_set.remove(index);
84+
}
85+
for offset in &mut self.offsets {
86+
*offset -= min;
87+
}
88+
}
89+
}
90+
91+
pub fn commit(&mut self, snapshot: Snapshot<T>) {
92+
self.snapshots -= 1;
93+
self.undo_offsets.truncate(snapshot.offsets_start);
94+
if self.snapshots == 0 {
95+
// Everything up until this point is committed, so we can forget anything before the
96+
// current offsets
97+
let min = self.offsets.iter().copied().min().unwrap_or(0);
98+
self.modified.drain(..min);
99+
for offset in &mut self.offsets {
100+
*offset -= min;
101+
}
102+
}
103+
}
104+
105+
pub fn register(&mut self) -> Offset<T> {
106+
let index = self.offsets.len();
107+
self.offsets.push(0);
108+
Offset { index, _marker: PhantomData }
109+
}
110+
111+
pub fn deregister(&mut self, offset: Offset<T>) {
112+
assert_eq!(offset.index, self.offsets.len() - 1);
113+
self.offsets.pop();
114+
std::mem::forget(offset);
115+
}
116+
}
117+
118+
#[must_use]
119+
pub struct Offset<T> {
120+
index: usize,
121+
_marker: PhantomData<T>,
122+
}
123+
124+
impl<T> Drop for Offset<T> {
125+
fn drop(&mut self) {
126+
if !std::thread::panicking() {
127+
panic!("Offsets should be deregistered")
128+
}
129+
}
130+
}
131+
132+
#[must_use]
133+
#[derive(Debug)]
134+
pub struct Snapshot<T> {
135+
modified_len: usize,
136+
offsets_start: usize,
137+
offsets_len: usize,
138+
_marker: PhantomData<T>,
139+
}

0 commit comments

Comments
 (0)