Skip to content

Commit cc79420

Browse files
committed
Auto merge of #50106 - nnethercote:nearest_common_ancestor, r=nikomatsakis
Speed up `nearest_common_ancestor`. `nearest_common_ancestor` can be made faster. Here are all the benchmarks where one of the measurements improved by at least 1%. ``` clap-rs-check avg: -4.5% min: -8.8% max: -0.3% clap-rs avg: -2.6% min: -4.5% max: 0.5% script-servo avg: -1.7% min: -3.6% max: 0.0% regression-31157 avg: -1.5% min: -2.6% max: -0.4% hyper avg: -1.2% min: -2.5% max: -0.0% piston-image avg: -1.6% min: -2.5% max: 0.1% regex avg: -1.2% min: -2.2% max: 0.0% issue-46449 avg: -1.8% min: -2.1% max: -0.7% crates.io avg: -1.2% min: -2.1% max: 0.0% hyper-check avg: -1.0% min: -2.1% max: -0.1% clap-rs-opt avg: -1.4% min: -2.0% max: -0.3% piston-image-check avg: -1.2% min: -1.9% max: -0.1% regex-check avg: -0.5% min: -1.8% max: -0.1% syn avg: -1.1% min: -1.7% max: -0.1% tokio-webpush-simple-check avg: -1.1% min: -1.6% max: -0.3% tokio-webpush-simple avg: -1.2% min: -1.6% max: -0.0% helloworld-check avg: -1.4% min: -1.6% max: -1.2% deeply-nested avg: -1.2% min: -1.4% max: -0.8% encoding-check avg: -0.8% min: -1.3% max: -0.3% unify-linearly-check avg: -1.0% min: -1.3% max: -0.8% script-servo-check avg: -0.6% min: -1.3% max: 0.0% regression-31157-check avg: -0.9% min: -1.2% max: -0.7% script-servo-opt avg: -0.5% min: -1.2% max: 0.1% deeply-nested-check avg: -0.8% min: -1.2% max: -0.7% encoding avg: -0.7% min: -1.1% max: -0.3% issue-46449-check avg: -0.9% min: -1.1% max: -0.6% parser-check avg: -0.9% min: -1.1% max: -0.8% html5ever avg: -0.5% min: -1.0% max: -0.0% ```
2 parents 432fe0c + cccd51c commit cc79420

File tree

1 file changed

+60
-80
lines changed

1 file changed

+60
-80
lines changed

src/librustc/middle/region.rs

+60-80
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use ty;
2222

