Skip to content

Commit f686885

Browse files
committed
Auto merge of #52342 - nnethercote:CanonicalVar, r=nikomatsakis
Avoid most allocations in `Canonicalizer`. Extra allocations are a significant cost of NLL, and the most common ones come from within `Canonicalizer`. In particular, `canonical_var()` contains this code: indices .entry(kind) .or_insert_with(|| { let cvar1 = variables.push(info); let cvar2 = var_values.push(kind); assert_eq!(cvar1, cvar2); cvar1 }) .clone() `variables` and `var_values` are `Vec`s. `indices` is a `HashMap` used to track what elements have been inserted into `var_values`. If `kind` hasn't been seen before, `indices`, `variables` and `var_values` all get a new element. (The number of elements in each container is always the same.) This results in lots of allocations. In practice, most of the time these containers only end up holding a few elements. This PR changes them to avoid heap allocations in the common case, by changing the `Vec`s to `SmallVec`s and only using `indices` once enough elements are present. (When the number of elements is small, a direct linear search of `var_values` is as good or better than a hashmap lookup.) The changes to `variables` are straightforward and contained within `Canonicalizer`. The changes to `indices` are more complex but also contained within `Canonicalizer`. The changes to `var_values` are more intrusive because they require defining a new type `SmallCanonicalVarValues` -- which is to `CanonicalVarValues` as `SmallVec` is to `Vec -- and passing stack-allocated values of that type in from outside. All this speeds up a number of NLL "check" builds, the best by 2%. r? @nikomatsakis
2 parents 4f3c7a4 + 7cc5277 commit f686885

File tree

12 files changed

+121
-63
lines changed

12 files changed

+121
-63
lines changed

src/librustc/infer/canonical/canonicalizer.rs

+68-32
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
//! [c]: https://rust-lang-nursery.github.io/rustc-guide/traits/canonicalization.html
1717
1818
use infer::canonical::{
19-
Canonical, CanonicalTyVarKind, CanonicalVarInfo, CanonicalVarKind, CanonicalVarValues,
20-
Canonicalized,
19+
Canonical, CanonicalTyVarKind, CanonicalVarInfo, CanonicalVarKind, Canonicalized,
20+
SmallCanonicalVarValues,
2121
};
2222
use infer::InferCtxt;
2323
use std::sync::atomic::Ordering;
@@ -26,7 +26,8 @@ use ty::subst::Kind;
2626
use ty::{self, CanonicalVar, Lift, Slice, Ty, TyCtxt, TypeFlags};
2727

2828
use rustc_data_structures::fx::FxHashMap;
29-
use rustc_data_structures::indexed_vec::IndexVec;
29+
use rustc_data_structures::indexed_vec::Idx;
30+
use rustc_data_structures::small_vec::SmallVec;
3031

3132
impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
3233
/// Canonicalizes a query value `V`. When we canonicalize a query,
@@ -47,7 +48,8 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
4748
pub fn canonicalize_query<V>(
4849
&self,
4950
value: &V,
50-
) -> (Canonicalized<'gcx, V>, CanonicalVarValues<'tcx>)
51+
var_values: &mut SmallCanonicalVarValues<'tcx>
52+
) -> Canonicalized<'gcx, V>
5153
where
5254
V: TypeFoldable<'tcx> + Lift<'gcx>,
5355
{
@@ -65,6 +67,7 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
6567
static_region: true,
6668
other_free_regions: true,
6769
},
70+
var_values,
6871
)
6972
}
7073

@@ -96,10 +99,11 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
9699
pub fn canonicalize_response<V>(
97100
&self,
98101
value: &V,
99-
) -> (Canonicalized<'gcx, V>, CanonicalVarValues<'tcx>)
102+
) -> Canonicalized<'gcx, V>
100103
where
101104
V: TypeFoldable<'tcx> + Lift<'gcx>,
102105
{
106+
let mut var_values = SmallVec::new();
103107
Canonicalizer::canonicalize(
104108
value,
105109
Some(self),
@@ -108,6 +112,7 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
108112
static_region: false,
109113
other_free_regions: false,
110114
},
115+
&mut var_values
111116
)
112117
}
113118

