Skip to content

Add simplified critical path scheduler to improve build times - #2177

Merged
jhasse merged 22 commits into
ninja-build:masterfrom
peterbell10:cpsched-2
Feb 29, 2024
Merged

Add simplified critical path scheduler to improve build times#2177
jhasse merged 22 commits into
ninja-build:masterfrom
peterbell10:cpsched-2

Conversation

@peterbell10

Copy link
Copy Markdown
Contributor

This is a simplified alternative to #2019.

It still adds a critical path scheduler based on weighted edges in the build graph, but instead of using historical runtime it simply assigns a priority of 1 to everything except phony edges. In doing so, it prioritizes jobs by their depth in the build graph. This is better than random scheduling because jobs with dependents will unlock more work to be done when they are completed, thus we get fewer instances of starving the command runner.

For example, if your build has some code-generated sources then the code generator is prioritized above compiling non-generated sources and so all sources files will be unblocked and able to compile in parallel.

Compared to weighting by historic runtime, this will be worse in cases where build times are limited by a single long-running compile job at a low depth and with no dependencies. However, it does avoid the problem of systematically launching the resource-intensive jobs all at once.

nico and others added 22 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.
1. Move EdgePriorityQueue to graph.h and inherit from priority_queue
2. Add comment about edge->critical_time()
AddTarget cannot add edges to the ready queue before the critical time
has been computed.
@Meinersbur

Copy link
Copy Markdown

I would have preferred the approach from #2019. The number of edges is a quite inaccurate estimate for the build time of the critical path. Also, I feel the mentioned problem1 is an orthogonal problem that should be solved differently, e.g. with pools. Both are symptoms that heavily depend on the particular project and do not necessarily generalize to other projects.

Have you considered only prioritizing the critical path (as if with infinite parallelism), and schedule other edges in parallel to it according to other priorities, such as minimizing peak resource usage2?

Footnotes

  1. https://github.com/ninja-build/ninja/pull/2019#issuecomment-1076502406 â†Đ

  2. I think ninja currently does not measure resource usage, but a simple peak rss could recorded? â†Đ

@kaspar030

Copy link
Copy Markdown

The number of edges is a quite inaccurate estimate for the build time of the critical path.

The historic build time is inaccurate, too, if e.g., ccache is used.

elliotgoodrich added a commit to elliotgoodrich/trimja that referenced this pull request Mar 23, 2025
If a build is going to fail, we want it to fail as fast as possible.
This will give quicker feedback to the developer and reduce build
resources.

To facilitate this, we want to prioritize build edges that depend on
changed files over other build edges that are at the same dependency
level. e.g. for C++ projects we want to start compiling modified
source files before other sources files that belong to the same
library/executable.  In this case we shouldn't increase (on average) the
total build time on a successful fun, but an unsuccessful build would
fail much faster.

In ninja 1.12.0 a critical path schedule was added
ninja-build/ninja#2177, which prioritizes build
commands based on their distance from the build target.  If two or more
build commands have the same distance, it looks like the one that
appears first in the build file is built first.

Before ninja 1.12.0, the one that appears first in the build file is
prioritized.

This commit floats all affected commands to the top of the ninja build
file (as far as it is possible at least) so that older ninja versions
will work, and it helps ninja 1.12.0 in the case of ties.

A future change will need to be made to artificially lengthen the
critical path for affected files so that ninja 1.12.0+ will be fully
supported.
elliotgoodrich added a commit to elliotgoodrich/trimja that referenced this pull request Mar 23, 2025
If a build is going to fail, we want it to fail as fast as possible.
This will give quicker feedback to the developer and reduce build
resources.

To facilitate this, we want to prioritize build edges that depend on
changed files over other build edges that are at the same dependency
level. e.g. for C++ projects we want to start compiling modified
source files before other sources files that belong to the same
library/executable.  In this case we shouldn't increase (on average) the
total build time on a successful fun, but an unsuccessful build would
fail much faster.

In ninja 1.12.0 a critical path schedule was added
ninja-build/ninja#2177, which prioritizes build
commands based on their distance from the build target.  If two or more
build commands have the same distance, it looks like the one that
appears first in the build file is built first.

Before ninja 1.12.0, the one that appears first in the build file is
prioritized.

This commit floats all affected commands to the top of the ninja build
file (as far as it is possible at least) so that older ninja versions
will work, and it helps ninja 1.12.0 in the case of ties.

A future change will need to be made to artificially lengthen the
critical path for affected files so that ninja 1.12.0+ will be fully
supported.
elliotgoodrich added a commit to elliotgoodrich/trimja that referenced this pull request Mar 23, 2025
If a build is going to fail, we want it to fail as fast as possible.
This will give quicker feedback to the developer and reduce build
resources.

