Uh oh!
There was an error while loading. Please reload this page.
Inline resource strings in the compiler - #80896
Conversation
ghost
commented
Jan 20, 2023
Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas Issue DetailsContributes to #80165. Allows getting rid of resource manager. Cc @dotnet/ilc-contrib
|
MichalStrehovsky
left a comment
There was a problem hiding this comment.
Pointing out the hacks, but they're quite obvious.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
We now need to inline knowledge about these names. Don't like this one either. Don't see way out.
There was a problem hiding this comment.
How does resource manager find it? In theory this has to be written somewhere in the compiled code, so the compiler could get it from there. But it's probably VERY complicated to do so.
There was a problem hiding this comment.
Resource manager grabs it from a piece of (generated) code. It looks like this:
namespaceFxResources.System.Private.DisabledReflection{internalstaticclassSR{}}namespaceSystem{internalstaticpartialclassSR{privatestaticglobal::System.Resources.ResourceManagers_resourceManager;internalstaticglobal::System.Resources.ResourceManagerResourceManager=>s_resourceManager??(s_resourceManager=newglobal::System.Resources.ResourceManager(typeof(FxResources.System.Private.DisabledReflection.SR)));}}So we'd need to crack open the cctor on the System.SR class and find the type that is passed to resource manager's constructor. That's the name of the resource.
It's not impossible. But also not completely straightforward.
There was a problem hiding this comment.
Hm, but this is all generated by Arcade logic, so we can just change it: https://github.com/dotnet/arcade/blob/083fc0406279952794c853630a08a141eef7244f/src/Microsoft.DotNet.Arcade.Sdk/src/GenerateResxSource.cs
Looks like the extra type is only done to comfort .NET Native (likely also some old version of .NET Native that still did ILMerging base on the comment).
So we can remove the useless type and instead call into the ResourceManager ctor that takes the resource name.
That will be a net improvement everywhere. I like those kinds of changes.
namespace System
{
internal static partial class SR
{
private const string ResourceName = "FxResources.System.Private.DisabledReflection.SR";
private static global::System.Resources.ResourceManager s_resourceManager;
internal static global::System.Resources.ResourceManager ResourceManager => s_resourceManager ?? (s_resourceManager = new global::System.Resources.ResourceManager(typeof(SR).Assembly), ResourceName);
}
}
There was a problem hiding this comment.
Nice - although in probably won't help that much to detect it from the compiler. We would still have to understand the .cctor and alike. I'd say keep the current "hardcoded" version and file a bug that this could be improved by analyzing the static .cctor. Even that would still rely on just specific patterns, but it would more robust.
There was a problem hiding this comment.
We'd be looking for the const string - that one is easy to find and can be just part of the protocol.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Would need to stop delaying grabbing the resource string. I'm not sure this one is on the hello world path, but we'd need to audit everything.
marek-safar
commented
Jan 20, 2023
Does it make sense to optimize via "hacks" for hello world? |
MichalStrehovsky
commented
Jan 20, 2023
Looking for ways to make it non-hacky! That's why this is a draft. In .NET 7 we shipped NativeAOT for command line apps scenarios - lots of command line apps can go by without ever touching resource manager. For a small command line utility, this can be a 40% saving (this is bringing hello world to 1.6 MB by default without any other feature switches or optimization, just |
am11
commented
Jan 20, 2023
Can linker help with inlining of string resources more broadly? Perhaps with some existing criteria, e.g. |
vitek-karas
commented
Jan 20, 2023
Linker could in theory do this as well - as a middle ground between full resources and resource keys only. The priority in linker is lower though because the size saving is relatively smaller (the constant 10MB of the native runtime outweighs any managed code in the app for small apps). That said we should design this such that the same technique could be used in the linker as well. |
2b51b7b to
22d8052CompareMichalStrehovsky
commented
Jan 25, 2023
Pushed out a new iteration. This addresses the caching issue. The optimization now also gracefully falls back if someone used |
| private static string GetDisplayName(int codePage) | ||
| { | ||
| string? displayName = SR.GetResourceString("Globalization_cp_" + codePage.ToString()); | ||
| if (string.IsNullOrEmpty(displayName)) |
There was a problem hiding this comment.
I think this was dead code. The only situation when GetResourceString could return a null is if we failed to find the manifest resource blob.
I traced this back to dotnet/corert@db4c4e1 "this implementation does not try to find resource strings for EncodingName, because ProjectN does not support this yet". 6 years passed. We added support at some point.
Uh oh!
There was an error while loading. Please reload this page.
MichalStrehovsky
commented
Jan 26, 2023
Goldilocks before 16,486,400 bytes. Goldilocks after 16,224,768 bytes (1.6% savings). Hello world before ~2.5 MB. Hello world after 1,857,536 bytes (massive% savings). |
VSadov
commented
Jan 26, 2023
The change in HelloWorld size is quite impressive |
We can now optimize away the assembly even when resource strings are in use.
MichalStrehovsky
commented
Jan 27, 2023
/azp run runtime-extra-platforms |
|
Azure Pipelines successfully started running 1 pipeline(s). |
MichalStrehovsky
commented
Jan 27, 2023
@dotnet/ilc-contrib could someone have a look? On a high level:
|
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
| // Do not attempt to inline resource strings if we only want to use resource keys. | ||
| // The optimizations are not compatible. | ||
| bool shouldInlineResourceStrings = | ||
| !_hashtable._switchValues.TryGetValue("System.Resources.UseSystemResourceKeys", out bool useResourceKeys) || !useResourceKeys; |
There was a problem hiding this comment.
Any estimate on additional saving we would get if this feature also worked with UseSystemResourceKeys feature switch (some of the message are very long)?
There was a problem hiding this comment.
Any estimate on additional saving we would get if this feature also worked with
UseSystemResourceKeysfeature switch (some of the message are very long)?
This will always be inlined - for the below app:
Console.WriteLine(newNullReferenceException().Message);thrownull;If I compile this with .NET 7 as dotnet publish -c Release /p:PublishAot=true /p:UseSystemResourceKeys=true and step into the ctor, I see this (the first lea is loading the string literal of the resource key - inlined from the accessor):
00007FF787207950 56 push rsi 00007FF787207951 48 83 EC 20 sub rsp,20h 00007FF787207955 48 8B F1 mov rsi,rcx 00007FF787207958 48 8D 0D 11 7B 17 00 lea rcx,[__Str_Arg_NullReferenceException (07FF78737F470h)] 00007FF78720795F E8 0C C2 00 00 call S_P_CoreLib_System_SR__GetResourceString (07FF787213B70h) 00007FF787207964 C7 46 48 00 15 13 80 mov dword ptr [rsi+48h],80131500h 00007FF78720796B 48 8D 4E 08 lea rcx,[rsi+8] 00007FF78720796F 48 8B D0 mov rdx,rax 00007FF787207972 E8 89 BE F4 FF call RhpAssignRefAVLocation (07FF787153800h) 00007FF787207977 C7 46 48 01 15 13 80 mov dword ptr [rsi+48h],80131501h 00007FF78720797E C7 46 48 03 40 00 80 mov dword ptr [rsi+48h],80004003h 00007FF787207985 48 83 C4 20 add rsp,20h 00007FF787207989 5E pop rsi 00007FF78720798A C3 ret SR__GetResourceString didn't get inlined probably because the IL is messed up with too many nops and looks large even thought it isn't. It looks like this and there's only one of it, so not a huge deal size-wise, but we could leave fewer nops when substitutions happen to not to throw off inlining heuristics:
00007FF787213B70 48 8B C1 mov rax,rcx 00007FF787213B73 C3 ret marek-safar
commented
Jan 27, 2023
It might be worth enabling BannedAPIAnalyzet for this. |
MichalStrehovsky
commented
Jan 27, 2023
Yes, we definitely have a couple calls to these for no good reason! |
MichalStrehovsky
commented
Jan 29, 2023
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
This reverts commit 759fecb.
Contributes to #80165.
Allows getting rid of resource manager.
Cc @dotnet/ilc-contrib