Skip to content

Commit 460dff8

Browse files
authored
Merge pull request #2177 from peterbell10/cpsched-2
Add simplified critical path scheduler to improve build times
2 parents 5f93e5a + 29fe3ef commit 460dff8

File tree

7 files changed

+326
-52
lines changed

7 files changed

+326
-52
lines changed

src/build.cc

+122-7
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ void Plan::Reset() {
8989
}
9090

9191
bool Plan::AddTarget(const Node* target, string* err) {
92+
targets_.push_back(target);
9293
return AddSubTarget(target, NULL, err, NULL);
9394
}
9495

@@ -128,8 +129,6 @@ bool Plan::AddSubTarget(const Node* node, const Node* dependent, string* err,
128129
if (node->dirty() && want == kWantNothing) {
129130
want = kWantToStart;
130131
EdgeWanted(edge);
131-
if (!dyndep_walk && edge->AllInputsReady())
132-
ScheduleWork(want_ins.first);
133132
}
134133

135134
if (dyndep_walk)
@@ -156,10 +155,10 @@ void Plan::EdgeWanted(const Edge* edge) {
156155
Edge* Plan::FindWork() {
157156
if (ready_.empty())
158157
return NULL;
159-
EdgeSet::iterator e = ready_.begin();
160-
Edge* edge = *e;
161-
ready_.erase(e);
162-
return edge;
158+
159+
Edge* work = ready_.top();
160+
ready_.pop();
161+
return work;
163162
}
164163

165164
void Plan::ScheduleWork(map<Edge*, Want>::iterator want_e) {
@@ -180,7 +179,7 @@ void Plan::ScheduleWork(map<Edge*, Want>::iterator want_e) {
180179
pool->RetrieveReadyEdges(&ready_);
181180
} else {
182181
pool->EdgeScheduled(*edge);
183-
ready_.insert(edge);
182+
ready_.push(edge);
184183
}
185184
}
186185

@@ -442,6 +441,121 @@ void Plan::UnmarkDependents(const Node* node, set<Node*>* dependents) {
442441
}
443442
}
444443

444+
namespace {
445+
446+
template <typename T>
447+
struct SeenBefore {
448+
std::set<const T*>* seen_;
449+
450+
SeenBefore(std::set<const T*>* seen) : seen_(seen) {}
451+
452+
bool operator() (const T* item) {
453+
// Return true if the item has been seen before
454+
return !seen_->insert(item).second;
455+
}
456+
};
457+
458+
// Heuristic for edge priority weighting.
459+
// Phony edges are free (0 cost), all other edges are weighted equally.
460+
int64_t EdgeWeightHeuristic(Edge *edge) {
461+
return edge->is_phony() ? 0 : 1;
462+
}
463+
464+
} // namespace
465+
466+
void Plan::ComputeCriticalPath() {
467+
METRIC_RECORD("ComputeCriticalPath");
468+
// Remove duplicate targets
469+
{
470+
std::set<const Node*> seen;
471+
SeenBefore<Node> seen_before(&seen);
472+
targets_.erase(std::remove_if(targets_.begin(), targets_.end(), seen_before),
473+
targets_.end());
474+
}
475+
476+
// Use backflow algorithm to compute the critical path for all
477+
// nodes, starting from the destination nodes.
478+
// XXX: ignores pools
479+
std::queue<Edge*> work_queue; // Queue, for breadth-first traversal
480+
// The set of edges currently in work_queue, to avoid duplicates.
481+
std::set<const Edge*> active_edges;
482+
SeenBefore<Edge> seen_edge(&active_edges);
483+
484+
for (size_t i = 0; i < targets_.size(); ++i) {
485+
const Node* target = targets_[i];
486+
if (Edge* in = target->in_edge()) {
487+
int64_t edge_weight = EdgeWeightHeuristic(in);
488+
in->set_critical_path_weight(
489+
std::max<int64_t>(edge_weight, in->critical_path_weight()));
490+
if (!seen_edge(in)) {
491+
work_queue.push(in);
492+
}
493+
}
494+
}
495+
496+
while (!work_queue.empty()) {
497+
Edge* e = work_queue.front();
498+
work_queue.pop();
499+
// If the critical path of any dependent edges is updated, this
500+
// edge may need to be processed again. So re-allow insertion.
501+
active_edges.erase(e);
502+
503+
for (std::vector<Node*>::iterator it = e->inputs_.begin(),
504+
end = e->inputs_.end();
505+
it != end; ++it) {
506+
Edge* in = (*it)->in_edge();
507+
if (!in) {
508+
continue;
509+
}
510+
// Only process edge if this node offers a higher weighted path
511+
const int64_t edge_weight = EdgeWeightHeuristic(in);
512+
const int64_t proposed_weight = e->critical_path_weight() + edge_weight;
513+
if (proposed_weight > in->critical_path_weight()) {
514+
in->set_critical_path_weight(proposed_weight);
515+
if (!seen_edge(in)) {
516+
work_queue.push(in);
517+
}
518+
}
519+
}
520+
}
521+
}
522+
523+
void Plan::ScheduleInitialEdges() {
524+
// Add ready edges to queue.
525+
assert(ready_.empty());
526+
std::set<Pool*> pools;
527+
528+
for (std::map<Edge*, Plan::Want>::iterator it = want_.begin(),
529+
end = want_.end(); it != end; ++it) {
530+
Edge* edge = it->first;
531+
Plan::Want want = it->second;
532+
if (!(want == kWantToStart && edge->AllInputsReady())) {
533+
continue;
534+
}
535+
536+
Pool* pool = edge->pool();
537+
if (pool->ShouldDelayEdge()) {
538+
pool->DelayEdge(edge);
539+
pools.insert(pool);
540+
} else {
541+
ScheduleWork(it);
542+
}
543+
}
544+
545+
// Call RetrieveReadyEdges only once at the end so higher priority
546+
// edges are retrieved first, not the ones that happen to be first
547+
// in the want_ map.
548+
for (std::set<Pool*>::iterator it=pools.begin(),
549+
end = pools.end(); it != end; ++it) {
550+
(*it)->RetrieveReadyEdges(&ready_);
551+
}
552+
}
553+
554+
void Plan::PrepareQueue() {
555+
ComputeCriticalPath();
556+
ScheduleInitialEdges();
557+
}
558+
445559
void Plan::Dump() const {
446560
printf("pending: %d\n", (int)want_.size());
447561
for (map<Edge*, Want>::const_iterator e = want_.begin(); e != want_.end(); ++e) {
@@ -611,6 +725,7 @@ bool Builder::AlreadyUpToDate() const {
611725

612726
bool Builder::Build(string* err) {
613727
assert(!AlreadyUpToDate());
728+
plan_.PrepareQueue();
614729

615730
status_->PlanHasTotalEdges(plan_.command_edge_count());
616731
int pending_commands = 0;

src/build.h

+24-14
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@
1818
#include <cstdio>
1919
#include <map>
2020
#include <memory>
21-
#include <queue>
2221
#include <string>
2322
#include <vector>
2423

2524
#include "depfile_parser.h"
26-
#include "graph.h" // XXX needed for DependencyScan; should rearrange.
25+
#include "graph.h"
2726
#include "exit_status.h"
2827
#include "util.h" // int64_t
2928

@@ -76,21 +75,13 @@ struct Plan {
7675
/// Reset state. Clears want and ready sets.
7776
void Reset();
7877

78+
// After all targets have been added, prepares the ready queue for find work.
79+
void PrepareQueue();
80+
7981
/// Update the build plan to account for modifications made to the graph
8082
/// by information loaded from a dyndep file.
8183
bool DyndepsLoaded(DependencyScan* scan, const Node* node,
8284
const DyndepFile& ddf, std::string* err);
83-
private:
84-
bool RefreshDyndepDependents(DependencyScan* scan, const Node* node, std::string* err);
85-
void UnmarkDependents(const Node* node, std::set<Node*>* dependents);
86-
bool AddSubTarget(const Node* node, const Node* dependent, std::string* err,
87-
std::set<Edge*>* dyndep_walk);
88-
89-
/// Update plan with knowledge that the given node is up to date.
90-
/// If the node is a dyndep binding on any of its dependents, this
91-
/// loads dynamic dependencies from the node's path.
92-
/// Returns 'false' if loading dyndep info fails and 'true' otherwise.
93-
bool NodeFinished(Node* node, std::string* err);
9485

9586
/// Enumerate possible steps we want for an edge.
9687
enum Want
@@ -105,6 +96,23 @@ struct Plan {
10596
kWantToFinish
10697
};
10798

99+
private:
100+
void ComputeCriticalPath();
101+
bool RefreshDyndepDependents(DependencyScan* scan, const Node* node, std::string* err);
102+
void UnmarkDependents(const Node* node, std::set<Node*>* dependents);
103+
bool AddSubTarget(const Node* node, const Node* dependent, std::string* err,
104+
std::set<Edge*>* dyndep_walk);
105+
106+
// Add edges that kWantToStart into the ready queue
107+
// Must be called after ComputeCriticalPath and before FindWork
108+
void ScheduleInitialEdges();
109+
110+
/// Update plan with knowledge that the given node is up to date.
111+
/// If the node is a dyndep binding on any of its dependents, this
112+
/// loads dynamic dependencies from the node's path.
113+
/// Returns 'false' if loading dyndep info fails and 'true' otherwise.
114+
bool NodeFinished(Node* node, std::string* err);
115+
108116
void EdgeWanted(const Edge* edge);
109117
bool EdgeMaybeReady(std::map<Edge*, Want>::iterator want_e, std::string* err);
110118

@@ -119,9 +127,11 @@ struct Plan {
119127
/// we want for the edge.
120128
std::map<Edge*, Want> want_;
121129

122-
EdgeSet ready_;
130+
EdgePriorityQueue ready_;
123131

124132
Builder* builder_;
133+
/// user provided targets in build order, earlier one have higher priority
134+
std::vector<const Node*> targets_;
125135

126136
/// Total number of edges that have commands (not phony).
127137
int command_edges_;

0 commit comments

Comments
 (0)