Defer unused CoreLib cache initialization - #132576
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
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. |
|
Tagging subscribers to this area: @dotnet/area-system-globalization |
There was a problem hiding this comment.
Pull request overview
This PR defers initialization of two rarely-needed CoreLib caches by moving them behind nested static holder types, allowing NativeAOT trimming to omit the cache initialization (and related dependencies) when those paths aren’t used.
Changes:
- Move the
EncodingTablename→codepageConcurrentDictionaryinto a nested static holder to avoid initializing it unlessGetCodePageFromNameis called. - Move the ICU-only
CompareInfoSearchValues<char>initialization into a nested static holder to avoid creating it unless the ASCII fast-path is exercised.
Show a summary per file
| File | Description |
|---|---|
| src/libraries/System.Private.CoreLib/src/System/Text/EncodingTable.cs | Defers ConcurrentDictionary<string,int> construction until GetCodePageFromName is invoked. |
| src/libraries/System.Private.CoreLib/src/System/Globalization/CompareInfo.Icu.cs | Defers ICU ASCII SearchValues<char> creation until the ordinal helper methods need it. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 0
- Review effort level: Lite
|
A sibling to #132550. Just did some local analysis for obvious static constructor roots that looked unnecessary -- CC. @MichalStrehovsky, @jkotas, @agocke this is a trivial improvement that's likely worth taking for .NET 11/12, but it also feels like a more general issue and is a really common pattern in how we and the broader ecosystem write code. This is likely worth explicitly handling in the linker, either via some explicit trimming support or a way (maybe even an attribute) to indicate we support it being "outlined" by the tooling into such helper shapes. We shouldn't have to explicitly think that every unique |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Updated everything and savings measured locally are still the same. |
I don't know what we could do here. Roslyn dumps these inline into the static constructor so we'd need to determine the field is never read (easy enough) and then: determine this is indeed side effect free ( We could replace stsfld with pop and hope RyuJIT determines this is side effect free, but it likely won't help with anything that is interesting (like here). |
|
Nice! Size statistics
|
If nothing else, we should have an (likely opt-in) analyzer for this type of thing and flag it as something that can root code unnecessarily. But, it could also be something we need to think around and discuss across the language, tooling, and libraries. For example, maybe this just ends up as some There's a lot that could be done here to help avoid these kinds of issues being introduced, even accidentally in the future; and ideally something minimal that can be done so that idiomatic code patterns aren't pessimizing trimability of the ecosystem. -- I'm not saying its going to be easy, just that I think this is a representative example of how impactful a handful of lines can be and so its worth spending some time looking into it deeper, as I imagine there are multiple similar cases in real world apps where we can save multiple KB more. |
|
This is not just about trimming. The trimming issues have large overlap with startup time issues. If you have a static constructor that does unnecessary work eagerly, it is both trimming and startup time problem. |
|
For sure. The general idea is that people are going to keep writing the "idiomatic" code, we have lots of it ourselves. So either an analyzer telling them "hey this is actually bad" or some tooling, language feature, etc that "fixes" things for them to be "friendly" should exist so that we can push users (and ourselves) into the pit of success here. We have source generators, we have IL rewriters, we have analyzers, interceptors, etc. Lots of tools available and I'm sure amongst ourselves we can come up with something to help. One could even imagine that Options exist and we have enough experts to find a good path forward, whatever that might be. |
Moves the ICU-only
CompareInfosearch values and theEncodingTablename cache behind nested static holders. This preserves one-time initialization when those paths are used while allowing NativeAOT to trim them from applications that only need unrelated static state.A locally built Release NativeAOT
win-x64Hello World withInvariantGlobalization=truemeasured:mainCompareInfoholderThe cumulative raw section changes are
.text-12,800 bytes,.rdata-9,728 bytes,.data-1,024 bytes,.pdata-512 bytes, and.reloc-512 bytes. The final dependency graph no longer containsSearchValues.Create,EncodingTable::.cctor, or theConcurrentDictionary<string, int>name-cache construction.Note
This pull request was prepared with GitHub Copilot.