Skip to content

Performance improvements RecomputeOutputsDirtyCache - #2821

Open
moritzx22 wants to merge 5 commits into
ninja-build:masterfrom
moritzx22:RecomputeOutputsDirtyCache_Improve
Open

Performance improvements RecomputeOutputsDirtyCache#2821
moritzx22 wants to merge 5 commits into
ninja-build:masterfrom
moritzx22:RecomputeOutputsDirtyCache_Improve

Conversation

@moritzx22

@moritzx22 moritzx22 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

This PR adds several performance improvements to class RecomputeOutputsDirtyCache, see PR #2680. There are no functional changes.

  • Lazy allocation of logEntry_
    • The cache vector is now allocated on first use in all() instead of unconditionally in the constructor, and is skipped entirely for phony edges, which never touch it.
  • Fast path for single-output edges
    • Many edges have exactly one output
  • Lazy computation of isRestat_
  • Shrink CachedLogEntry from 16 to 8 bytes
    • Noticeable especially on edges with many outputs.
std::equal(a.begin(), a.end(), b.begin()) doesn't verify that a
and b are the same length. Use the 4-iterator overload instead.
Skip the allocation entirely for phony edges, which never use it.
Moved from the constructor into all(), right before first use, so
instances that are constructed but never have all() called on them
also avoid the allocation.
Many edges have exactly one output. Cache the lookup result in an
member (logEntryOneOutput_) for that case instead of allocating
a std::vector, for better performance.
Comment thread src/graph.cc Outdated
private:
bool evaluated_ = false;
BuildLog::LogEntry* entry_ = nullptr;
static BuildLog::LogEntry* const kUnevaluated;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: Just use a constant pointer value like reinterpret_cast<const BuildLog::Entry*>(1) instead of loading a pointer for .rodata on each call.

You cannot make such an expression constexpr due to the reinterpret_cast<> so, as a special case, just use a macro for simplicity instead. Call this CACHED_LOG_ENTRY_INIT_SENTINEL for clarity.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment thread src/graph.cc Outdated
// the log against the most recent input's mtime (see below)
bool used_restat = false;
if (isRestat_ && buildLog_ && entry.LookupByOutput(buildLog_, output)) {
if (isRestat_() && buildLog_ && entry.LookupByOutput(buildLog_, output)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So much complexity makes the code unnecessarily obscure and hard to follow, Just add a is_restat_init_ boolean flag, and a is_restat() method that will check it before returning the result to perform.

Follow conventions by using snake_case and not pascalCase.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

entry_ now starts pointing at a static object instead of nullptr,
so a single pointer tracks both "not yet evaluated" and "evaluated,
not found". No separate evaluated_ flag needed anymore.

Shrinks CachedLogEntry from 16 to 8 bytes, halving the size of the
logEntry_ vector. Most noticeable on edges with many outputs.
@moritzx22
moritzx22 force-pushed the RecomputeOutputsDirtyCache_Improve branch from 3682921 to 7d679ac Compare August 11, 2026 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants