-
Notifications
You must be signed in to change notification settings - Fork 12.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deduplicate obligations in opt_normalize_projection_type
#91186
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,10 +8,14 @@ mod project; | |
mod structural_impls; | ||
pub mod util; | ||
|
||
use rustc_data_structures::fx::FxHashMap; | ||
use rustc_hir as hir; | ||
use rustc_middle::ty::error::{ExpectedFound, TypeError}; | ||
use rustc_middle::ty::{self, Const, Ty, TyCtxt}; | ||
use rustc_span::Span; | ||
use std::borrow::{Borrow, Cow}; | ||
use std::collections::hash_map::RawEntryMut; | ||
use std::hash::Hash; | ||
|
||
pub use self::FulfillmentErrorCode::*; | ||
pub use self::ImplSource::*; | ||
|
@@ -105,6 +109,63 @@ pub enum FulfillmentErrorCode<'tcx> { | |
CodeAmbiguity, | ||
} | ||
|
||
pub struct ObligationsDedup<'a, 'tcx, T> { | ||
obligations: &'a mut Vec<Obligation<'tcx, T>>, | ||
} | ||
|
||
impl<'a, 'tcx, T: 'tcx> ObligationsDedup<'a, 'tcx, T> | ||
where | ||
T: Clone + Hash + Eq, | ||
{ | ||
pub fn from_vec(vec: &'a mut Vec<Obligation<'tcx, T>>) -> Self { | ||
ObligationsDedup { obligations: vec } | ||
} | ||
|
||
pub fn extend<'b>(&mut self, iter: impl ExactSizeIterator<Item = Cow<'b, Obligation<'tcx, T>>>) | ||
where | ||
'tcx: 'b, | ||
{ | ||
// obligation tracing has shown that initial batches added to an empty vec do not | ||
// contain any duplicates, so there's no need to attempt deduplication | ||
if self.obligations.is_empty() { | ||
*self.obligations = iter.into_iter().map(Cow::into_owned).collect(); | ||
return; | ||
} | ||
|
||
let initial_size = self.obligations.len(); | ||
let current_capacity = self.obligations.capacity(); | ||
let expected_new = iter.len(); | ||
let combined_size = initial_size + expected_new; | ||
|
||
if combined_size <= 16 || combined_size <= current_capacity { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The checks here are to determine when to skip deduplication work, not directly to reduce memory footprint (although it does indirectly by preventing blowup with duplicate items) |
||
// small case/not crossing a power of two. don't bother with dedup | ||
self.obligations.extend(iter.map(Cow::into_owned)); | ||
} else { | ||
// crossing power of two threshold. this would incur a vec growth anyway if we didn't do | ||
// anything. piggyback a dedup on that | ||
let mut seen = FxHashMap::with_capacity_and_hasher(initial_size, Default::default()); | ||
|
||
let mut is_duplicate = move |obligation: &Obligation<'tcx, _>| -> bool { | ||
return match seen.raw_entry_mut().from_key(obligation) { | ||
RawEntryMut::Occupied(..) => true, | ||
RawEntryMut::Vacant(vacant) => { | ||
vacant.insert(obligation.clone(), ()); | ||
false | ||
} | ||
}; | ||
}; | ||
|
||
self.obligations.retain(|obligation| !is_duplicate(obligation)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know that we don't dedup earlier because of cache effects (a perf optimization), but doesn't that mean that you always incur the O(n) cost of deduplicating the obligations caught here? Can we check what the perf implications would be of having state in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Well, not always the vec is grown, no, that would result in O(n²) time. It's amortized O(n) because we only do it when the vec would grow.
Currently the dedup struct is only constructed temporarily in method scope, it lives much shorter than the vec, so it can't remember whether the vec has already been deduped. It would require larger changes to plumb the dedup state through all method calls that pass the
|
||
self.obligations.extend(iter.filter_map(|obligation| { | ||
if is_duplicate(obligation.borrow()) { | ||
return None; | ||
} | ||
Some(obligation.into_owned()) | ||
})); | ||
} | ||
} | ||
} | ||
|
||
impl<'tcx, O> Obligation<'tcx, O> { | ||
pub fn new( | ||
cause: ObligationCause<'tcx>, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm somewhat confused. Why does
Item
need to be aCow
? I'm not seeing where borrows make sense, but I might be missing something ^_^Wouldn't passing a
&[Obligation<'tcx, T>]
give us maybe better perf? Because doingobligations.into_iter().map(...)
in the callers is incurring new allocations in the hot path.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Items from the projection cache are borrowed, items from a fresh project_type + normalization are owned.
Do you mean
obligations.extend(result.obligations.into_iter().map(Cow::Owned));
? That doesn't allocate new obligations, it just stuffs already owned ones into aCow::Owned
enum variant.