Skip to content

Commit 0253f4e

Browse files
committed
Auto merge of #6097 - Eh2406:refactoring, r=alexcrichton
small refactor This moves the visual indicator code out of the most complicated loop in the resolver. The `activate_deps_loop` is the heart of the resolver, with all its moving pieces in play at the same time. It is completely overwhelming. This is a true refactoring, nothing about the executable has changed, but a screen full of code/comments got moved to a helper type in a different file. I'd love to see more moved out of `activate_deps_loop` but this is the thing that came most cleanly. edit: I was also able to move the `pop_most_constrained` code out as well. So that is now also in this PR.
2 parents e6c3a17 + cca4d45 commit 0253f4e

File tree

2 files changed

+130
-78
lines changed

2 files changed

+130
-78
lines changed

src/cargo/core/resolver/mod.rs

Lines changed: 14 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
//! that we're implementing something that probably shouldn't be allocating all
4848
//! over the place.
4949
50-
use std::collections::{BTreeMap, BinaryHeap, HashMap, HashSet};
50+
use std::collections::{BTreeMap, HashMap, HashSet};
5151
use std::mem;
5252
use std::rc::Rc;
5353
use std::time::{Duration, Instant};
@@ -64,7 +64,7 @@ use util::profile;
6464

6565
use self::context::{Activations, Context};
6666
use self::types::{ActivateError, ActivateResult, Candidate, ConflictReason, DepsFrame, GraphNode};
67-
use self::types::{RcVecIter, RegistryQueryer};
67+
use self::types::{RcVecIter, RegistryQueryer, RemainingDeps, ResolverProgress};
6868

6969
pub use self::encode::{EncodableDependency, EncodablePackageId, EncodableResolve};
7070
pub use self::encode::{Metadata, WorkspaceResolve};
@@ -170,15 +170,8 @@ fn activate_deps_loop(
170170
summaries: &[(Summary, Method)],
171171
config: Option<&Config>,
172172
) -> CargoResult<Context> {
173-
// Note that a `BinaryHeap` is used for the remaining dependencies that need
174-
// activation. This heap is sorted such that the "largest value" is the most
175-
// constrained dependency, or the one with the least candidates.
176-
//
177-
// This helps us get through super constrained portions of the dependency
178-
// graph quickly and hopefully lock down what later larger dependencies can
179-
// use (those with more candidates).
180173
let mut backtrack_stack = Vec::new();
181-
let mut remaining_deps = BinaryHeap::new();
174+
let mut remaining_deps = RemainingDeps::new();
182175

183176
// `past_conflicting_activations` is a cache of the reasons for each time we
184177
// backtrack.
@@ -200,11 +193,7 @@ fn activate_deps_loop(
200193
}
201194
}
202195

203-
let mut ticks = 0u16;
204-
let start = Instant::now();
205-
let time_to_print = Duration::from_millis(500);
206-
let mut printed = false;
207-
let mut deps_time = Duration::new(0, 0);
196+
let mut printed = ResolverProgress::new();
208197

