Memoize the negative result of the lazy cache getter - #1
Open
CamdenBopp wants to merge 1 commit into
Open
Conversation
`MachOFile.cache` lazily loads the dyld shared cache the file belongs to via `try? DyldCache.init(url:)`, storing the result in `_cache`. For a standalone (non-cache) Mach-O the init fails and `_cache` stays nil, which is indistinguishable from "not yet attempted", so every subsequent `cache` access re-runs the init: another `open()`, mmap and URL-path resolution of the file, only to fail again. A consumer that touches `cache` once per symbol pays this per symbol. Building a Swift symbol index over a ~30 MB standalone binary (~45k local Swift symbols) spent ~8.5s of a 12.8s run re-opening the file as a dyld cache tens of thousands of times, all discarded. Memoize the negative result with a one-shot flag so the failed init runs at most once. `_cache`/`_fullCache` success paths are unchanged; a genuine cache image still resolves and caches on first touch. Behavior is identical, only repeated failed loads are elided. Verified: byte-identical output from a downstream Swift metadata dumper across five fixture dylibs; the same dump on a 30 MB binary went 12.8s -> 4.3s and an interface reconstruction 19s -> 5.1s, with `DyldCache.init` dropping from ~99% of samples to absent in a sampling profile.
CamdenBopp
added a commit
to CamdenBopp/swiftdc
that referenced
this pull request
Sep 12, 2026
dump / interface / analyze on a large on-disk binary were dominated by repeatedly re-opening the dyld shared cache. `MachOFile.cache` lazily tries `DyldCache.init(url:)` and stores the result, but a standalone (non-cache) binary's failed load leaves `_cache` nil, indistinguishable from "not yet attempted", so every access re-runs it. MachOSwiftSection's symbol-index sweep touches `.cache` once per local Swift symbol (~45k on swiftdc itself), so the build spent ~8.5s of a 12.8s dump re-opening and discarding the cache. Fixed upstream as MxIris-Reverse-Engineering/MachOKit#1 (memoize the negative result). Pinned here to CamdenBopp/MachOKit at a fork of the 0.52.102 tag carrying only that change; as the root package this revision requirement overrides MachOSwiftSection's transitive 0.52.101 ..< 0.53.0 range. Revert to an upstream version pin once #1 ships in a release. Measured on swiftdc itself (~30 MB): dump 12.8s -> 4.3s, interface 19s -> 5.1s. Verified: byte-identical dump on all five fixture dylibs; full suite (204 tests) green; DyldCache.init drops from ~99% of profile samples to absent.
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
MachOFile.cachelazily loads the dyld shared cache the file belongs to withtry? DyldCache.init(url:)and stores it in_cache. For a standalone (non-cache) Mach-O the init fails and_cachestaysnil, which is indistinguishable from "not yet attempted" — so every later access tocachere-runs the init: anotheropen(), mmap, and URL-path resolution of the file, only to fail again.A consumer that reads
cacheonce per symbol pays this once per symbol. Indexing the Swift symbols of a ~30 MB standalone binary (~45k local Swift symbols) spent ~8.5s of a 12.8s run re-opening the same file as a dyld cache tens of thousands of times, every attempt discarded. A sampling profile showedDyldCache.init(url:)as ~99% of samples.Fix
Memoize the negative result with a one-shot flag so the failed load runs at most once. The
_cache/_fullCachesuccess paths are untouched; a genuine cache image still resolves and caches on first touch. Only repeated failed loads are elided, so behavior is unchanged.Verification
dump12.8s → 4.3s, interface reconstruction 19s → 5.1s;DyldCache.initdrops from ~99% of profile samples to absent.