perf: memoize successful path canonicalization (~35k realpath calls per build) - #1544
Merged
Merged
Conversation
…uild) Cache-key construction normalizes every path-bearing compiler flag of every translation unit. `path_arg_for_compile_cwd` canonicalizes *both* the path and the compile cwd on each call, so building the ESP32-S3 framework-core cache key performs, per build: 385 include dirs x 46 core sources x 2 (path + cwd) = ~35,000 realpath(2) The cwd is the same value in all 35,420 calls and the include dirs are the same 385 for every source, so ~386 distinct resolutions are repeated 35,000 times. Measured on bench/blink -e esp32s3 (16-core Linux), this is the bulk of the unaccounted time between the `framework-libs cache` and `framework core cache` log lines, where no perf phase is open: it is cache-state independent and identical on warm and cold builds. `canonicalize_lexical` now memoizes its **successful `realpath` results only**. The parent-plus-filename fallback is deliberately not memoized: that is the branch answering for paths that do not exist yet, and caching it would keep reporting a directory as missing for the rest of the process, so a not-yet-created include dir would never resolve. A memo hit is therefore always a path that existed and was resolved once. The map is capped at 8192 entries so a long-lived daemon cannot grow it without bound. Fixing this at `canonicalize_lexical` rather than at the ESP32 call site also covers the other sites that build signatures the same way — `refresh_command_hashes` and `needs_rebuild_with_signature` run the same per-flag normalization. Measured (3 interleaved cold trials each, median, true cache misses): wall 5.96s -> 4.81s unphased 709ms -> 218ms The saving overlaps PR #1543 (fewer include dirs means fewer realpath calls too): on top of that change it is a further 3.97s -> 3.79s. The core-cache key is unchanged by this commit (`41b106e21c2b` before and after), which is the point: memoizing a pure function must not move the cache. Refs #1537
|
Warning Review limit reachedNext included review available in 53 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Repository: FastLED/fbuild/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Part of #1537 (cause 3). ESP32-S3 builds spend ~0.7 s in work that no perf phase brackets, between the
framework-libs cacheandframework core cachelog lines. It is cache-state independent — identical on warm and cold builds — which is why it never showed up as a phase.Root cause
Building the framework-core cache key normalizes every path-bearing compiler flag of every core source.
path_arg_for_compile_cwd(crates/fbuild-core/src/path.rs) canonicalizes both the path and the compile cwd on every call:core_cache_key(crates/fbuild-build-engine/src/framework_core_cache.rs) callscompiler.artifact_cache_signature(...)once per core source, and the ESP32 compiler's signature includes the full include list. So per build:The cwd is the same value in all 35,420 calls, and the include dirs are the same 385 for every source — ~386 distinct resolutions repeated 35,000 times, single-threaded, on the hot path of every build.
The same per-flag normalization also runs in
refresh_command_hashesandneeds_rebuild_with_signature, so this is not ESP32-specific.Fix
Memoize
canonicalize_lexical's successfulrealpathresults only.The parent-plus-filename fallback is deliberately not memoized: that is the branch that answers for paths that do not exist yet, so caching it would keep reporting a directory as missing for the rest of the process and a not-yet-created include dir would never resolve. A memo hit is therefore always a path that existed and was resolved once. The map is capped at 8192 entries so a long-lived daemon cannot grow it without bound.
Fixing it at
canonicalize_lexicalrather than at the ESP32 call site covers all the signature-building paths at once, rather than only the one that showed up in this profile.Verification
Interleaved cold trials (median of 3, round-robined so machine drift hits every candidate equally, daemon torn down around each trial, and every trial confirmed a genuine framework-core cache miss):
compile-core-variantmainThe framework-core cache key is unchanged (
41b106e21c2bbefore and after) — which is the point: memoizing a pure function must not move the cache, and it does not.The saving overlaps #1543 (fewer include dirs means fewer
realpathcalls too). On top of that change it is a further 3.97s → 3.79s, so this PR is worth landing independently of the order.New tests pin both halves of the contract: a resolved path is memoized, and the missing-parent fallback is not (a directory created after the first call must still resolve correctly on the second).
soldr cargo test -p fbuild-core --lib path::— all passfbuild-pythonlibpython3.13.so.1.0loader error on this box, unrelatedsoldr cargo clippy --workspace --all-targetscleanRefs #1537