@@ -123,7 +128,8 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
123128
pub fn canonicalize_hr_query_hack<V>(
124129
&self,
125130
value: &V,
126-
) -> (Canonicalized<'gcx, V>, CanonicalVarValues<'tcx>)
131+
var_values: &mut SmallCanonicalVarValues<'tcx>
132+
) -> Canonicalized<'gcx, V>
127133
where
128134
V: TypeFoldable<'tcx> + Lift<'gcx>,
129135
{
@@ -141,6 +147,7 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
141147
static_region: false,
142148
other_free_regions: true,
143149
},
150+
var_values
144151
)
145152
}
146153
}
@@ -163,9 +170,11 @@ impl CanonicalizeRegionMode {
163170
struct Canonicalizer<'cx, 'gcx: 'tcx, 'tcx: 'cx> {
164171
infcx: Option<&'cx InferCtxt<'cx, 'gcx, 'tcx>>,
165172
tcx: TyCtxt<'cx, 'gcx, 'tcx>,
166-
variables: IndexVec<CanonicalVar, CanonicalVarInfo>,
173+
variables: SmallVec<[CanonicalVarInfo; 8]>,
174+
var_values: &'cx mut SmallCanonicalVarValues<'tcx>,
175+
// Note that indices is only used once `var_values` is big enough to be
176+
// heap-allocated.
167177
indices: FxHashMap<Kind<'tcx>, CanonicalVar>,
168-
var_values: IndexVec<CanonicalVar, Kind<'tcx>>,
169178
canonicalize_region_mode: CanonicalizeRegionMode,
170179
needs_canonical_flags: TypeFlags,
171180
}
@@ -295,7 +304,8 @@ impl<'cx, 'gcx, 'tcx> Canonicalizer<'cx, 'gcx, 'tcx> {
295304
infcx: Option<&'cx InferCtxt<'cx, 'gcx, 'tcx>>,
296305
tcx: TyCtxt<'cx, 'gcx, 'tcx>,
297306
canonicalize_region_mode: CanonicalizeRegionMode,
298-
) -> (Canonicalized<'gcx, V>, CanonicalVarValues<'tcx>)
307+
var_values: &'cx mut SmallCanonicalVarValues<'tcx>
308+
) -> Canonicalized<'gcx, V>
299309
where
300310
V: TypeFoldable<'tcx> + Lift<'gcx>,
301311
{
@@ -320,20 +330,17 @@ impl<'cx, 'gcx, 'tcx> Canonicalizer<'cx, 'gcx, 'tcx> {
320330
variables: Slice::empty(),
321331
value: out_value,
322332
};
323-
let values = CanonicalVarValues {
324-
var_values: IndexVec::default(),
325-
};
326-
return (canon_value, values);
333+
return canon_value;
327334
}
328335

329336
let mut canonicalizer = Canonicalizer {
330337
infcx,
331338
tcx,
332339
canonicalize_region_mode,
333340
needs_canonical_flags,
334-
variables: IndexVec::default(),
341+
variables: SmallVec::new(),
342+
var_values,
335343
indices: FxHashMap::default(),
336-
var_values: IndexVec::default(),
337344
};
338345
let out_value = value.fold_with(&mut canonicalizer);
339346

@@ -348,16 +355,12 @@ impl<'cx, 'gcx, 'tcx> Canonicalizer<'cx, 'gcx, 'tcx> {
348355
)
349356
});
350357

351-
let canonical_variables = tcx.intern_canonical_var_infos(&canonicalizer.variables.raw);
358+
let canonical_variables = tcx.intern_canonical_var_infos(&canonicalizer.variables);
352359

353-
let canonical_value = Canonical {
360+
Canonical {
354361
variables: canonical_variables,
355362
value: out_value,
356-
};
357-
let canonical_var_values = CanonicalVarValues {
358-
var_values: canonicalizer.var_values,
359-
};
360-
(canonical_value, canonical_var_values)
363+
}
361364
}
362365