2323
use std::fmt;
2424
use std::mem;
25+
use rustc_data_structures::small_vec::SmallVec;
2526
use rustc_data_structures::sync::Lrc;
2627
use syntax::codemap;
2728
use syntax::ast;
@@ -677,96 +678,75 @@ impl<'tcx> ScopeTree {
677678
-> Scope {
678679
if scope_a == scope_b { return scope_a; }
679680

680-
// [1] The initial values for `a_buf` and `b_buf` are not used.
681-
// The `ancestors_of` function will return some prefix that
682-
// is re-initialized with new values (or else fallback to a
683-
// heap-allocated vector).
684-
let mut a_buf: [Scope; 32] = [scope_a /* [1] */; 32];
685-
let mut a_vec: Vec<Scope> = vec![];
686-
let mut b_buf: [Scope; 32] = [scope_b /* [1] */; 32];
687-
let mut b_vec: Vec<Scope> = vec![];
688-
let parent_map = &self.parent_map;
689-
let a_ancestors = ancestors_of(parent_map, scope_a, &mut a_buf, &mut a_vec);
690-
let b_ancestors = ancestors_of(parent_map, scope_b, &mut b_buf, &mut b_vec);
691-
let mut a_index = a_ancestors.len() - 1;
692-
let mut b_index = b_ancestors.len() - 1;
693-
694-
// Here, [ab]_ancestors is a vector going from narrow to broad.
695-
// The end of each vector will be the item where the scope is
696-
// defined; if there are any common ancestors, then the tails of
697-
// the vector will be the same. So basically we want to walk
698-
// backwards from the tail of each vector and find the first point
699-
// where they diverge. If one vector is a suffix of the other,
700-
// then the corresponding scope is a superscope of the other.
701-
702-
if a_ancestors[a_index] != b_ancestors[b_index] {
703-
// In this case, the two regions belong to completely
704-
// different functions. Compare those fn for lexical
705-
// nesting. The reasoning behind this is subtle. See the
706-
// "Modeling closures" section of the README in
707-
// infer::region_constraints for more details.
708-
let a_root_scope = a_ancestors[a_index];
709-
let b_root_scope = a_ancestors[a_index];
710-
return match (a_root_scope.data(), b_root_scope.data()) {
711-
(ScopeData::Destruction(a_root_id),
712-
ScopeData::Destruction(b_root_id)) => {
713-
if self.closure_is_enclosed_by(a_root_id, b_root_id) {
714-
// `a` is enclosed by `b`, hence `b` is the ancestor of everything in `a`
715-
scope_b
716-
} else if self.closure_is_enclosed_by(b_root_id, a_root_id) {
717-
// `b` is enclosed by `a`, hence `a` is the ancestor of everything in `b`
718-
scope_a
719-
} else {
720-
// neither fn encloses the other
721-
bug!()
722-
}
681+
// Process the lists in tandem from the innermost scope, recording the
682+
// scopes seen so far. The first scope that comes up for a second time
683+
// is the nearest common ancestor.
684+
//
685+
// Note: another way to compute the nearest common ancestor is to get
686+
// the full scope chain for both scopes and then compare the chains to
687+
// find the first scope in a common tail. But getting a parent scope
688+
// requires a hash table lookup, and we often have very long scope
689+
// chains (10s or 100s of scopes) that only differ by a few elements at
690+
// the start. So this algorithm is faster.
691+
let mut ma = Some(scope_a);
692+
let mut mb = Some(scope_b);
693+
let mut seen: SmallVec<[Scope; 32]> = SmallVec::new();
694+
loop {
695+
if let Some(a) = ma {
696+
if seen.iter().position(|s| *s == a).is_some() {
697+
return a;
723698
}
724-
_ => {
725-
// root ids are always Node right now
726-
bug!()
699+
seen.push(a);
700+
ma = self.parent_map.get(&a).map(|s| *s);
701+
}
702+
703+
if let Some(b) = mb {
704+
if seen.iter().position(|s| *s == b).is_some() {
705+
return b;
727706
}
728-
};
729-
}
707+
seen.push(b);
708+
mb = self.parent_map.get(&b).map(|s| *s);
709+
}
730710

731-
loop {
732-
// Loop invariant: a_ancestors[a_index] == b_ancestors[b_index]
733-
// for all indices between a_index and the end of the array
734-
if a_index == 0 { return scope_a; }
735-
if b_index == 0 { return scope_b; }
736-
a_index -= 1;
737-
b_index -= 1;
738-
if a_ancestors[a_index] != b_ancestors[b_index] {
739-
return a_ancestors[a_index + 1];
711+
if ma.is_none() && mb.is_none() {
712+
break;
740713
}
741-
}
714+
};
742715

743-
fn ancestors_of<'a, 'tcx>(parent_map: &FxHashMap<Scope, Scope>,
744-
scope: Scope,
745-
buf: &'a mut [Scope; 32],
746-
vec: &'a mut Vec<Scope>)
747-
-> &'a [Scope] {
748-
// debug!("ancestors_of(scope={:?})", scope);
716+
fn outermost_scope(parent_map: &FxHashMap<Scope, Scope>, scope: Scope) -> Scope {
749717
let mut scope = scope;
750-
751-
let mut i = 0;
752-
while i < 32 {
753-
buf[i] = scope;
754-
match parent_map.get(&scope) {
755-
Some(&superscope) => scope = superscope,
756-
_ => return &buf[..i+1]
757-
}
758-
i += 1;
718+
loop {
719+
match parent_map.get(&scope) {
720+
Some(&superscope) => scope = superscope,
721+
None => break scope,
722+
}
759723
}
724+
}
760725

761-
*vec = Vec::with_capacity(64);
762-
vec.extend_from_slice(buf);
763-
loop {
764-
vec.push(scope);
765-
match parent_map.get(&scope) {
766-
Some(&superscope) => scope = superscope,
767-
_ => return &*vec
726+
// In this (rare) case, the two regions belong to completely different
727+
// functions. Compare those fn for lexical nesting. The reasoning
728+
// behind this is subtle. See the "Modeling closures" section of the
729+
// README in infer::region_constraints for more details.
730+
let a_root_scope = outermost_scope(&self.parent_map, scope_a);
731+
let b_root_scope = outermost_scope(&self.parent_map, scope_b);
732+
match (a_root_scope.data(), b_root_scope.data()) {
733+
(ScopeData::Destruction(a_root_id),
734+
ScopeData::Destruction(b_root_id)) => {
735+
if self.closure_is_enclosed_by(a_root_id, b_root_id) {
736+
// `a` is enclosed by `b`, hence `b` is the ancestor of everything in `a`
737+
scope_b
738+
} else if self.closure_is_enclosed_by(b_root_id, a_root_id) {
739+
// `b` is enclosed by `a`, hence `a` is the ancestor of everything in `b`
740+
scope_a
741+
} else {
742+
// neither fn encloses the other
743+
bug!()
768744
}
769745
}
746+
_ => {
747+
// root ids are always Node right now
748+
bug!()
749+
}
770750
}
771751
}
772752

0 commit comments

Comments
 (0)