You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
perf(esp32): library-selection/LDF resolver is uncached on ESP32 while RP2040/Teensy/STM32 use the cached path #1539
ESP32's orchestrator resolves the project's library-selection / LDF result through the uncached path, while RP2040, Teensy and STM32 all go through the cached one. A library-selection cache store already exists on disk and is populated by other platforms; ESP32 never reads it.
The cached entry point lives in crates/fbuild-build-engine/src/framework_libs.rs alongside the uncached one, so this looks like an incomplete migration rather than a deliberate ESP32 choice.
Why it matters
The LDF pass is a header-scan + dependency resolution over every candidate library. On bench/blink it resolves 0 libraries and is under 1 ms, so it does not show up in today's ESP32 numbers — but it is the one platform where the cost is unamortized, and it is exactly the platform with the largest library surface (Arduino core + ESP-IDF bundled libraries + the framework's own libraries/). On a real FastLED sketch with several lib_deps, this is a per-build cost that other platforms already avoid.
A populated ~/.fbuild/prod/cache/library-selection store (~24 MB observed) exists on a machine that only builds ESP32, which suggests the ESP32 path used to be cached, or that another code path populates it.
Suspected fix
Switch the ESP32 call site to resolve_framework_library_sources_cached (matching the other three platforms), keeping the existing ESP32-specific inputs — or document why ESP32 must stay uncached.
Resolved by #1551 and #1554, which went after the measured cost rather than bolting ESP32 onto the cross-build selection cache.
The uncached ESP32 path was not just unamortized; it was pathological. With ~400 ESP32 search paths, walker::resolve_includeis_file()-probed every path for every include reference, across four walks per build (the defined-macro pre-walk also ran twice). A perf profile of an incremental FastLED build put 67% of daemon CPU there.
Library selection in an incremental FastLED DemoReel100 esp32s3 build (sketch edit):
selection time
before
~17 s
#1551: memoize each name's resolution; share the resolver across walks; drop the duplicate walk
1.2 s
#1554: index the search roots so misses stop probing every path
0.23 s
The selected set is the same (FS, I2S, LittleFS, SPI, WiFi), and the whole incremental build went from ~25 s to ~6–7 s.
I deliberately did not adopt resolve_framework_library_sources_cached for ESP32. Its key hashes seed contents but not the headers those seeds reach. On ESP32, FastLED's sources are seeds and its headers change constantly, so a hit could replay a stale selection. At 0.23 s the remaining cost doesn't justify that risk.
Activity
perf(ldf): resolve each include name once per walk instead of per ref…
perf(ldf): index search-path roots so include misses stop probing eve…
zackees commentedon Sep 28, 2026
Resolved by #1551 and #1554, which went after the measured cost rather than bolting ESP32 onto the cross-build selection cache.
The uncached ESP32 path was not just unamortized; it was pathological. With ~400 ESP32 search paths,
walker::resolve_includeis_file()-probed every path for every include reference, across four walks per build (the defined-macro pre-walk also ran twice). Aperfprofile of an incremental FastLED build put 67% of daemon CPU there.Library selection in an incremental FastLED DemoReel100 esp32s3 build (sketch edit):
The selected set is the same (FS, I2S, LittleFS, SPI, WiFi), and the whole incremental build went from ~25 s to ~6–7 s.
I deliberately did not adopt
resolve_framework_library_sources_cachedfor ESP32. Its key hashes seed contents but not the headers those seeds reach. On ESP32, FastLED's sources are seeds and its headers change constantly, so a hit could replay a stale selection. At 0.23 s the remaining cost doesn't justify that risk.