363366
/// Creates a canonical variable replacing `kind` from the input,
@@ -366,21 +369,54 @@ impl<'cx, 'gcx, 'tcx> Canonicalizer<'cx, 'gcx, 'tcx> {
366369
/// potentially a free region).
367370
fn canonical_var(&mut self, info: CanonicalVarInfo, kind: Kind<'tcx>) -> CanonicalVar {
368371
let Canonicalizer {
369-
indices,
370372
variables,
371373
var_values,
374+
indices,
372375
..
373376
} = self;
374377

375-
indices
376-
.entry(kind)
377-
.or_insert_with(|| {
378-
let cvar1 = variables.push(info);
379-
let cvar2 = var_values.push(kind);
380-
assert_eq!(cvar1, cvar2);
381-
cvar1
382-
})
383-
.clone()
378+
// This code is hot. `variables` and `var_values` are usually small
379+
// (fewer than 8 elements ~95% of the time). They are SmallVec's to
380+
// avoid allocations in those cases. We also don't use `indices` to
381+
// determine if a kind has been seen before until the limit of 8 has
382+
// been exceeded, to also avoid allocations for `indices`.
383+
if var_values.is_array() {
384+
// `var_values` is stack-allocated. `indices` isn't used yet. Do a
385+
// direct linear search of `var_values`.
386+
if let Some(idx) = var_values.iter().position(|&k| k == kind) {
387+
// `kind` is already present in `var_values`.
388+
CanonicalVar::new(idx)
389+
} else {
390+
// `kind` isn't present in `var_values`. Append it. Likewise
391+
// for `info` and `variables`.
392+
variables.push(info);
393+
var_values.push(kind);
394+
assert_eq!(variables.len(), var_values.len());
395+
396+
// If `var_values` has become big enough to be heap-allocated,
397+
// fill up `indices` to facilitate subsequent lookups.
398+
if !var_values.is_array() {
399+
assert!(indices.is_empty());
400+
*indices =
401+
var_values.iter()
402+
.enumerate()
403+
.map(|(i, &kind)| (kind, CanonicalVar::new(i)))
404+
.collect();
405+
}
406+
// The cv is the index of the appended element.
407+
CanonicalVar::new(var_values.len() - 1)
408+
}
409+
} else {
410+
// `var_values` is large. Do a hashmap search via `indices`.
411+
*indices
412+
.entry(kind)
413+
.or_insert_with(|| {
414+
variables.push(info);
415+
var_values.push(kind);
416+
assert_eq!(variables.len(), var_values.len());
417+
CanonicalVar::new(variables.len() - 1)
418+
})
419+
}
384420
}
385421

386422
/// Given a type variable `ty_var` of the given kind, first check

src/librustc/infer/canonical/mod.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
3434
use infer::{InferCtxt, RegionVariableOrigin, TypeVariableOrigin};
3535
use rustc_data_structures::indexed_vec::IndexVec;
36+
use rustc_data_structures::small_vec::SmallVec;
3637
use rustc_data_structures::sync::Lrc;
3738
use serialize::UseSpecializedDecodable;
3839
use std::ops::Index;
@@ -74,6 +75,10 @@ pub struct CanonicalVarValues<'tcx> {
7475
pub var_values: IndexVec<CanonicalVar, Kind<'tcx>>,
7576
}
7677

78+
/// Like CanonicalVarValues, but for use in places where a SmallVec is
79+
/// appropriate.
80+
pub type SmallCanonicalVarValues<'tcx> = SmallVec<[Kind<'tcx>; 8]>;
81+
7782
/// Information about a canonical variable that is included with the
7883
/// canonical value. This is sufficient information for code to create
7984
/// a copy of the canonical value in some other inference context,
@@ -281,10 +286,6 @@ BraceStructLiftImpl! {
281286
}
282287

283288
impl<'tcx> CanonicalVarValues<'tcx> {
284-
fn iter<'a>(&'a self) -> impl Iterator<Item = Kind<'tcx>> + 'a {
285-
self.var_values.iter().cloned()
286-
}
287-
288289
fn len(&self) -> usize {
289290
self.var_values.len()
290291
}

src/librustc/infer/canonical/query_result.rs

