[assembly-preparer] Skip re-serializing assemblies that weren't modified.#25971
[assembly-preparer] Skip re-serializing assemblies that weren't modified.#25971rolfbjarne wants to merge 1 commit into
Conversation
…ied. SaveAssembliesStep used to Cecil-Write every assembly whose action is Link or Save, which in a trimming build re-serializes every assembly in the trim set even though the assembly-preparer didn't modify them (the trimming is done later by ILLink). Now assemblies that weren't modified are output as-is - the original file is referenced during preparation, or copied during post-processing so all assemblies end up in the same directory for AOT compilation - instead of being re-serialized. The modified assemblies are collected in a set on the LinkerConfiguration from the AppBundleRewriter.AssemblySaved callback. Note we can't just encode "modified" in the assembly action (i.e. switch Link to Save when an assembly is modified and then only re-serialize Save assemblies), because AssemblyAction.Link is used as a gating condition by many steps (their IsActiveFor checks 'GetAction (assembly) == Link'); changing a modified assembly's action from Link to Save mid-pipeline would make later Link-gated steps skip it. For monotouch-test (ios-arm64, SdkOnly) this cut the number of Cecil writes from ~170 to 14 and roughly halved the time spent in SaveAssembliesStep. Verified with the monotouch-test suite (both 'release|linksdk' and link None, with PrepareAssemblies=true): all tests pass. Before build times: | Project | Runtime identifier | Link mode | PrepareAssemblies=false | PrepareAssemblies=true | Difference | | -------------- | ------------------ | --------- | ----------------------- | ---------------------- | ----------- | | monotouch-test | ios-arm64 | None | 00:02:04.03 | 00:02:05.67 | 00:00:01.64 | | monotouch-test | ios-arm64 | SdkOnly | 00:01:17.47 | 00:01:43.61 | 00:00:26.14 | | monotouch-test | iossimulator-arm64 | None | 00:01:37.04 | 00:01:38.26 | 00:00:01.21 | | monotouch-test | iossimulator-arm64 | SdkOnly | 00:01:09.96 | 00:01:19.42 | 00:00:09.46 | | MySimpleApp | ios-arm64 | None | 00:01:12.77 | 00:01:15.47 | 00:00:02.70 | | MySimpleApp | ios-arm64 | SdkOnly | 00:00:20.12 | 00:00:47.29 | 00:00:27.17 | | MySimpleApp | iossimulator-arm64 | None | 00:00:56.20 | 00:00:55.54 | 00:00:00.66 | | MySimpleApp | iossimulator-arm64 | SdkOnly | 00:00:19.68 | 00:00:31.96 | 00:00:12.27 | After build times: | Project | Runtime identifier | Link mode | PrepareAssemblies=false | PrepareAssemblies=true | Difference | | -------------- | ------------------ | --------- | ----------------------- | ---------------------- | ----------- | | monotouch-test | ios-arm64 | None | 00:02:05.92 | 00:02:07.87 | 00:00:01.94 | | monotouch-test | ios-arm64 | SdkOnly | 00:01:22.09 | 00:01:45.01 | 00:00:22.92 | | monotouch-test | iossimulator-arm64 | None | 00:01:36.61 | 00:01:37.47 | 00:00:00.85 | | monotouch-test | iossimulator-arm64 | SdkOnly | 00:01:10.02 | 00:01:14.54 | 00:00:04.51 | | MySimpleApp | ios-arm64 | None | 00:01:14.37 | 00:01:16.49 | 00:00:02.11 | | MySimpleApp | ios-arm64 | SdkOnly | 00:00:20.63 | 00:00:42.59 | 00:00:21.96 | | MySimpleApp | iossimulator-arm64 | None | 00:00:53.89 | 00:00:54.73 | 00:00:00.83 | | MySimpleApp | iossimulator-arm64 | SdkOnly | 00:00:19.67 | 00:00:27.25 | 00:00:07.58 | Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
This PR optimizes the assembly-preparer pipeline by avoiding unnecessary Mono.Cecil re-serialization for assemblies that were not actually modified by assembly-preparer steps (even if they’re in the trim set). Instead, unmodified assemblies are forwarded as-is during preparation (by referencing the original path) and copied during post-processing when needed to co-locate assemblies for AOT.
Changes:
- Track which
AssemblyDefinitions were actually modified viaAppBundleRewriter.AssemblySavedand store them onLinkerConfiguration. - Update
SaveAssembliesStepto skip CecilWriteforLink/Saveassemblies not present in the modified set, outputting them without rewriting. - Extend the assembly-preparer step runner to populate the modified-assembly set while still recording per-step “modified any assemblies” timing info.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| tools/dotnet-linker/LinkerConfiguration.cs | Adds a ModifiedAssemblies set to track which assemblies were actually changed and therefore require serialization. |
| tools/assembly-preparer/SaveAssembliesStep.cs | Skips Cecil write for unmodified Link/Save assemblies; factors “output without rewriting” into a helper for both Copy* and unchanged Link/Save cases. |
| tools/assembly-preparer/AssemblyPreparer.cs | Hooks AppBundleRewriter.AssemblySaved to both mark the current step as modifying assemblies and record the specific modified assembly into configuration state. |
✅ API diff for current PR / commitNET (empty diffs)✅ API diff vs stableNET (empty diffs)ℹ️ Generator diffGenerator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes) Pipeline on Agent |
🔥 [CI Build #eda01c0] Test results 🔥Test results❌ Tests failed on VSTS: test results 0 tests crashed, 1 tests failed, 198 tests passed. Failures❌ monotouch tests (iOS)1 tests failed, 17 tests passed.Failed tests
Html Report (VSDrops) Download Successes✅ assembly-processing: All 1 tests passed. Html Report (VSDrops) Download macOS tests✅ Tests on macOS Monterey (12): All 5 tests passed. Html Report (VSDrops) Download Linux Build VerificationPipeline on Agent |
SaveAssembliesStep used to Cecil-Write every assembly whose action is Link or Save,
which in a trimming build re-serializes every assembly in the trim set even though
the assembly-preparer didn't modify them (the trimming is done later by ILLink). Now
assemblies that weren't modified are output as-is - the original file is referenced
during preparation, or copied during post-processing so all assemblies end up in the
same directory for AOT compilation - instead of being re-serialized.
The modified assemblies are collected in a set on the LinkerConfiguration from the
AppBundleRewriter.AssemblySaved callback. Note we can't just encode "modified" in the
assembly action (i.e. switch Link to Save when an assembly is modified and then only
re-serialize Save assemblies), because AssemblyAction.Link is used as a gating
condition by many steps (their IsActiveFor checks 'GetAction (assembly) == Link');
changing a modified assembly's action from Link to Save mid-pipeline would make later
Link-gated steps skip it.
For monotouch-test (ios-arm64, SdkOnly) this cut the number of Cecil writes from ~170
to 14 and roughly halved the time spent in SaveAssembliesStep. Verified with the
monotouch-test suite (both 'release|linksdk' and link None, with PrepareAssemblies=true):
all tests pass.
Before build times:
After build times: