diff --git a/src/build.cc b/src/build.cc index 6f11ed7a3c..9c0dbc2d7e 100644 --- a/src/build.cc +++ b/src/build.cc @@ -89,6 +89,7 @@ void Plan::Reset() { } bool Plan::AddTarget(const Node* target, string* err) { + targets_.push_back(target); return AddSubTarget(target, NULL, err, NULL); } @@ -123,8 +124,6 @@ bool Plan::AddSubTarget(const Node* node, const Node* dependent, string* err, if (node->dirty() && want == kWantNothing) { want = kWantToStart; EdgeWanted(edge); - if (!dyndep_walk && edge->AllInputsReady()) - ScheduleWork(want_ins.first); } if (dyndep_walk) @@ -151,10 +150,10 @@ void Plan::EdgeWanted(const Edge* edge) { Edge* Plan::FindWork() { if (ready_.empty()) return NULL; - EdgeSet::iterator e = ready_.begin(); - Edge* edge = *e; - ready_.erase(e); - return edge; + + Edge* work = ready_.top(); + ready_.pop(); + return work; } void Plan::ScheduleWork(map::iterator want_e) { @@ -175,7 +174,7 @@ void Plan::ScheduleWork(map::iterator want_e) { pool->RetrieveReadyEdges(&ready_); } else { pool->EdgeScheduled(*edge); - ready_.insert(edge); + ready_.push(edge); } } @@ -437,6 +436,204 @@ void Plan::UnmarkDependents(const Node* node, set* dependents) { } } +namespace { + +template +struct SeenBefore { + std::set* seen_; + + SeenBefore(std::set* seen) : seen_(seen) {} + + bool operator() (const T* item) { + // Return true if the item has been seen before + return !seen_->insert(item).second; + } +}; + +// Assign run_time_ms for all wanted edges, and returns total time for all edges +// For phony edges, 0 cost. +// For edges with a build history, use the last build time. +// For edges without history, use the 75th percentile time for edges with history. +// Or, if there is no history at all just use 1 +int64_t AssignEdgeRuntime(BuildLog* build_log, + const std::map& want) { + bool missing_durations = false; + std::vector durations; + int64_t total_time = 0; + const int64_t kUnknownRunTime = -1; // marker value for the two loops below. + + for (std::map::const_iterator it = want.begin(), + end = want.end(); + it != end; ++it) { + Edge* edge = it->first; + if (edge->is_phony()) { + continue; + } + BuildLog::LogEntry* entry = + build_log->LookupByOutput(edge->outputs_[0]->path()); + if (!entry) { + missing_durations = true; + edge->set_run_time_ms(kUnknownRunTime); // mark as needing filled in + continue; + } + const int64_t duration = entry->end_time - entry->start_time; + edge->set_run_time_ms(duration); + total_time += duration; + durations.push_back(duration); + } + + if (!missing_durations) { + return total_time; + } + + // Heuristic: for unknown edges, take the 75th percentile time. + // This allows the known-slowest jobs to run first, but isn't so + // small that it is always the lowest priority. Which for slow jobs, + // might bottleneck the build. + int64_t p75_time = 1; + int64_t num_durations = static_cast(durations.size()); + if (num_durations > 0) { + size_t p75_idx = (num_durations - 1) - num_durations / 4; + std::vector::iterator p75_it = durations.begin() + p75_idx; + std::nth_element(durations.begin(), p75_it, durations.end()); + p75_time = *p75_it; + } + + for (std::map::const_iterator it = want.begin(), + end = want.end(); + it != end; ++it) { + Edge* edge = it->first; + if (edge->run_time_ms() != kUnknownRunTime) { + continue; + } + edge->set_run_time_ms(p75_time); + total_time += p75_time; + } + return total_time; +} + +int64_t AssignDefaultEdgeRuntime(std::map &want) { + int64_t total_time = 0; + + for (std::map::const_iterator it = want.begin(), + end = want.end(); + it != end; ++it) { + Edge* edge = it->first; + if (edge->is_phony()) { + continue; + } + + edge->set_run_time_ms(1); + ++total_time; + } + return total_time; +} + +} // namespace + +void Plan::ComputeCriticalTime(BuildLog* build_log) { + METRIC_RECORD("ComputeCriticalTime"); + // Remove duplicate targets + { + std::set seen; + SeenBefore seen_before(&seen); + targets_.erase(std::remove_if(targets_.begin(), targets_.end(), seen_before), + targets_.end()); + } + + // total time if building all edges in serial. This value is big + // enough to ensure higher priority target's initial critical time + // is always bigger than lower ones + const int64_t total_time = build_log ? + AssignEdgeRuntime(build_log, want_) : + AssignDefaultEdgeRuntime(want_); // Plan tests have no build_log + + + // Use backflow algorithm to compute critical times for all nodes, starting + // from the destination nodes. + // XXX: ignores pools + std::queue work_queue; // Queue, for breadth-first traversal + // The set of edges currently in work_queue, to avoid duplicates. + std::set active_edges; + SeenBefore seen_edge(&active_edges); + + for (size_t i = 0; i < targets_.size(); ++i) { + const Node* target = targets_[i]; + if (Edge* in = target->in_edge()) { + // Add a bias to ensure that targets that appear first in |targets_| have a larger critical time than + // those that follow them. E.g. for 3 targets: [2*total_time, total_time, 0]. + int64_t priority_weight = (targets_.size() - i - 1) * total_time; + in->set_critical_time_ms( + priority_weight + + std::max(in->run_time_ms(), in->critical_time_ms())); + if (!seen_edge(in)) { + work_queue.push(in); + } + } + } + + while (!work_queue.empty()) { + Edge* e = work_queue.front(); + work_queue.pop(); + // If the critical time of any dependent edges is updated, this + // edge may need to be processed again. So re-allow insertion. + active_edges.erase(e); + + for (std::vector::iterator it = e->inputs_.begin(), + end = e->inputs_.end(); + it != end; ++it) { + Edge* in = (*it)->in_edge(); + if (!in) { + continue; + } + // Only process edge if this node offers a higher critical time + const int64_t proposed_time = e->critical_time_ms() + in->run_time_ms(); + if (proposed_time > in->critical_time_ms()) { + in->set_critical_time_ms(proposed_time); + if (!seen_edge(in)) { + work_queue.push(in); + } + } + } + } +} + +void Plan::ScheduleInitialEdges() { + // Add ready edges to queue. + assert(ready_.empty()); + std::set pools; + + for (std::map::iterator it = want_.begin(), + end = want_.end(); it != end; ++it) { + Edge* edge = it->first; + Plan::Want want = it->second; + if (!(want == kWantToStart && edge->AllInputsReady())) { + continue; + } + + Pool* pool = edge->pool(); + if (pool->ShouldDelayEdge()) { + pool->DelayEdge(edge); + pools.insert(pool); + } else { + ScheduleWork(it); + } + } + + // Call RetrieveReadyEdges only once at the end so higher priority + // edges are retrieved first, not the ones that happen to be first + // in the want_ map. + for (std::set::iterator it=pools.begin(), + end = pools.end(); it != end; ++it) { + (*it)->RetrieveReadyEdges(&ready_); + } +} + +void Plan::PrepareQueue(BuildLog* build_log) { + ComputeCriticalTime(build_log); + ScheduleInitialEdges(); +} + void Plan::Dump() const { printf("pending: %d\n", (int)want_.size()); for (map::const_iterator e = want_.begin(); e != want_.end(); ++e) { @@ -598,6 +795,7 @@ bool Builder::AlreadyUpToDate() const { bool Builder::Build(string* err) { assert(!AlreadyUpToDate()); + plan_.PrepareQueue(scan_.build_log()); status_->PlanHasTotalEdges(plan_.command_edge_count()); int pending_commands = 0; diff --git a/src/build.h b/src/build.h index d697dfb89e..7719d9a9a4 100644 --- a/src/build.h +++ b/src/build.h @@ -18,12 +18,11 @@ #include #include #include -#include #include #include #include "depfile_parser.h" -#include "graph.h" // XXX needed for DependencyScan; should rearrange. +#include "graph.h" #include "exit_status.h" #include "util.h" // int64_t @@ -76,21 +75,13 @@ struct Plan { /// Reset state. Clears want and ready sets. void Reset(); + // After all targets have been added, prepares the ready queue for find work. + void PrepareQueue(BuildLog* build_log); + /// Update the build plan to account for modifications made to the graph /// by information loaded from a dyndep file. bool DyndepsLoaded(DependencyScan* scan, const Node* node, const DyndepFile& ddf, std::string* err); -private: - bool RefreshDyndepDependents(DependencyScan* scan, const Node* node, std::string* err); - void UnmarkDependents(const Node* node, std::set* dependents); - bool AddSubTarget(const Node* node, const Node* dependent, std::string* err, - std::set* dyndep_walk); - - /// Update plan with knowledge that the given node is up to date. - /// If the node is a dyndep binding on any of its dependents, this - /// loads dynamic dependencies from the node's path. - /// Returns 'false' if loading dyndep info fails and 'true' otherwise. - bool NodeFinished(Node* node, std::string* err); /// Enumerate possible steps we want for an edge. enum Want @@ -105,6 +96,23 @@ struct Plan { kWantToFinish }; +private: + void ComputeCriticalTime(BuildLog* build_log); + bool RefreshDyndepDependents(DependencyScan* scan, const Node* node, std::string* err); + void UnmarkDependents(const Node* node, std::set* dependents); + bool AddSubTarget(const Node* node, const Node* dependent, std::string* err, + std::set* dyndep_walk); + + // Add edges that kWantToStart into the ready queue + // Must be called after ComputeCriticalTime and before FindWork + void ScheduleInitialEdges(); + + /// Update plan with knowledge that the given node is up to date. + /// If the node is a dyndep binding on any of its dependents, this + /// loads dynamic dependencies from the node's path. + /// Returns 'false' if loading dyndep info fails and 'true' otherwise. + bool NodeFinished(Node* node, std::string* err); + void EdgeWanted(const Edge* edge); bool EdgeMaybeReady(std::map::iterator want_e, std::string* err); @@ -119,9 +127,11 @@ struct Plan { /// we want for the edge. std::map want_; - EdgeSet ready_; + EdgePriorityQueue ready_; Builder* builder_; + /// user provided targets in build order, earlier one have higher priority + std::vector targets_; /// Total number of edges that have commands (not phony). int command_edges_; diff --git a/src/build_test.cc b/src/build_test.cc index 4ef62b2113..e8518b47b4 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -50,6 +50,14 @@ struct PlanTest : public StateTestWithBuiltinRules { sort(ret->begin(), ret->end(), CompareEdgesByOutput::cmp); } + void PrepareForTarget(const char* node, BuildLog *log=NULL) { + string err; + EXPECT_TRUE(plan_.AddTarget(GetNode(node), &err)); + ASSERT_EQ("", err); + plan_.PrepareQueue(log); + ASSERT_TRUE(plan_.more_to_do()); + } + void TestPoolWithDepthOne(const char *test_case); }; @@ -59,10 +67,7 @@ TEST_F(PlanTest, Basic) { "build mid: cat in\n")); GetNode("mid")->MarkDirty(); GetNode("out")->MarkDirty(); - string err; - EXPECT_TRUE(plan_.AddTarget(GetNode("out"), &err)); - ASSERT_EQ("", err); - ASSERT_TRUE(plan_.more_to_do()); + PrepareForTarget("out"); Edge* edge = plan_.FindWork(); ASSERT_TRUE(edge); @@ -71,6 +76,7 @@ TEST_F(PlanTest, Basic) { ASSERT_FALSE(plan_.FindWork()); + string err; plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err); ASSERT_EQ("", err); @@ -95,15 +101,12 @@ TEST_F(PlanTest, DoubleOutputDirect) { GetNode("mid1")->MarkDirty(); GetNode("mid2")->MarkDirty(); GetNode("out")->MarkDirty(); - - string err; - EXPECT_TRUE(plan_.AddTarget(GetNode("out"), &err)); - ASSERT_EQ("", err); - ASSERT_TRUE(plan_.more_to_do()); + PrepareForTarget("out"); Edge* edge; edge = plan_.FindWork(); ASSERT_TRUE(edge); // cat in + string err; plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err); ASSERT_EQ("", err); @@ -128,14 +131,12 @@ TEST_F(PlanTest, DoubleOutputIndirect) { GetNode("b1")->MarkDirty(); GetNode("b2")->MarkDirty(); GetNode("out")->MarkDirty(); - string err; - EXPECT_TRUE(plan_.AddTarget(GetNode("out"), &err)); - ASSERT_EQ("", err); - ASSERT_TRUE(plan_.more_to_do()); + PrepareForTarget("out"); Edge* edge; edge = plan_.FindWork(); ASSERT_TRUE(edge); // cat in + string err; plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err); ASSERT_EQ("", err); @@ -169,15 +170,12 @@ TEST_F(PlanTest, DoubleDependent) { GetNode("a1")->MarkDirty(); GetNode("a2")->MarkDirty(); GetNode("out")->MarkDirty(); - - string err; - EXPECT_TRUE(plan_.AddTarget(GetNode("out"), &err)); - ASSERT_EQ("", err); - ASSERT_TRUE(plan_.more_to_do()); + PrepareForTarget("out"); Edge* edge; edge = plan_.FindWork(); ASSERT_TRUE(edge); // cat in + string err; plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err); ASSERT_EQ("", err); @@ -209,6 +207,7 @@ void PlanTest::TestPoolWithDepthOne(const char* test_case) { ASSERT_EQ("", err); EXPECT_TRUE(plan_.AddTarget(GetNode("out2"), &err)); ASSERT_EQ("", err); + plan_.PrepareQueue(NULL); ASSERT_TRUE(plan_.more_to_do()); Edge* edge = plan_.FindWork(); @@ -284,10 +283,7 @@ TEST_F(PlanTest, PoolsWithDepthTwo) { GetNode("outb" + string(1, '1' + static_cast(i)))->MarkDirty(); } GetNode("allTheThings")->MarkDirty(); - - string err; - EXPECT_TRUE(plan_.AddTarget(GetNode("allTheThings"), &err)); - ASSERT_EQ("", err); + PrepareForTarget("allTheThings"); deque edges; FindWorkSorted(&edges, 5); @@ -306,6 +302,7 @@ TEST_F(PlanTest, PoolsWithDepthTwo) { ASSERT_EQ("outb3", edge->outputs_[0]->path()); // finish out1 + string err; plan_.EdgeFinished(edges.front(), Plan::kEdgeSucceeded, &err); ASSERT_EQ("", err); edges.pop_front(); @@ -363,10 +360,7 @@ TEST_F(PlanTest, PoolWithRedundantEdges) { GetNode("bar.cpp.obj")->MarkDirty(); GetNode("libfoo.a")->MarkDirty(); GetNode("all")->MarkDirty(); - string err; - EXPECT_TRUE(plan_.AddTarget(GetNode("all"), &err)); - ASSERT_EQ("", err); - ASSERT_TRUE(plan_.more_to_do()); + PrepareForTarget("all"); Edge* edge = NULL; @@ -375,6 +369,7 @@ TEST_F(PlanTest, PoolWithRedundantEdges) { edge = initial_edges[1]; // Foo first ASSERT_EQ("foo.cpp", edge->outputs_[0]->path()); + string err; plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err); ASSERT_EQ("", err); @@ -439,6 +434,7 @@ TEST_F(PlanTest, PoolWithFailingEdge) { ASSERT_EQ("", err); EXPECT_TRUE(plan_.AddTarget(GetNode("out2"), &err)); ASSERT_EQ("", err); + plan_.PrepareQueue(NULL); ASSERT_TRUE(plan_.more_to_do()); Edge* edge = plan_.FindWork(); @@ -467,6 +463,148 @@ TEST_F(PlanTest, PoolWithFailingEdge) { ASSERT_EQ(0, edge); } +TEST_F(PlanTest, PriorityWithoutBuildLog) { + // Without a build log, the critical time is equivalent to graph + // depth. Test with the following graph: + // a2 + // | + // a1 b1 + // | | | + // a0 b0 c0 + // \ | / + // out + + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, + "rule r\n" + " command = unused\n" + "build out: r a0 b0 c0\n" + "build a0: r a1\n" + "build a1: r a2\n" + "build b0: r b1\n" + "build c0: r b1\n" + )); + GetNode("a1")->MarkDirty(); + GetNode("a0")->MarkDirty(); + GetNode("b0")->MarkDirty(); + GetNode("c0")->MarkDirty(); + GetNode("out")->MarkDirty(); + BuildLog log; + PrepareForTarget("out", &log); + + EXPECT_EQ(GetNode("out")->in_edge()->critical_time_ms(), 1); + EXPECT_EQ(GetNode("a0")->in_edge()->critical_time_ms(), 2); + EXPECT_EQ(GetNode("b0")->in_edge()->critical_time_ms(), 2); + EXPECT_EQ(GetNode("c0")->in_edge()->critical_time_ms(), 2); + EXPECT_EQ(GetNode("a1")->in_edge()->critical_time_ms(), 3); + + const int n_edges = 5; + const char *expected_order[n_edges] = { + "a1", "a0", "b0", "c0", "out"}; + for (int i = 0; i < n_edges; ++i) { + Edge* edge = plan_.FindWork(); + ASSERT_NE(edge, NULL); + EXPECT_EQ(expected_order[i], edge->outputs_[0]->path()); + + std::string err; + ASSERT_TRUE(plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err)); + EXPECT_EQ(err, ""); + } + + EXPECT_FALSE(plan_.FindWork()); +} + +TEST_F(PlanTest, PriorityWithBuildLog) { + // With a build log, the critical time is longest weighted path. + // Test with the following graph: + // a2 + // | + // a1 b1 + // | | | + // a0 b0 c0 + // \ | / + // out + + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, + "rule r\n" + " command = unused\n" + "build out: r a0 b0 c0\n" + "build a0: r a1\n" + "build a1: r a2\n" + "build b0: r b1\n" + "build c0: r b1\n" + )); + GetNode("a1")->MarkDirty(); + GetNode("a0")->MarkDirty(); + GetNode("b0")->MarkDirty(); + GetNode("c0")->MarkDirty(); + GetNode("out")->MarkDirty(); + + BuildLog log; + log.RecordCommand(GetNode("out")->in_edge(), 0, 100); // time = 100 + log.RecordCommand(GetNode("a0")->in_edge(), 10, 20); // time = 10 + log.RecordCommand(GetNode("a1")->in_edge(), 20, 40); // time = 20 + log.RecordCommand(GetNode("b0")->in_edge(), 10, 30); // time = 20 + log.RecordCommand(GetNode("c0")->in_edge(), 20, 70); // time = 50 + + PrepareForTarget("out", &log); + + EXPECT_EQ(GetNode("out")->in_edge()->critical_time_ms(), 100); + EXPECT_EQ(GetNode("a0")->in_edge()->critical_time_ms(), 110); + EXPECT_EQ(GetNode("b0")->in_edge()->critical_time_ms(), 120); + EXPECT_EQ(GetNode("c0")->in_edge()->critical_time_ms(), 150); + EXPECT_EQ(GetNode("a1")->in_edge()->critical_time_ms(), 130); + + const int n_edges = 5; + const char *expected_order[n_edges] = { + "c0", "a1", "b0", "a0", "out"}; + for (int i = 0; i < n_edges; ++i) { + Edge* edge = plan_.FindWork(); + ASSERT_NE(edge, NULL); + EXPECT_EQ(expected_order[i], edge->outputs_[0]->path()); + + std::string err; + ASSERT_TRUE(plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, &err)); + EXPECT_EQ(err, ""); + } + EXPECT_FALSE(plan_.FindWork()); +} + +TEST_F(PlanTest, RuntimePartialBuildLog) { + // Test the edge->run_time_ms() estimate when no build log is available + + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, + "rule r\n" + " command = unused\n" + "build out: r a0 b0 c0 d0\n" + "build a0: r a1\n" + "build b0: r b1\n" + "build c0: r c1\n" + "build d0: r d1\n" + )); + GetNode("a0")->MarkDirty(); + GetNode("b0")->MarkDirty(); + GetNode("c0")->MarkDirty(); + GetNode("d0")->MarkDirty(); + GetNode("out")->MarkDirty(); + + BuildLog log; + log.RecordCommand(GetNode("out")->in_edge(), 0, 100); // time = 40 + log.RecordCommand(GetNode("a0")->in_edge(), 10, 20); // time = 10 + log.RecordCommand(GetNode("b0")->in_edge(), 20, 40); // time = 20 + log.RecordCommand(GetNode("c0")->in_edge(), 10, 40); // time = 30 + + PrepareForTarget("out", &log); + + // These edges times are read from the build log + EXPECT_EQ(GetNode("out")->in_edge()->run_time_ms(), 100); + EXPECT_EQ(GetNode("a0")->in_edge()->run_time_ms(), 10); + EXPECT_EQ(GetNode("b0")->in_edge()->run_time_ms(), 20); + EXPECT_EQ(GetNode("c0")->in_edge()->run_time_ms(), 30); + + // The missing data is taken from the 3rd quintile of known data + EXPECT_EQ(GetNode("d0")->in_edge()->run_time_ms(), 30); +} + /// Fake implementation of CommandRunner, useful for tests. struct FakeCommandRunner : public CommandRunner { explicit FakeCommandRunner(VirtualFileSystem* fs) : diff --git a/src/graph.h b/src/graph.h index 141b43993e..0fab807c9c 100644 --- a/src/graph.h +++ b/src/graph.h @@ -19,6 +19,7 @@ #include #include #include +#include #include "dyndep.h" #include "eval_env.h" @@ -170,9 +171,10 @@ struct Edge { Edge() : rule_(NULL), pool_(NULL), dyndep_(NULL), env_(NULL), mark_(VisitNone), - id_(0), outputs_ready_(false), deps_loaded_(false), - deps_missing_(false), generated_by_dep_loader_(false), - implicit_deps_(0), order_only_deps_(0), implicit_outs_(0) {} + id_(0), run_time_ms_(0), critical_time_ms_(-1), outputs_ready_(false), + deps_loaded_(false), deps_missing_(false), + generated_by_dep_loader_(false), implicit_deps_(0), order_only_deps_(0), + implicit_outs_(0) {} /// Return true if all inputs' in-edges are ready. bool AllInputsReady() const; @@ -195,6 +197,23 @@ struct Edge { void Dump(const char* prefix="") const; + // Critical time is the estimated execution time in ms of the edges + // forming the longest time-weighted path to the target output. + // This quantity is used as a priority during build scheduling. + // NOTE: Defaults to -1 as a marker smaller than any valid time + int64_t critical_time_ms() const { return critical_time_ms_; } + void set_critical_time_ms(int64_t critical_time_ms) { + critical_time_ms_ = critical_time_ms; + } + + // Run time in ms for this edge's command. + // Taken from the build log if present, or estimated otherwise. + // Default initialized to 0. + int64_t run_time_ms() const { return run_time_ms_; } + void set_run_time_ms(int64_t run_time_ms) { + run_time_ms_ = run_time_ms; + } + const Rule* rule_; Pool* pool_; std::vector inputs_; @@ -204,6 +223,8 @@ struct Edge { BindingEnv* env_; VisitMark mark_; size_t id_; + int64_t run_time_ms_; + int64_t critical_time_ms_; bool outputs_ready_; bool deps_loaded_; bool deps_missing_; @@ -363,4 +384,40 @@ struct DependencyScan { DyndepLoader dyndep_loader_; }; +// Implements a less comparison for edges by priority, where highest +// priority is defined lexicographically first by largest critical +// time, then lowest ID. +// +// Including ID means that wherever the critical times are the same, +// the edges are executed in ascending ID order which was historically +// how all tasks were scheduled. +struct EdgePriorityLess { + bool operator()(const Edge* e1, const Edge* e2) const { + const int64_t ct1 = e1->critical_time_ms(); + const int64_t ct2 = e2->critical_time_ms(); + if (ct1 != ct2) { + return ct1 < ct2; + } + return e1->id_ > e2->id_; + } +}; + +// Reverse of EdgePriorityLess, e.g. to sort by highest priority first +struct EdgePriorityGreater { + bool operator()(const Edge* e1, const Edge* e2) const { + return EdgePriorityLess()(e2, e1); + } +}; + +// A priority queue holding non-owning Edge pointers. top() will +// return the edge with the largest critical time, and lowest ID if +// more than one edge has the same critical time. +class EdgePriorityQueue: + public std::priority_queue, EdgePriorityLess>{ +public: + void clear() { + c.clear(); + } +}; + #endif // NINJA_GRAPH_H_ diff --git a/src/graph_test.cc b/src/graph_test.cc index 5314bc5f5f..c7efec3284 100644 --- a/src/graph_test.cc +++ b/src/graph_test.cc @@ -944,3 +944,56 @@ TEST_F(GraphTest, PhonyDepsMtimes) { EXPECT_EQ(out1->mtime(), out1Mtime1); EXPECT_TRUE(out1->dirty()); } + +// Test that EdgeQueue correctly prioritizes by critical time +TEST_F(GraphTest, EdgeQueuePriority) { + + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"rule r\n" +" command = unused\n" +"build out1: r in1\n" +"build out2: r in2\n" +"build out3: r in3\n" +)); + + const int n_edges = 3; + Edge *(edges)[n_edges] = { + GetNode("out1")->in_edge(), + GetNode("out2")->in_edge(), + GetNode("out3")->in_edge(), + }; + + // Output is largest critical time to smallest + for (int i = 0; i < n_edges; ++i) { + edges[i]->set_critical_time_ms(i * 10); + } + + EdgePriorityQueue queue; + for (int i = 0; i < n_edges; ++i) { + queue.push(edges[i]); + } + + EXPECT_EQ(queue.size(), n_edges); + for (int i = 0; i < n_edges; ++i) { + EXPECT_EQ(queue.top(), edges[n_edges - 1 - i]); + queue.pop(); + } + EXPECT_TRUE(queue.empty()); + + // When there is ambiguity, the lowest edge id comes first + for (int i = 0; i < n_edges; ++i) { + edges[i]->set_critical_time_ms(0); + } + + queue.push(edges[1]); + queue.push(edges[2]); + queue.push(edges[0]); + + for (int i = 0; i < n_edges; ++i) { + EXPECT_EQ(queue.top(), edges[i]); + queue.pop(); + } + EXPECT_TRUE(queue.empty()); +} + + diff --git a/src/state.cc b/src/state.cc index 556b0d8802..e194519d68 100644 --- a/src/state.cc +++ b/src/state.cc @@ -38,13 +38,13 @@ void Pool::DelayEdge(Edge* edge) { delayed_.insert(edge); } -void Pool::RetrieveReadyEdges(EdgeSet* ready_queue) { +void Pool::RetrieveReadyEdges(EdgePriorityQueue* ready_queue) { DelayedEdges::iterator it = delayed_.begin(); while (it != delayed_.end()) { Edge* edge = *it; if (current_use_ + edge->weight() > depth_) break; - ready_queue->insert(edge); + ready_queue->push(edge); EdgeScheduled(*edge); ++it; } diff --git a/src/state.h b/src/state.h index 878ac6d991..05fb50e59f 100644 --- a/src/state.h +++ b/src/state.h @@ -62,7 +62,7 @@ struct Pool { void DelayEdge(Edge* edge); /// Pool will add zero or more edges to the ready_queue - void RetrieveReadyEdges(EdgeSet* ready_queue); + void RetrieveReadyEdges(EdgePriorityQueue* ready_queue); /// Dump the Pool and its edges (useful for debugging). void Dump() const; @@ -80,7 +80,10 @@ struct Pool { if (!a) return b; if (!b) return false; int weight_diff = a->weight() - b->weight(); - return ((weight_diff < 0) || (weight_diff == 0 && EdgeCmp()(a, b))); + if (weight_diff != 0) { + return weight_diff < 0; + } + return EdgePriorityGreater()(a, b); } };