Skip to content

Commit 558c7d2

Browse files
committed
Auto merge of #5100 - alexcrichton:better-dep-ordering, r=matklad
Improve Cargo's scheduling of builds Historically Cargo has been pretty naive about scheduling builds, basically just greedily scheduling as much work as possible. As pointed out in #5014, however, this isn't guaranteed to always have the best results. If we've got a very deep dependency tree that would otherwise fill up our CPUs Cargo should ideally schedule these dependencies first. That way when we reach higher up in the dependency tree we should have more work available to fill in the cracks if there's spare cpus. Closes #5014
2 parents 382967a + e54b5f8 commit 558c7d2

File tree

2 files changed

+85
-3
lines changed

2 files changed

+85
-3
lines changed

src/cargo/ops/cargo_rustc/job_queue.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ impl<'a> JobQueue<'a> {
111111
/// possible along each dependency chain.
112112
pub fn execute(&mut self, cx: &mut Context) -> CargoResult<()> {
113113
let _p = profile::start("executing the job graph");
114+
self.queue.queue_finished();
114115

115116
// We need to give a handle to the send half of our message queue to the
116117
// jobserver helper thread. Unfortunately though we need the handle to be

src/cargo/util/dependency_queue.rs

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ pub struct DependencyQueue<K: Eq + Hash, V> {
3434
/// The packages which are currently being built, waiting for a call to
3535
/// `finish`.
3636
pending: HashSet<K>,
37+
38+
/// Topological depth of each key
39+
depth: HashMap<K, usize>,
3740
}
3841

3942
/// Indication of the freshness of a package.
@@ -66,6 +69,7 @@ impl<K: Hash + Eq + Clone, V> DependencyQueue<K, V> {
6669
reverse_dep_map: HashMap::new(),
6770
dirty: HashSet::new(),
6871
pending: HashSet::new(),
72+
depth: HashMap::new(),
6973
}
7074
}
7175

@@ -97,14 +101,60 @@ impl<K: Hash + Eq + Clone, V> DependencyQueue<K, V> {
97101
&mut slot.insert((my_dependencies, value)).1
98102
}
99103

104+
/// All nodes have been added, calculate some internal metadata and prepare
105+
/// for `dequeue`.
106+
pub fn queue_finished(&mut self) {
107+
for key in self.dep_map.keys() {
108+
depth(key, &self.reverse_dep_map, &mut self.depth);
109+
}
110+
111+
fn depth<K: Hash + Eq + Clone>(
112+
key: &K,
113+
map: &HashMap<K, HashSet<K>>,
114+
results: &mut HashMap<K, usize>,
115+
) -> usize {
116+
const IN_PROGRESS: usize = !0;
117+
118+
if let Some(&depth) = results.get(key) {
119+
assert_ne!(depth, IN_PROGRESS, "cycle in DependencyQueue");
120+
return depth;
121+
}
122+
123+
results.insert(key.clone(), IN_PROGRESS);
124+
125+
let depth = 1 + map.get(&key)
126+
.into_iter()
127+
.flat_map(|it| it)
128+
.map(|dep| depth(dep, map, results))
129+
.max()
130+
.unwrap_or(0);
131+
132+
*results.get_mut(key).unwrap() = depth;
133+
134+
depth
135+
}
136+
}
137+
100138
/// Dequeues a package that is ready to be built.
101139
///
102140
/// A package is ready to be built when it has 0 un-built dependencies. If
103141
/// `None` is returned then no packages are ready to be built.
104142
pub fn dequeue(&mut self) -> Option<(Freshness, K, V)> {
105-
let key = match self.dep_map.iter()
106-
.find(|&(_, &(ref deps, _))| deps.is_empty())
107-
.map(|(key, _)| key.clone()) {
143+
// Look at all our crates and find everything that's ready to build (no
144+
// deps). After we've got that candidate set select the one which has
145+
// the maximum depth in the dependency graph. This way we should
146+
// hopefully keep CPUs hottest the longest by ensuring that long
147+
// dependency chains are scheduled early on in the build process and the
148+
// leafs higher in the tree can fill in the cracks later.
149+
//
150+
// TODO: it'd be best here to throw in a heuristic of crate size as
151+
// well. For example how long did this crate historically take to
152+
// compile? How large is its source code? etc.
153+
let next = self.dep_map.iter()
154+
.filter(|&(_, &(ref deps, _))| deps.is_empty())
155+
.map(|(key, _)| key.clone())
156+
.max_by_key(|k| self.depth[k]);
157+
let key = match next {
108158
Some(key) => key,
109159
None => return None
110160
};
@@ -142,3 +192,34 @@ impl<K: Hash + Eq + Clone, V> DependencyQueue<K, V> {
142192
}
143193
}
144194
}
195+
196+
#[cfg(test)]
197+
mod test {
198+
use super::{DependencyQueue, Freshness};
199+
200+
#[test]
201+
fn deep_first() {
202+
let mut q = DependencyQueue::new();
203+
204+
q.queue(Freshness::Fresh, 1, (), &[]);
205+
q.queue(Freshness::Fresh, 2, (), &[1]);
206+
q.queue(Freshness::Fresh, 3, (), &[]);
207+
q.queue(Freshness::Fresh, 4, (), &[2, 3]);
208+
q.queue(Freshness::Fresh, 5, (), &[4, 3]);
209+
q.queue_finished();
210+
211+
assert_eq!(q.dequeue(), Some((Freshness::Fresh, 1, ())));
212+
assert_eq!(q.dequeue(), Some((Freshness::Fresh, 3, ())));
213+
assert_eq!(q.dequeue(), None);
214+
q.finish(&3, Freshness::Fresh);
215+
assert_eq!(q.dequeue(), None);
216+
q.finish(&1, Freshness::Fresh);
217+
assert_eq!(q.dequeue(), Some((Freshness::Fresh, 2, ())));
218+
assert_eq!(q.dequeue(), None);
219+
q.finish(&2, Freshness::Fresh);
220+
assert_eq!(q.dequeue(), Some((Freshness::Fresh, 4, ())));
221+
assert_eq!(q.dequeue(), None);
222+
q.finish(&4, Freshness::Fresh);
223+
assert_eq!(q.dequeue(), Some((Freshness::Fresh, 5, ())));
224+
}
225+
}

0 commit comments

Comments
 (0)