209198
// Main resolution loop, this is the workhorse of the resolution algorithm.
210199
//
@@ -219,55 +208,14 @@ fn activate_deps_loop(
219208
// its own dependencies in turn. The `backtrack_stack` is a side table of
220209
// backtracking states where if we hit an error we can return to in order to
221210
// attempt to continue resolving.
222-
while let Some(mut deps_frame) = remaining_deps.pop() {
223-
// If we spend a lot of time here (we shouldn't in most cases) then give
224-
// a bit of a visual indicator as to what we're doing. Only enable this
225-
// when stderr is a tty (a human is likely to be watching) to ensure we
226-
// get deterministic output otherwise when observed by tools.
227-
//
228-
// Also note that we hit this loop a lot, so it's fairly performance
229-
// sensitive. As a result try to defer a possibly expensive operation
230-
// like `Instant::now` by only checking every N iterations of this loop
231-
// to amortize the cost of the current time lookup.
232-
ticks += 1;
233-
if let Some(config) = config {
234-
if config.shell().is_err_tty()
235-
&& !printed
236-
&& ticks % 1000 == 0
237-
&& start.elapsed() - deps_time > time_to_print
238-
{
239-
printed = true;
240-
config.shell().status("Resolving", "dependency graph...")?;
241-
}
242-
}
243-
// The largest test in our sweet takes less then 5000 ticks
244-
// with all the algorithm improvements.
245-
// If any of them are removed then it takes more than I am willing to measure.
246-
// So lets fail the test fast if we have ben running for two long.
247-
debug_assert!(ticks < 50_000);
248-
// The largest test in our sweet takes less then 30 sec
249-
// with all the improvements to how fast a tick can go.
250-
// If any of them are removed then it takes more than I am willing to measure.
251-
// So lets fail the test fast if we have ben running for two long.
252-
if cfg!(debug_assertions) && (ticks % 1000 == 0) {
253-
assert!(start.elapsed() - deps_time < Duration::from_secs(90));
254-
}
255-
256-
let just_here_for_the_error_messages = deps_frame.just_for_error_messages;
257-
258-
// Figure out what our next dependency to activate is, and if nothing is
259-
// listed then we're entirely done with this frame (yay!) and we can
260-
// move on to the next frame.
261-
let frame = match deps_frame.remaining_siblings.next() {
262-
Some(sibling) => {
263-
let parent = Summary::clone(&deps_frame.parent);
264-
remaining_deps.push(deps_frame);
265-
(parent, sibling)
266-
}
267-
None => continue,
268-
};
211+
while let Some((just_here_for_the_error_messages, frame)) =
212+
remaining_deps.pop_most_constrained()
213+
{
269214
let (mut parent, (mut cur, (mut dep, candidates, mut features))) = frame;
270-
assert!(!remaining_deps.is_empty());
215+
216+
// If we spend a lot of time here (we shouldn't in most cases) then give
217+
// a bit of a visual indicator as to what we're doing.
218+
printed.shell_status(config)?;
271219

272220
trace!(
273221
"{}[{}]>{} {} candidates",
@@ -398,7 +346,7 @@ fn activate_deps_loop(
398346
Some(BacktrackFrame {
399347
cur,
400348
context_backup: Context::clone(&cx),
401-
deps_backup: <BinaryHeap<DepsFrame>>::clone(&remaining_deps),
349+
deps_backup: remaining_deps.clone(),
402350
remaining_candidates: remaining_candidates.clone(),
403351
parent: Summary::clone(&parent),
404352
dep: Dependency::clone(&dep),
@@ -431,7 +379,7 @@ fn activate_deps_loop(
431379
// frame in the end if it looks like it's not going to end well,
432380
// so figure that out here.
433381
Ok(Some((mut frame, dur))) => {
434-
deps_time += dur;
382+
printed.elapsed(dur);
435383

436384
// Our `frame` here is a new package with its own list of
437385
// dependencies. Do a sanity check here of all those
@@ -486,7 +434,6 @@ fn activate_deps_loop(
486434
{
487435
if let Some((other_parent, conflict)) = remaining_deps
488436
.iter()
489-
.flat_map(|other| other.flatten())
490437
// for deps related to us
491438
.filter(|&(_, ref other_dep)| {
492439
known_related_bad_deps.contains(other_dep)
@@ -683,7 +630,7 @@ fn activate(
683630
struct BacktrackFrame {
684631
cur: usize,
685632
context_backup: Context,
686-
deps_backup: BinaryHeap<DepsFrame>,
633+
deps_backup: RemainingDeps,
687634
remaining_candidates: RemainingCandidates,
688635
parent: Summary,
689636
dep: Dependency,

src/cargo/core/resolver/types.rs

Lines changed: 116 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,70 @@
11
use std::cmp::Ordering;
2-
use std::collections::{HashMap, HashSet};
2+
use std::collections::{BinaryHeap, HashMap, HashSet};
33
use std::ops::Range;
44
use std::rc::Rc;
5+
use std::time::{Duration, Instant};
56

67
use core::interning::InternedString;
78
use core::{Dependency, PackageId, PackageIdSpec, Registry, Summary};
8-
use util::{CargoError, CargoResult};
9+
use util::{CargoError, CargoResult, Config};
10+
11+
pub struct ResolverProgress {
12+
ticks: u16,
13+
start: Instant,
14+
time_to_print: Duration,
15+
printed: bool,
16+
deps_time: Duration,
17+
}
18+
19+
impl ResolverProgress {
20+
pub fn new() -> ResolverProgress {
21+
ResolverProgress {
22+
ticks: 0,
23+
start: Instant::now(),
24+
time_to_print: Duration::from_millis(500),
25+
printed: false,
26+
deps_time: Duration::new(0, 0),
27+
}
28+
}
29+
pub fn shell_status(&mut self, config: Option<&Config>) -> CargoResult<()> {
30+
// If we spend a lot of time here (we shouldn't in most cases) then give
31+
// a bit of a visual indicator as to what we're doing. Only enable this
32+
// when stderr is a tty (a human is likely to be watching) to ensure we
33+
// get deterministic output otherwise when observed by tools.
34+
//
35+
// Also note that we hit this loop a lot, so it's fairly performance
36+
// sensitive. As a result try to defer a possibly expensive operation
37+
// like `Instant::now` by only checking every N iterations of this loop
38+
// to amortize the cost of the current time lookup.
39+
self.ticks += 1;
40+
if let Some(config) = config {
41+
if config.shell().is_err_tty()
42+
&& !self.printed
43+
&& self.ticks % 1000 == 0
44+
&& self.start.elapsed() - self.deps_time > self.time_to_print
45+
{
46+
self.printed = true;
47+
config.shell().status("Resolving", "dependency graph...")?;
48+
}
49+
}
50+
// The largest test in our sweet takes less then 5000 ticks
51+
// with all the algorithm improvements.
52+
// If any of them are removed then it takes more than I am willing to measure.
53+
// So lets fail the test fast if we have ben running for two long.
54+
debug_assert!(self.ticks < 50_000);
55+
// The largest test in our sweet takes less then 30 sec
56+
// with all the improvements to how fast a tick can go.
57+
// If any of them are removed then it takes more than I am willing to measure.
58+
// So lets fail the test fast if we have ben running for two long.
59+
if cfg!(debug_assertions) && (self.ticks % 1000 == 0) {
60+
assert!(self.start.elapsed() - self.deps_time < Duration::from_secs(90));
61+
}
62+
Ok(())
63+
}
64+
pub fn elapsed(&mut self, dur: Duration) {
65+
self.deps_time += dur;
66+
}
67+
}
968

1069
pub struct RegistryQueryer<'a> {
1170
pub registry: &'a mut (Registry + 'a),
@@ -46,24 +105,33 @@ impl<'a> RegistryQueryer<'a> {
46105
}
47106

48107
let mut ret = Vec::new();
49-
self.registry.query(dep, &mut |s| {
50-
ret.push(Candidate {
51-
summary: s,
52-
replace: None,
53-
});
54-
}, false)?;
108+
self.registry.query(
109+
dep,
110+
&mut |s| {
111+
ret.push(Candidate {
112+
summary: s,
113+
replace: None,
114+
});
115+
},
116+
false,
117+
)?;
55118
for candidate in ret.iter_mut() {
56119
let summary = &candidate.summary;
57120

58-
let mut potential_matches = self.replacements
121+
let mut potential_matches = self
122+
.replacements
59123
.iter()
60124
.filter(|&&(ref spec, _)| spec.matches(summary.package_id()));
61125

62126
let &(ref spec, ref dep) = match potential_matches.next() {
63127
None => continue,
64128
Some(replacement) => replacement,
65129
};
66-
debug!("found an override for {} {}", dep.package_name(), dep.version_req());
130+
debug!(
131+
"found an override for {} {}",
132+
dep.package_name(),
133+
dep.version_req()
134+
);
67135

68136
let mut summaries = self.registry.query_vec(dep, false)?.into_iter();
69137
let s = summaries.next().ok_or_else(|| {
@@ -204,7 +272,7 @@ impl DepsFrame {
204272
.unwrap_or(0)
205273
}
206274

207-
pub fn flatten<'s>(&'s self) -> impl Iterator<Item = (&PackageId, Dependency)> + 's {
275+
pub fn flatten(&self) -> impl Iterator<Item = (&PackageId, Dependency)> {
208276
self.remaining_siblings
209277
.clone()
210278
.map(move |(_, (d, _, _))| (self.parent.package_id(), d))
@@ -238,6 +306,43 @@ impl Ord for DepsFrame {
238306
}
239307
}
240308

309+
/// Note that a `BinaryHeap` is used for the remaining dependencies that need
310+
/// activation. This heap is sorted such that the "largest value" is the most
311+
/// constrained dependency, or the one with the least candidates.
312+
///
313+
/// This helps us get through super constrained portions of the dependency
314+
/// graph quickly and hopefully lock down what later larger dependencies can
315+
/// use (those with more candidates).
316+
#[derive(Clone)]
317+
pub struct RemainingDeps(BinaryHeap<DepsFrame>);
318+
319+
impl RemainingDeps {
320+
pub fn new() -> RemainingDeps {
321+
RemainingDeps(BinaryHeap::new())
322+
}
323+
pub fn push(&mut self, x: DepsFrame) {
324+
self.0.push(x)
325+
}
326+
pub fn pop_most_constrained(&mut self) -> Option<(bool, (Summary, (usize, DepInfo)))> {
327+
while let Some(mut deps_frame) = self.0.pop() {
328+
let just_here_for_the_error_messages = deps_frame.just_for_error_messages;
329+
330+
// Figure out what our next dependency to activate is, and if nothing is
331+
// listed then we're entirely done with this frame (yay!) and we can
332+
// move on to the next frame.
333+
if let Some(sibling) = deps_frame.remaining_siblings.next() {
334+
let parent = Summary::clone(&deps_frame.parent);
335+
self.0.push(deps_frame);
336+
return Some((just_here_for_the_error_messages, (parent, sibling)));
337+
}
338+
}
339+
None
340+
}
341+
pub fn iter(&mut self) -> impl Iterator<Item = (&PackageId, Dependency)> {
342+
self.0.iter().flat_map(|other| other.flatten())
343+
}
344+
}
345+
241346
// Information about the dependencies for a crate, a tuple of:
242347
//
243348
// (dependency info, candidates, features activated)

0 commit comments

Comments
 (0)