Avoid HashSet allocations for singleton conditional dependencies - #132934
Open
awakecoding wants to merge 1 commit into
Open
Avoid HashSet allocations for singleton conditional dependencies#132934awakecoding wants to merge 1 commit into
awakecoding wants to merge 1 commit into
Conversation
Store the first pending conditional dependency directly in a small reference-backed bucket. Promote to a HashSet only when a second distinct dependency is added, avoiding per-bucket HashSet storage for the common singleton case.
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
|
Tagging subscribers to this area: @agocke, @dotnet/ilc-contrib |
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
Pull request overview
This PR optimizes NativeAOT’s dependency analysis by avoiding a HashSet<CombinedDependencyListEntry> allocation for the common case where a condition only accumulates a single pending conditional dependency, while preserving existing deduplication and replay semantics.
Changes:
- Replaces the conditional-dependency dictionary value from
HashSet<CombinedDependencyListEntry>to a small reference-backedConditionalDependencyBucketthat stores the first entry inline and promotes toHashSeton the second distinct add. - Uses
Dictionary.Remove(key, out value)to remove-and-replay stored conditional dependencies in one lookup when a condition node becomes marked. - Adds focused unit tests covering singleton vs promoted buckets, deduplication (including across promotion), owner/condition identity semantics, deferred dependency computation, and replay behavior.
File summaries
| File | Description |
|---|---|
| src/coreclr/tools/aot/ILCompiler.DependencyAnalysisFramework/DependencyAnalyzer.cs | Introduces a singleton-then-promote bucket to reduce per-condition allocations and replays stored dependencies when conditions are satisfied. |
| src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs | Adds regression tests validating conditional dependency storage, deduplication, replay, and ordering/identity invariants. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 0
- Review effort level: Lite
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.
Summary
HashSetonly when a second distinct dependency is added.Motivation
DependencyAnalyzercurrently allocates aHashSet<CombinedDependencyListEntry>for everycondition that has a pending conditional dependency, even when that condition has only one
dependency.
In a full Remote Desktop Manager NativeAOT compilation using the matching .NET 10.0.11
toolchain, the analyzer created 5,724,010 conditional buckets, but only 1,189,313 (20.78%)
needed more than one distinct entry. A reference-backed singleton representation reduced
managed allocation by 0.66-0.71 GiB in both measured runs. An earlier value-type prototype
was rejected because copying the large dictionary value erased the timing benefit; keeping
the bucket behind a reference avoids those dictionary-value copies.
Implementation
The dictionary key and its equality behavior are unchanged. Each dictionary value is now a
small
ConditionalDependencyBucketobject:CombinedDependencyListEntryis stored directly.HashSet.HashSetcontaining both entries.dependencies.
The change does not alter conditional dependency production, graph sorting, profiling,
capacity policy, or any other NativeAOT optimization.
Validation
ILCompiler.Compiler.Tests: 33/33 passed in Release.ILCompiler.Compiler.Tests: 33/33 passed in Debug.B09CDFB966C0306D667EEA682A54D8D71816ACB6CA2F5487952D66B65877CE02.Focused analyzer tests cover singleton and promoted buckets, duplicate entries before and
after promotion, distinct owner nodes, condition-first and dependency-first marking,
already-marked dependencies, null conditions, deferred dependency computation, replay and
removal, condition identity, and distinct entries across promotion. The NativeAOT determinism
test and current-main A/B output hashes cover ordering and output stability.
Current-main stress benchmark
The benchmark used current
mainatc210d82dbc1ab432b9369604a1caef9a0ab763d2.It constructed 2,000,000 pending conditional buckets and promoted 415,552 (20.7776%), matching
the full-RDM promotion ratio. Owners were marked before a trigger marked every condition.
Baseline and changed analyzer assemblies were run in alternating order for 12 measured pairs
after one warmup per variant.
Every measured run produced the same 25,662,216-byte logical marked-node output with
SHA-256
03357BB1EB8FE3A673546DA58681BD6EB651E5799330BD1908508B02BEECB9D5.Managed allocation fell by 246.91-246.93 MiB in every pair. Timing was much noisier:
individual paired graph deltas ranged from -38.26% to +63.35%. The median timing result is
directionally favorable, but the repeatable allocation reduction and output identity are
the primary current-main evidence.
Retained full-application evidence
The separate .NET 10.0.11 RDM experiment observed:
C71AA7E9BAC37D9F2A2B2E2D75C695947D70CBD4EE39464C3623C4C9A8B75668.9.12 seconds, and total time by 9.98 seconds.
The first RDM timing run was frequency-confounded, and peak private memory varied with GC
timing (the confirmation was higher). Those results are retained as supporting historical
evidence rather than treated as current-main timing proof.
Note
This pull request description was drafted with GitHub Copilot.