Skip to content

Avoid boxing NativeAOT dependency enumerators - #132922

Open
awakecoding wants to merge 1 commit into
dotnet:mainfrom
awakecoding:copilot/nativeaot-dependency-iteration
Open

Avoid boxing NativeAOT dependency enumerators#132922
awakecoding wants to merge 1 commit into
dotnet:mainfrom
awakecoding:copilot/nativeaot-dependency-iteration

Conversation

@awakecoding

Copy link
Copy Markdown

Summary

  • Enumerate known concrete static and conditional dependency collections directly instead of through IEnumerable<T>.
  • Keep the generic interface-enumeration fallback for all other collection types.
  • Preserve custom List<T> subclass enumeration and list mutation detection by restricting the fast path to exact known types and using the concrete List<T>.Enumerator.

Motivation

Dependency analysis exposes dependencies as IEnumerable<T>, but the hot paths commonly receive DependencyList, List<CombinedDependencyListEntry>, or arrays. Enumerating those values through the interface boxes collection enumerators.

Profiling a large production NativeAOT desktop application found:

Collection Lists Entries
Static 28.536 million 96.861 million
Conditional 6.715 million 73.188 million

Against the midpoint of adjacent controls, direct concrete iteration reduced both graph and mark time by about 7.3 seconds and managed allocation by 1.35-1.39 GiB. The generated object was byte-identical. This was the strongest repeatable compiler wall-phase improvement among 15 tested hypotheses.

Implementation

DependencyAnalyzer now recognizes the exact built-in list types and dependency-entry arrays before falling back to the existing IEnumerable<T> loops. Exact-type guards ensure a List<T> subclass that reimplements IEnumerable<T> retains its custom behavior.

The list paths intentionally use the concrete struct enumerator rather than CollectionsMarshal.AsSpan. This removes boxing while retaining the existing List<T> version checks if an OnMarked callback mutates the collection during enumeration.

Conditional dependency handling is extracted into a shared helper without changing null-condition, already-marked, or deferred-condition behavior.

Validation

  • .\build.cmd clr.aot+libs -rc Release -lc Release
  • .\dotnet.cmd test src\coreclr\tools\aot\ILCompiler.Compiler.Tests\ILCompiler.Compiler.Tests.csproj -c Release -p:Platform=x64
    • 68 passed, including 46 new cases for arrays, exact lists, DependencyList, iterator fallback, custom List<T> enumeration, empty/single/multiple entries, immediate/deferred/null conditions, ordering, and mutation detection
  • .\src\tests\build.cmd nativeaot Release tree nativeaot
  • .\src\tests\run.cmd runnativeaottests Release
    • 28 passed

Current-main benchmark

I did not reuse the retained RDM response because it is pinned to net10.0, WindowsDesktop 10.0.11, and the matching .NET 10 framework closure, while current main builds a net11.0 compiler and framework. Mixing those contracts would not be a valid comparison.

Instead, an ignored local runner generated a current-main net11.0 workload with 10,000 worker/marker type pairs, used the current-main ILC response shape, and ran two interleaved batches after warmup. Both compilers contained the same temporary measurement probes; no probes or experiment gates are in this change.

Metric Batch 1 baseline Batch 1 changed Batch 2 baseline Batch 2 changed Median paired change, 12 pairs
Wall time 8.34 s 7.26 s 5.81 s 5.92 s -0.52%
CPU time 17.95 s 16.36 s 15.27 s 14.84 s -5.18%
Graph time 4.81 s 3.89 s 3.44 s 3.50 s -1.98%
Mark time 2.35 s 1.63 s 1.57 s 1.59 s -2.81%
Graph allocation 851.69 MiB 832.84 MiB 854.63 MiB 835.01 MiB -19.86 MiB (-2.33%)
Peak private memory 845.20 MiB 731.47 MiB 734.32 MiB 726.29 MiB -1.62%

Short-run wall and phase measurements were sensitive to machine load; the consistently repeated signal was lower graph allocation. The full-scale profile above provides the stronger graph/mark timing evidence.

Every same-path baseline/changed pair produced a byte-identical 39,922,334-byte object. Batch 1 used SHA-256 5CAE603CDA1F795CFF49DEC7B9BAEFDDF2422C5D33A0CD52E49E229F7F21353E; batch 2 used D8BCA75F1759EA5FF0B3E585CAA4C14F81A5ED04165044A2DC5063712880CE97. Identity is evaluated within each batch because the response and output paths differ between batch roots.

Note

This PR description was drafted with GitHub Copilot.

Enumerate known concrete dependency lists and arrays directly while preserving custom enumerable and mutation semantics.

Add focused coverage for collection shapes, ordering, conditional dependencies, and mutation.
Copilot AI lite review requested due to automatic review settings August 29, 2026 17:15
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Aug 29, 2026
@azure-pipelines

Copy link
Copy Markdown
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.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke, @dotnet/ilc-contrib
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

Pull request overview

This PR updates the NativeAOT dependency analysis “mark” walk to avoid allocator churn when static/conditional dependencies are provided as common concrete collections but exposed via IEnumerable<T>.

Changes:

  • Adds fast paths in DependencyAnalyzer.GetStaticDependenciesImpl for exact DependencyList, exact List<T>, and arrays to avoid boxing/allocation of enumerators.
  • Extracts conditional dependency handling into a shared helper (ProcessConditionalDependency) while keeping existing conditional/deferred semantics.
  • Adds targeted unit tests covering ordering, fallback enumeration behavior (including List<T> subclasses that reimplement IEnumerable<T>), and mutation detection.
File summaries
File Description
src/coreclr/tools/aot/ILCompiler.DependencyAnalysisFramework/DependencyAnalyzer.cs Adds exact-type fast paths for list/array dependency enumeration and factors conditional dependency handling into a helper.
src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj Includes the new DependencyAnalyzerTests.cs in the explicit compile item list.
src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyAnalyzerTests.cs Adds coverage for ordering, fallback enumeration semantics, null/unconditional condition handling, and mutation detection.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Lite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-NativeAOT-coreclr community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants