Skip to content

Instantly share code, notes, and snippets.

@baronfel
Created August 6, 2026 19:10
Show Gist options
  • Select an option

  • Save baronfel/57fc30de5811fd10c235c8434ef8849c to your computer and use it in GitHub Desktop.

Select an option

Save baronfel/57fc30de5811fd10c235c8434ef8849c to your computer and use it in GitHub Desktop.
NativeAOT CLI size and startup analysis for dotnet/sdk PR #55497

NativeAOT CLI size and startup analysis for dotnet/sdk#55497

PR: dotnet/sdk#55497 — Enable MSBuild-backed commands in the Native AOT CLI

Test setup

  • OS/RID: Windows x64 (win-x64)
  • Configuration: Release
  • PR base: 2e5fa46b2d150671d77cf313930d4de322907118
  • PR head: 3df559618a8d1776b58dd71c3b4c2ab424b46ab8
  • SDK used to build: 11.0.100-preview.5.26302.115
  • NativeAOT compiler/runtime packs: 11.0.0-preview.7.26365.101
  • Each bundle was force-recompiled to prevent incremental output reuse.
  • Bundle size is the native dotnet-aot.dll only. PDB size is excluded.
  • Commands were launched through the NativeAOT dn host with DOTNET_CLI_ENABLEAOT=true.

Bundle size

Optimization Invariant globalization Base PR PR increase
Speed false 20.60 MiB 45.79 MiB +25.18 MiB / 122.2%
Size false 17.16 MiB 36.28 MiB +19.12 MiB / 111.4%
Speed true 20.39 MiB 45.54 MiB +25.15 MiB / 123.3%
Size true 16.97 MiB 36.06 MiB +19.09 MiB / 112.5%

On the PR head:

  • OptimizationPreference=Size saves 9.51 MiB / 20.8% with normal globalization.
  • OptimizationPreference=Size plus invariant globalization saves 9.73 MiB / 21.3% versus the default.
  • InvariantGlobalization=true alone saves only 0.22–0.25 MiB / ~0.6% on Windows.

Native section growth at default settings

PE section Base PR Increase Share of growth
.text 8.43 MiB 18.03 MiB +9.60 MiB 38.1%
.rdata 10.35 MiB 23.45 MiB +13.09 MiB 52.0%
.data 0.82 MiB 1.72 MiB +0.90 MiB 3.6%
.pdata 0.48 MiB 1.08 MiB +0.60 MiB 2.4%
.reloc 0.52 MiB 1.50 MiB +0.99 MiB 3.9%

Most of the increase is code and read-only data, consistent with resolver registration rooting the MSBuild/NuGet dependency closure.

Command startup on the PR head

These are medians from 150 launches per command/configuration. Commands were rotated across three passes to reduce ordering effects. Output was identical across all four configurations.

Command Speed Size Speed + invariant Size + invariant
--version 27.40 ms 40.47 ms 26.28 ms 39.58 ms
--info 55.47 ms 68.87 ms 45.45 ms 58.61 ms
--help 27.79 ms 40.84 ms 26.93 ms 39.93 ms
build --help 27.97 ms 41.36 ms 27.75 ms 40.12 ms
restore --help 28.74 ms 41.89 ms 27.41 ms 40.88 ms
sdk check 108.06 ms 117.27 ms 90.30 ms 106.41 ms

Relative to the default

Command Size Speed + invariant Size + invariant
--version +47.7% -4.1% +44.5%
--info +24.2% -18.1% +5.7%
--help +47.0% -3.1% +43.7%
build --help +47.9% -0.8% +43.4%
restore --help +45.8% -4.6% +42.2%
sdk check +8.5% -16.4% -1.5%

Conclusions

  • The PR more than doubles the native DLL at default settings: 20.60 MiB to 45.79 MiB.
  • OptimizationPreference=Size recovers about 9.5 MiB, but adds a largely fixed ~9–13 ms with normal globalization and ~12–16 ms with invariant globalization in this command matrix.
  • The fixed absolute cost makes the percentage regression look largest for parser-only commands and smaller for commands that do more work.
  • Invariant globalization has little size benefit on Windows, but reduced measured startup for --info and sdk check by roughly 16–18%.
  • --list-sdks and --list-runtimes were excluded because they used managed fallback in the minimal benchmark harness.

These timings are process-launch microbenchmarks on one machine. They are useful for comparing these builds under the same conditions, not as universal absolute startup numbers.

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