To facilitate this, we want to prioritize build edges that depend on
changed files over other build edges that are at the same dependency
level. e.g. for C++ projects we want to start compiling modified
source files before other sources files that belong to the same
library/executable.  In this case we shouldn't increase (on average) the
total build time on a successful fun, but an unsuccessful build would
fail much faster.

In ninja 1.12.0 a critical path schedule was added
ninja-build/ninja#2177, which prioritizes build
commands based on their distance from the build target.  If two or more
build commands have the same distance, it looks like the one that
appears first in the build file is built first.

Before ninja 1.12.0, the one that appears first in the build file is
prioritized.

This commit floats all affected commands to the top of the ninja build
file (as far as it is possible at least) so that older ninja versions
will work, and it helps ninja 1.12.0 in the case of ties.

A future change will need to be made to artificially lengthen the
critical path for affected files so that ninja 1.12.0+ will be fully
supported.
elliotgoodrich added a commit to elliotgoodrich/trimja that referenced this pull request Mar 23, 2025
If a build is going to fail, we want it to fail as fast as possible.
This will give quicker feedback to the developer and reduce build
resources.

To facilitate this, we want to prioritize build edges that depend on
changed files over other build edges that are at the same dependency
level. e.g. for C++ projects we want to start compiling modified
source files before other sources files that belong to the same
library/executable.  In this case we shouldn't increase (on average) the
total build time on a successful fun, but an unsuccessful build would
fail much faster.

In ninja 1.12.0 a critical path schedule was added
ninja-build/ninja#2177, which prioritizes build
commands based on their distance from the build target.  If two or more
build commands have the same distance, it looks like the one that
appears first in the build file is built first.

Before ninja 1.12.0, the one that appears first in the build file is
prioritized.

This commit floats all affected commands to the top of the ninja build
file (as far as it is possible at least) so that older ninja versions
will work, and it helps ninja 1.12.0 in the case of ties.

A future change will need to be made to artificially lengthen the
critical path for affected files so that ninja 1.12.0+ will be fully
supported.
jtmorrisbytes pushed a commit to jtmorrisbytes/ninja-rs that referenced this pull request May 4, 2026
Add simplified critical path scheduler to improve build times
kinke added a commit to kinke/ninja that referenced this pull request Sep 9, 2026
â€Ķit targets

This is a stab at ninja-build#232,
allowing to tweak the scheduling order via e.g.
`ninja <slow output> all`, for first-time builds without previous
build times yet, such as fresh CI builds in ephemeral containers.

Implemented by overriding the weight of target-producing edges iff.
a) multiple targets have been specified, and
b) there is no previous build time for that edge yet.

This target-order-specific weight is stored in the upper 32 bits
of the 64-bit weight, starting with 1<<32 for the last target's edge,
2<<32 for the 2nd-last target's, 3<<32 for the 3rd-last etc.
It should thus always dominate normal weights (e.g., elapsed
milliseconds for the previous build).

(Something similar to this was implemented in ninja-build#2019, but not in
accepted ninja-build#2177. With ninja-build#2682 having landed, I think this is the last
remaining interesting piece from ninja-build#2019.)
kinke added a commit to kinke/ninja that referenced this pull request Sep 9, 2026
â€Ķit targets

This is a stab at ninja-build#232,
allowing to tweak the scheduling order via e.g.
`ninja <slow output> all`, for first-time builds without previous
build times yet, such as fresh CI builds in ephemeral containers.

Implemented by overriding the weight of target-producing edges iff.
* multiple targets have been specified, and
* there is no previous build time for that edge yet.

This target-order-specific weight is stored in the upper 32 bits
of the 64-bit weight, starting with 1<<32 for the last target's edge,
2<<32 for the 2nd-last target's, 3<<32 for the 3rd-last etc.
It should thus always dominate normal weights (e.g., elapsed
milliseconds for the previous build).

(Something similar to this was implemented in ninja-build#2019, but not in
accepted ninja-build#2177. With ninja-build#2686 having landed, I think this is the last
remaining interesting piece from ninja-build#2019.)
kinke added a commit to kinke/ninja that referenced this pull request Sep 10, 2026
â€Ķit targets

This is a stab at ninja-build#232,
allowing to tweak the scheduling order via e.g.
`ninja <slow output> all`, for first-time builds without previous
build times yet, such as fresh CI builds in ephemeral containers.

Implemented by overriding the weight of target-producing edges iff.
* multiple targets have been specified, and
* there is no previous build time for that edge yet.

This target-order-specific weight is stored in the upper 32 bits
of the 64-bit weight, starting with 1<<32 for the last target's edge,
2<<32 for the 2nd-last target's, 3<<32 for the 3rd-last etc.
It should thus always dominate normal weights (e.g., elapsed
milliseconds for the previous build).

(Something similar to this was implemented in ninja-build#2019, but not in
accepted ninja-build#2177. With ninja-build#2686 having landed, I think this is the last
remaining interesting piece from ninja-build#2019.)
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.

5 participants