+15-14
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
2020
use infer::canonical::substitute::substitute_value;
2121
use infer::canonical::{Canonical, CanonicalVarKind, CanonicalVarValues, CanonicalizedQueryResult,
22-
Certainty, QueryRegionConstraint, QueryResult};
22+
Certainty, QueryRegionConstraint, QueryResult, SmallCanonicalVarValues};
2323
use infer::region_constraints::{Constraint, RegionConstraintData};
2424
use infer::InferCtxtBuilder;
2525
use infer::{InferCtxt, InferOk, InferResult, RegionObligation};
@@ -103,7 +103,7 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
103103
T: Debug + Lift<'gcx> + TypeFoldable<'tcx>,
104104
{
105105
let query_result = self.make_query_result(inference_vars, answer, fulfill_cx)?;
106-
let (canonical_result, _) = self.canonicalize_response(&query_result);
106+
let canonical_result = self.canonicalize_response(&query_result);
107107

108108
debug!(
109109
"make_canonicalized_query_result: canonical_result = {:#?}",
@@ -186,7 +186,7 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
186186
&self,
187187
cause: &ObligationCause<'tcx>,
188188
param_env: ty::ParamEnv<'tcx>,
189-
original_values: &CanonicalVarValues<'tcx>,
189+
original_values: &SmallCanonicalVarValues<'tcx>,
190190
query_result: &Canonical<'tcx, QueryResult<'tcx, R>>,
191191
) -> InferResult<'tcx, R>
192192
where
@@ -252,7 +252,7 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
252252
&self,
253253
cause: &ObligationCause<'tcx>,
254254
param_env: ty::ParamEnv<'tcx>,
255-
original_values: &CanonicalVarValues<'tcx>,
255+
original_values: &SmallCanonicalVarValues<'tcx>,
256256
query_result: &Canonical<'tcx, QueryResult<'tcx, R>>,
257257
output_query_region_constraints: &mut Vec<QueryRegionConstraint<'tcx>>,
258258
) -> InferResult<'tcx, R>
@@ -274,10 +274,11 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
274274
// variable...
275275
let mut obligations = vec![];
276276

