Skip to content

Build Multidictionary's reverse index lazily - #2260

Open
jshier wants to merge 1 commit into
swiftlang:mainfrom
jshier:lazy-multidictionary-reverse-index
Open

jshier wants to merge 1 commit into
swiftlang:mainfrom
jshier:lazy-multidictionary-reverse-index

Conversation

@jshier

@jshier jshier commented Nov 9, 2026 •

Copy link
Copy Markdown

Resolves #2259.

Multidictionary has been updated to build the reverse index on the first removeOccurrences(of:) and maintain it from then on. It's stored inline as an optional, so copies keep value semantics. Its behavior is unchanged for every caller.

Alternatively, the reverse index could be built on the first access of keysContainingValues since it's public API, but since that was only accessed in assertions, it would make debug builds pay the cost more often.

Measured results:

Measurement Before After
Deserializing the priors of a generated 2,000-file module (12.4 MB; median of 7 release runs) 0.96 s 0.43 s (−56%)
Incremental planning of the production app, four interleaved runs with the same priors, measured with an earlier version of this change 9.86 to 9.99 s 7.36 to 8.60 s (about −2.4 s, −24%)

In Xcode, this is on the critical path of every edit ("Planning Swift module" took 12.5 s in each one-line-edit build of the app).

Testing: the new MultidictionaryTests cover lookups before the index exists, inserting and removing after it's built, and copies with and without it. Behavior is unchanged, so they also pass without the change. The MultidictionaryTests, ModuleDependencyGraphTests, DependencyGraphSerializationTests and incremental suites pass.

`Multidictionary` maintained a reverse index on every insertion, but
only `removeOccurrences(of:)` needs it (and `keysContainingValue(_:)`,
which only assertions and tests call). Reading the priors re-records
every arc of the dependency graph, so maintaining the index was most of
the time spent reading them: in a large app module, 3.5 of 4.2 CPU-s.

Build the index on the first `removeOccurrences(of:)` and maintain it
from then on. It's stored inline as an optional, so copies keep value
semantics. Equality only compares the forward dictionary, which the
index is derived from.

Reading the priors of a generated 2,000-file module takes 0.43 s instead
of 0.96 s.

Resolves swiftlang#2259

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

Reading incremental priors spends most of its time building an eagerly maintained reverse index

1 participant