Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add critical path scheduler to improve build times #2019

Closed
wants to merge 20 commits into from

Conversation

peterbell10
Copy link
Contributor

@peterbell10 peterbell10 commented Aug 25, 2021

This is based on #1333 and fixes #376.

I'm interesting in seeing #1333 merged, but unfortunately it seems to have languished. When I tried it, it was completely broken. This maintains the same principal, but reworks it to: address review comments, fix bugs, and improve time complexity of operations.

For each edge depended on by the targets being built, we calculate its priority as the longest path through the weighted directed graph of dependencies. Where weights come from the build log's last execution time. Ready build tasks are then always executed highest priority first.

#1333 maintained a std::list of all edges in priority order. This PR instead uses a std::priority_queue of only the ready edges (wrapped in the EdgeQueue class). This method has log time push, and pop so avoids the O(n) traversal we had with the list.

nico and others added 5 commits August 25, 2021 11:48
The existing algorithm doesn't work because it strictly requires that
all outputs are visited before updating an edge. So any task downstream
from a task with multiple out-edges may get ignored.

The fix is to always propagate your critical time to the next input
node, and only place it in the queue if you offer a higher critical
time.
src/build.cc Outdated Show resolved Hide resolved
active_edges.erase(e);

for (std::vector<Node*>::iterator it = e->inputs_.begin(),
end = e->inputs_.end();

This comment was marked as abuse.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assumed C++11 isn't required yet from the code style used elsewhere. Is that correct?

This comment was marked as abuse.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also means no std::chrono::duration<int64_t, std::milli> :-(

src/build.cc Outdated
@@ -75,6 +75,16 @@ bool DryRunCommandRunner::WaitForCommand(Result* result) {

} // namespace


bool EdgeQueue::EdgePriorityCompare::operator()(const Edge* e1, const Edge* e2) const {

This comment was marked as abuse.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

graph.h which defines Edge isn't included in the header.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could template it. (And if you really mean for it to be just Edge*, you can static_assert that the template is a forward-declared type. As in

class Edge; // Fwd declare
...
    template <typename EdgeType>
    [[nodiscard]] bool operator()(const EdgeType* e1, const EdgeType* e2) const {
        static_assert(std::is_same_v<EdgeType, Edge>); // Avoiding including `graph.h`.
        ...

src/graph.h Outdated Show resolved Hide resolved
src/build.cc Outdated Show resolved Hide resolved
src/build.cc Outdated Show resolved Hide resolved
src/build.cc Show resolved Hide resolved
src/build.cc Outdated Show resolved Hide resolved
src/build.h Outdated Show resolved Hide resolved
src/build.h Outdated Show resolved Hide resolved
src/build.h Outdated Show resolved Hide resolved
src/build.h Outdated Show resolved Hide resolved
src/build.h Outdated Show resolved Hide resolved
src/build.h Outdated Show resolved Hide resolved
src/build.cc Outdated Show resolved Hide resolved
src/build.cc Outdated Show resolved Hide resolved
src/build.cc Outdated Show resolved Hide resolved
src/build.cc Outdated Show resolved Hide resolved
@peterbell10
Copy link
Contributor Author

BTW, here is an example of how the scheduler improves build times. The project I'm working on takes 20 minutes to compile normally, but goes down to 15 minutes with this PR and a primed .ninja_log file.

Before

ninja_no_scheduler

After

ninja_cpsched

@peterbell10 peterbell10 force-pushed the cpsched branch 2 times, most recently from 50b5f28 to aff5ebc Compare March 8, 2022 04:35
@kinke
Copy link

kinke commented Mar 23, 2022

Something worth noting about clean builds is it should still be a win.

In my case, the initial build took the same time as with ninja master (I've rebased this PR). What I very much like is that I can manually tweak the ninja cmdline and add known long-running targets there; adding such an object file reduced the initial build time from 33 seconds to 26 seconds. Subsequent builds with .ninja_log are done in 24 seconds. So I absolutely love it, thanks dude!

FWIW, some info about my very imbalanced build (few long-running processes) with -j24:

    Longest build steps:
           0.4 weighted s to build .reggae/objs/sil.objs/__dub__/core/source/kaleidic/sil/std/core/c... (8.4 s elapsed time)
           0.4 weighted s to build .reggae/objs/sil.objs/__dub__/core-datetime/source/kaleidic/sil/s... (9.3 s elapsed time)
           0.4 weighted s to build .reggae/objs/sil.objs/__dub__/extra/source/kaleidic/sil/std/extra... (10.2 s elapsed time)
           0.4 weighted s to build .reggae/objs/sil.objs/__dub__/extra-xlsx/source/kaleidic/sil/std/... (10.7 s elapsed time)
           0.5 weighted s to build .reggae/objs/sil.objs/__dub__/extra/source/kaleidic/sil/std/extra... (11.4 s elapsed time)
           0.5 weighted s to build .reggae/objs/sil.objs/__dub__/extra-technical/source/kaleidic/sil... (13.2 s elapsed time)
           0.6 weighted s to build .reggae/objs/sil.objs/__dub__/extra-kaleidic/source/kaleidic/sil/... (13.8 s elapsed time)
           0.8 weighted s to build .reggae/objs/sil.objs/__dub__/symmetry-imap/source/imap_auth.o (18.7 s elapsed time)
           0.8 weighted s to build .reggae/objs/sil.objs/__dub__/core/source/kaleidic/sil/std/core_a... (18.9 s elapsed time)
           4.1 weighted s to build sil (4.1 s elapsed time)
    Time by build-step type:
           4.1 s weighted time to generate 1 (no extension found) files (4.1 s elapsed time sum)
          19.5 s weighted time to generate 269 .o files (466.2 s elapsed time sum)
    23.5 s weighted time (470.3 s elapsed time sum, 20.0x parallelism)
    270 build steps completed, average of 11.48/s

kinke added a commit to symmetryinvestments/ldc that referenced this pull request Mar 23, 2022
@kinke
Copy link

kinke commented Mar 23, 2022

I should add that for primed builds with a .ninja_log, the peak memory requirements can increase significantly in case the target runtimes correlate with memory consumption - all heavy targets being scheduled first at the same time.

@digit-google
Copy link
Contributor

Just to say that the current patch doesn't change the build time for Fuchsia builds (it may even slow it down by a few percent but it' s hard to be certain), however we rely heavily on pools, which may be limiting the benefit of the new algorithm.

kinke added a commit to symmetryinvestments/ldc that referenced this pull request May 16, 2022
ninja-build/ninja#2019, rebased onto latest
master (including ninja-build/ninja#1866 for
a deterministic and predictable build order, which hasn't landed in
any official release yet).
@hadrielk
Copy link

hadrielk commented Jun 3, 2022

@peterbell10 where did you get such a beautiful chart?

I've been staring at tables, waterfall charts and flame graphs, but I'd much prefer to see build-times your way.

@def-
Copy link

def- commented Jun 3, 2022

That's ninjatracing: https://github.com/nico/ninjatracing

kinke added a commit to symmetryinvestments/ldc that referenced this pull request Jul 16, 2022
kinke added a commit to symmetryinvestments/ldc that referenced this pull request Jul 16, 2022
@jhasse
Copy link
Collaborator

jhasse commented Aug 22, 2022

Closing in favor of #2177.

@jhasse jhasse closed this Aug 22, 2022
@jhasse jhasse removed this from the 1.12.0 milestone Jan 24, 2023
kinke added a commit to symmetryinvestments/ldc that referenced this pull request Mar 6, 2023
kinke added a commit to symmetryinvestments/ldc that referenced this pull request Mar 13, 2023
kinke added a commit to symmetryinvestments/ldc that referenced this pull request May 14, 2023
@kinke
Copy link

kinke commented Jul 30, 2023

FWIW, I've published a drop-in ninja release including this PR as main change, making the historical-build-times usage opt-in via --cp: https://github.com/symmetryinvestments/ninja/releases/tag/v1.11.1-sym1 (and https://github.com/symmetryinvestments/gha-setup-ninja along with it)

@travisdowns
Copy link

@kinke - awesome, I'm going to try this soon.

Just to clarify (since you can't create issues on forks), without --cp this release would work the same as upstream ninja 1.11.1?

@kinke
Copy link

kinke commented Jul 31, 2023

Just to clarify (since you can't create issues on forks), without --cp this release would work the same as upstream ninja 1.11.1?

Nope; there's still the default prioritization by explicit cmdline order / node depth. --cp really only affects whether the previous .ninja_log timings are to be used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use better logic for picking which node to build first