Skip to content

Commit ba68213

Browse files
digit-googlejhasse
authored andcommitted
ComputeCriticalPath: Use topological sort to speed up function.
Use a topological sort to get a sorted list of edges to perform the critical path weigth propagation as fast as possible. The previous version used a data flow algorithm that forced the values to be recomputed several times for far too many edges on complex build plans. For example, for a Fuchsia build plan with 93339 edges, this reduces the ComputeCriticalPath metric from 1.9s to 80ms! The unit-tests still pass, and manual testing shows that the order of commands does not change before and after this change for the example build plan above.
1 parent 9aa43a0 commit ba68213

File tree

1 file changed

+75
-37
lines changed

1 file changed

+75
-37
lines changed

src/build.cc

Lines changed: 75 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -463,49 +463,87 @@ int64_t EdgeWeightHeuristic(Edge *edge) {
463463

464464
void Plan::ComputeCriticalPath() {
465465
METRIC_RECORD("ComputeCriticalPath");
466-
// Remove duplicate targets
467-
std::unordered_set<const Node*> unique_targets(targets_.begin(),
468-
targets_.end());
469-
470-
// Use backflow algorithm to compute the critical path for all
471-
// nodes, starting from the destination nodes.
472-
// XXX: ignores pools
473-
std::queue<Edge*> work_queue; // Queue, for breadth-first traversal
474-
// The set of edges currently in work_queue, to avoid duplicates.
475-
std::unordered_set<const Edge*> active_edges;
476-
477-
for (const Node* target : unique_targets) {
478-
if (Edge* in = target->in_edge()) {
479-
int64_t edge_weight = EdgeWeightHeuristic(in);
480-
in->set_critical_path_weight(
481-
std::max<int64_t>(edge_weight, in->critical_path_weight()));
482-
if (active_edges.insert(in).second) {
483-
work_queue.push(in);
466+
467+
// Convenience class to perform a topological sort of all edges
468+
// reachable from a set of unique targets. Usage is:
469+
//
470+
// 1) Create instance.
471+
//
472+
// 2) Call VisitTarget() as many times as necessary.
473+
// Note that duplicate targets are properly ignored.
474+
//
475+
// 3) Call result() to get a sorted list of edges,
476+
// where each edge appears _after_ its parents,
477+
// i.e. the edges producing its inputs, in the list.
478+
//
479+
struct TopoSort {
480+
void VisitTarget(const Node* target) {
481+
Edge* producer = target->in_edge();
482+
if (producer)
483+
Visit(producer);
484+
}
485+
486+
const std::vector<Edge*>& result() const { return sorted_edges_; }
487+
488+
private:
489+
// Implementation note:
490+
//
491+
// This is the regular depth-first-search algorithm described
492+
// at https://en.wikipedia.org/wiki/Topological_sorting, except
493+
// that:
494+
//
495+
// - Edges are appended to the end of the list, for performance
496+
// reasons. Hence the order used in result().
497+
//
498+
// - Since the graph cannot have any cycles, temporary marks
499+
// are not necessary, and a simple set is used to record
500+
// which edges have already been visited.
501+
//
502+
void Visit(Edge* edge) {
503+
auto insertion = visited_set_.emplace(edge);
504+
if (!insertion.second)
505+
return;
506+
507+
for (const Node* input : edge->inputs_) {
508+
Edge* producer = input->in_edge();
509+
if (producer)
510+
Visit(producer);
484511
}
512+
sorted_edges_.push_back(edge);
485513
}
514+
515+
std::unordered_set<Edge*> visited_set_;
516+
std::vector<Edge*> sorted_edges_;
517+
};
518+
519+
TopoSort topo_sort;
520+
for (const Node* target : targets_) {
521+
topo_sort.VisitTarget(target);
486522
}
487523

488-
while (!work_queue.empty()) {
489-
Edge* e = work_queue.front();
490-
work_queue.pop();
491-
// If the critical path of any dependent edges is updated, this
492-
// edge may need to be processed again. So re-allow insertion.
493-
active_edges.erase(e);
524+
const auto& sorted_edges = topo_sort.result();
525+
526+
// First, reset all weights to 1.
527+
for (Edge* edge : sorted_edges)
528+
edge->set_critical_path_weight(EdgeWeightHeuristic(edge));
494529

495-
for (const Node* input : e->inputs_) {
496-
Edge* in = input->in_edge();
497-
if (!in) {
530+
// Second propagate / increment weidghts from
531+
// children to parents. Scan the list
532+
// in reverse order to do so.
533+
for (auto reverse_it = sorted_edges.rbegin();
534+
reverse_it != sorted_edges.rend(); ++reverse_it) {
535+
Edge* edge = *reverse_it;
536+
int64_t edge_weight = edge->critical_path_weight();
537+
538+
for (const Node* input : edge->inputs_) {
539+
Edge* producer = input->in_edge();
540+
if (!producer)
498541
continue;
499-
}
500-
// Only process edge if this node offers a higher weighted path
501-
const int64_t edge_weight = EdgeWeightHeuristic(in);
502-
const int64_t proposed_weight = e->critical_path_weight() + edge_weight;
503-
if (proposed_weight > in->critical_path_weight()) {
504-
in->set_critical_path_weight(proposed_weight);
505-
if (active_edges.insert(in).second) {
506-
work_queue.push(in);
507-
}
508-
}
542+
543+
int64_t producer_weight = producer->critical_path_weight();
544+
int64_t candidate_weight = edge_weight + EdgeWeightHeuristic(producer);
545+
if (candidate_weight > producer_weight)
546+
producer->set_critical_path_weight(candidate_weight);
509547
}
510548
}
511549
}

0 commit comments

Comments
 (0)