277-
for (index, original_value) in original_values.var_values.iter_enumerated() {
277+
for (index, original_value) in original_values.iter().enumerate() {
278278
// ...with the value `v_r` of that variable from the query.
279279
let result_value = query_result
280-
.substitute_projected(self.tcx, &result_subst, |v| &v.var_values[index]);
280+
.substitute_projected(self.tcx, &result_subst,
281+
|v| &v.var_values[CanonicalVar::new(index)]);
281282
match (original_value.unpack(), result_value.unpack()) {
282283
(UnpackedKind::Lifetime(ty::ReErased), UnpackedKind::Lifetime(ty::ReErased)) => {
283284
// no action needed
@@ -341,7 +342,7 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
341342
&self,
342343
cause: &ObligationCause<'tcx>,
343344
param_env: ty::ParamEnv<'tcx>,
344-
original_values: &CanonicalVarValues<'tcx>,
345+
original_values: &SmallCanonicalVarValues<'tcx>,
345346
query_result: &Canonical<'tcx, QueryResult<'tcx, R>>,
346347
) -> InferResult<'tcx, CanonicalVarValues<'tcx>>
347348
where
@@ -382,7 +383,7 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
382383
fn query_result_substitution_guess<R>(
383384
&self,
384385
cause: &ObligationCause<'tcx>,
385-
original_values: &CanonicalVarValues<'tcx>,
386+
original_values: &SmallCanonicalVarValues<'tcx>,
386387
query_result: &Canonical<'tcx, QueryResult<'tcx, R>>,
387388
) -> CanonicalVarValues<'tcx>
388389
where
@@ -418,14 +419,14 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
418419
// e.g., here `result_value` might be `?0` in the example above...
419420
if let ty::TyInfer(ty::InferTy::CanonicalTy(index)) = result_value.sty {
420421
// in which case we would set `canonical_vars[0]` to `Some(?U)`.
421-
opt_values[index] = Some(original_value);
422+
opt_values[index] = Some(*original_value);
422423
}
423424
}
424425
UnpackedKind::Lifetime(result_value) => {
425426
// e.g., here `result_value` might be `'?1` in the example above...
426427
if let &ty::RegionKind::ReCanonical(index) = result_value {
427428
// in which case we would set `canonical_vars[0]` to `Some('static)`.
428-
opt_values[index] = Some(original_value);
429+
opt_values[index] = Some(*original_value);
429430
}
430431
}
431432
}
@@ -459,7 +460,7 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
459460
&self,
460461
cause: &ObligationCause<'tcx>,
461462
param_env: ty::ParamEnv<'tcx>,
462-
original_values: &CanonicalVarValues<'tcx>,
463+
original_values: &SmallCanonicalVarValues<'tcx>,
463464
result_subst: &CanonicalVarValues<'tcx>,
464465
query_result: &Canonical<'tcx, QueryResult<'tcx, R>>,
465466
) -> InferResult<'tcx, ()>
@@ -522,13 +523,13 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
522523
&self,
523524
cause: &ObligationCause<'tcx>,
524525
param_env: ty::ParamEnv<'tcx>,
525-
variables1: &CanonicalVarValues<'tcx>,
526+
variables1: &SmallCanonicalVarValues<'tcx>,
526527
variables2: impl Fn(CanonicalVar) -> Kind<'tcx>,
527528
) -> InferResult<'tcx, ()> {
528529
self.commit_if_ok(|_| {
529530
let mut obligations = vec![];
530-
for (index, value1) in variables1.var_values.iter_enumerated() {
531-
let value2 = variables2(index);
531+
for (index, value1) in variables1.iter().enumerate() {
532+
let value2 = variables2(CanonicalVar::new(index));
532533

533534
match (value1.unpack(), value2.unpack()) {
534535
(UnpackedKind::Type(v1), UnpackedKind::Type(v2)) => {

src/librustc/infer/canonical/substitute.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ impl<'tcx, V> Canonical<'tcx, V> {
4646
where
4747
T: TypeFoldable<'tcx>,
4848
{
49-
assert_eq!(self.variables.len(), var_values.var_values.len());
49+
assert_eq!(self.variables.len(), var_values.len());
5050
let value = projection_fn(&self.value);
5151
substitute_value(tcx, var_values, value)
5252
}

src/librustc/traits/query/dropck_outlives.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
use infer::at::At;
1212
use infer::InferOk;
13+
use rustc_data_structures::small_vec::SmallVec;
1314
use std::iter::FromIterator;
1415
use syntax::codemap::Span;
1516
use ty::subst::Kind;
@@ -50,7 +51,8 @@ impl<'cx, 'gcx, 'tcx> At<'cx, 'gcx, 'tcx> {
5051
}
5152

5253
let gcx = tcx.global_tcx();
53-
let (c_ty, orig_values) = self.infcx.canonicalize_query(&self.param_env.and(ty));
54+
let mut orig_values = SmallVec::new();
55+
let c_ty = self.infcx.canonicalize_query(&self.param_env.and(ty), &mut orig_values);
5456
let span = self.cause.span;
5557
debug!("c_ty = {:?}", c_ty);
5658
match &gcx.dropck_outlives(c_ty) {

src/librustc/traits/query/evaluate_obligation.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// except according to those terms.
1010

1111
use infer::InferCtxt;
12+
use rustc_data_structures::small_vec::SmallVec;
1213
use traits::{EvaluationResult, PredicateObligation, SelectionContext,
1314
TraitQueryMode, OverflowError};
1415

@@ -38,8 +39,9 @@ impl<'cx, 'gcx, 'tcx> InferCtxt<'cx, 'gcx, 'tcx> {
3839
&self,
3940
obligation: &PredicateObligation<'tcx>,
4041
) -> EvaluationResult {
41-
let (c_pred, _) =
42-
self.canonicalize_query(&obligation.param_env.and(obligation.predicate));
42+
let mut _orig_values = SmallVec::new();
43+
let c_pred = self.canonicalize_query(&obligation.param_env.and(obligation.predicate),
44+
&mut _orig_values);
4345
// Run canonical query. If overflow occurs, rerun from scratch but this time
4446
// in standard trait query mode so that overflow is handled appropriately
4547
// within `SelectionContext`.

0 commit comments

Comments
 (0)