-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Size of WinForms with PublishAot #9911
Comments
Also worth noting that including |
@JeremyKuhne and @lonitra are the wizards focusing on Winforms AOT for .NET9 Is the issue with the design attributes because they live in the design assembly? So it pulls in the whole thing? |
Thanks for the details @MichalStrehovsky!
I had no idea the strings would resolve during compilation. Do you know precisely how this happens? Maybe we could get a feature that is more granular that would allow us to ignore specific usages wherever this is being figured out? Note that we're currently focused on getting the key COM pieces converted to |
The reason why this happens is that the attributes have an annotation instructing trimming to keep things on the types: [TypeConverter("System.Windows.Input.CommandConverter, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, Custom=null")]
public interface ICommand
{
}
public sealed class TypeConverterAttribute : Attribute
{
public TypeConverterAttribute([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] string typeName) { }
} The annotation on the We could ask trimming to drop all TypeConverters, but I assume WinForms actually use type converter and do require this trimming behavior. It just might not be desirable here. Cc @vitek-karas @sbomer for ideas |
We don't pull in whole assemblies (unless something forces it, like setting The Sizoscope tool is really good at showing the paths to roots for these, try it - it's a WinForms app. |
For the designer related attributes: If we can make an assumption that these attributes are not useful when running the app, then there are ways to remove all of those attributes (and what they depend on along with them). For the type converter: I can't think of a way to solve this super cleanly. There is a direct dependency all the way to all of the WPF functionality. Typically, the only way we can break such a chain is through feature switches - similar to what Michal used to get rid of the networking stack. But that depends on usability - if the code dependency is there, but in reality almost no WinForms app actually runs that code, then maybe a feature switch would be a solution. I fear TypeConverter as I know that it's used a lot in WinForms - and it's not very trimming friendly. |
There are some apps that use the designers at runtime. So it would have to be behind a switch, unless the team decided that designers are not supported in AOT scenarios. Could |
It's not about what assembly Clicking through the dependency graph in Sizoscope might help building intuition around this. Here I double clicked why the It doesn't even fit in a single screen so this is just a part. But basically CommandConverter implements ConvertFrom virtual method and since that one is called, we need ConvertFromHelper, that calls GetKnownCommand,... then suddenly we need XamlReader and that has built in support for a bunch of controls. |
But yes, the attributes are there, but we're actually not using them in the runtime context (and also not in the Designer context, because those would not work, anyway.) But apart from that: We will be enabling the |
The custom attribute is harmless if the project being built doesn't provide a way to resolve the PresentationFramework assembly. WinForms is inflicting it on itself by being packaged together with WPF. Granted it would be better if the ICommand interface wasn't annotated in a way that violated layering/separation of concerns but I don't know if anything can be done about that |
If we look at it from the other direction, WPF would require these attributes so that trimming/AOT works for them right? So we can't remove the annotations without breaking AOT for them? |
The attribute is in the official docs: https://learn.microsoft.com/en-us/dotnet/api/system.windows.input.icommand?view=net-7.0. The official docs make it seem this is a WPF-specific interface with some special behavior in WinRT with no mention of any other use for this interface. |
The dependency on it was added in #4895 to enable modernized model binding. No work was done to decouple it from |
I distinctly remember chatting with @KlausLoeffelmann mentioning that we're bringing the WPF stuff in... :) WRT: WPFt-rimming - why not make an assumption here - if no WRT: attributes/typeconverter trimming - the |
/cc: @kant2002 |
That sounds reasonable. As soon as someone drags |
@vitek-karas How difficult would it be to add configuration to ignore resolution of specified types? Say, if we indicated that we don't want |
Can we fix the Sdk to not pass the WPF assembly references if UseWpf is not specified? This would fix the problem too and work exactly the same between publish and F5 debug. Trimming flags that cause differences in behavior between F5 debug and a published app (without generating a warning about it at publish time) are typically reserved as last ditch effort when nothing else can work. Users don't like when that happens. |
This could likely also fix another issue I didn't mention: an empty WinForms app published with PublishAot also dumps 5 native DLLs into publish output (D3DCompiler_47_cor3.dll, PenImc_cor3.dll, PresentationNative_cor3.dll, vcruntime140_cor3.dll, wpfgfx_cor3.dll). I assume these are all WPF. They are doubling the size of a NativeAOT publish (provided we can fix all the issues discussed in top-post) - while the WinForms app can be 7 MB in size in theory, all of these together cost another 8 MB. |
So there are winforms changes and SDK changes required here? Do we have an SDK tracking issue? |
I didn't file one because the SDK repo backlog is filled with untriaged issues from years ago that nobody is looking at. In my experience the way to get work done in the SDK repo is to submit a pull request. The actual fix might also be in https://github.com/dotnet/windowsdesktop - I'm not quite sure what component decides the assembly references and native files we need. |
I was looking into this and it looks like there is already a mechanism for targeting packs to only reference a subset of the WindowsDesktop files depending on the values of Existing mechanism for targeting packsIn the .NET 8 RC2 SDK's <FrameworkReference Include="Microsoft.WindowsDesktop.App" IsImplicitlyDefined="true"
Condition="('$(UseWPF)' == 'true') And ('$(UseWindowsForms)' == 'true')"/>
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WPF" IsImplicitlyDefined="true"
Condition="('$(UseWPF)' == 'true') And ('$(UseWindowsForms)' != 'true')"/>
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WindowsForms" IsImplicitlyDefined="true"
Condition="('$(UseWPF)' != 'true') And ('$(UseWindowsForms)' == 'true')"/> So there are different In the .NET 8 RC2 SDK's <KnownFrameworkReference Include="Microsoft.WindowsDesktop.App"
TargetFramework="net8.0"
RuntimeFrameworkName="Microsoft.WindowsDesktop.App"
DefaultRuntimeFrameworkVersion="8.0.0-rc.2.23479.10"
LatestRuntimeFrameworkVersion="8.0.0-rc.2.23479.10"
TargetingPackName="Microsoft.WindowsDesktop.App.Ref"
TargetingPackVersion="8.0.0-rc.2.23479.10"
RuntimePackNamePatterns="Microsoft.WindowsDesktop.App.Runtime.**RID**"
RuntimePackRuntimeIdentifiers="win-x64;win-x86;win-arm64"
IsWindowsOnly="true"
/>
<KnownFrameworkReference Include="Microsoft.WindowsDesktop.App.WPF"
TargetFramework="net8.0"
RuntimeFrameworkName="Microsoft.WindowsDesktop.App"
DefaultRuntimeFrameworkVersion="8.0.0-rc.2.23479.10"
LatestRuntimeFrameworkVersion="8.0.0-rc.2.23479.10"
TargetingPackName="Microsoft.WindowsDesktop.App.Ref"
TargetingPackVersion="8.0.0-rc.2.23479.10"
RuntimePackNamePatterns="Microsoft.WindowsDesktop.App.Runtime.**RID**"
RuntimePackRuntimeIdentifiers="win-x64;win-x86;win-arm64"
IsWindowsOnly="true"
Profile="WPF"
/>
<KnownFrameworkReference Include="Microsoft.WindowsDesktop.App.WindowsForms"
TargetFramework="net8.0"
RuntimeFrameworkName="Microsoft.WindowsDesktop.App"
DefaultRuntimeFrameworkVersion="8.0.0-rc.2.23479.10"
LatestRuntimeFrameworkVersion="8.0.0-rc.2.23479.10"
TargetingPackName="Microsoft.WindowsDesktop.App.Ref"
TargetingPackVersion="8.0.0-rc.2.23479.10"
RuntimePackNamePatterns="Microsoft.WindowsDesktop.App.Runtime.**RID**"
RuntimePackRuntimeIdentifiers="win-x64;win-x86;win-arm64"
IsWindowsOnly="true"
Profile="WindowsForms"
/> In the targeting pack's Idea for runtime packThe runtime pack Nuget package has a file called To add the |
Similar issue raised in 2020: #3723 |
@MichalStrehovsky any standouts? I think we have quite a few icons and even more string localizations. |
Yes, that :). You can crossreference what Sizoscope reports with ILSpy on the original managed assembly (Sizoscope doesn't see the contents). |
TrimTest project, which we are tracking as part of WinForms trimming, gives a similar view as above from sizoscope. |
. |
You are right. The 19mb is the size after those dlls being deleted. |
By adding a couple supported switches to the project file, it can shrink down to 9.5 MB (~2.3 MB compressed, since this compresses well). This is on x64; x86 is even smaller. Very cool! <PropertyGroup>
<XmlResolverIsNetworkingEnabledByDefault>false</XmlResolverIsNetworkingEnabledByDefault>
<SatelliteResourceLanguages>en-US</SatelliteResourceLanguages>
<OptimizationPreference>Size</OptimizationPreference>
<InvariantGlobalization>true</InvariantGlobalization>
<StackTraceSupport>false</StackTraceSupport>
<UseSystemResourceKeys>true</UseSystemResourceKeys>
</PropertyGroup>
<ItemGroup>
<!-- We really should introduce a first class property for this -->
<RuntimeHostConfigurationOption Include="System.Windows.Forms.PictureBox.UseWebRequest" Value="false" Trim="true" />
</ItemGroup> |
@MichalStrehovsky Is the remaining size the WPF reference? Edit: |
There's no WPF anymore. This is size of WinForms, XML parser, garbage collector, etc. This is a fully standalone WinForms app that doesn't need a .NET runtime installed on the system. There's still ways to go down with size. One thing that immediately jumps out are various embedded resources. There's almost 3 MB of them (out of the 9.5 MB total): Many ways to go about this - could this be made trimmable (embedded resources are not trimmed, but embedded resources are not the only way to get data into the executable)? Could this be compressed (these are all uncompressed ICO files, but they compress ridiculously well and ICO has built-in support for PNG since Vista)? Then there's probably a bunch of other opportunities - I see a lot of WinForms code related to MDI, but many apps don't use MDI so it looks unnecessary. Etc. Sizoscope would let you see all that. |
wow, I just checked, its like a 90% reduction in file size by using PNG and still having multiple icons. Down to 2.59kb from 53kb on Something we should probably investigate. |
You'd be surprised how many LOB apps do actually use MDI. |
All the apps I am maintaining are MDI. |
I have never used MDI, so it would be a waste of space for me :) |
Yeah, if we run all the icons through gimp (iirc I used gimp anyway) we could probably get them smaller. I got an icon down from 416kB to 3kB by using it (which ended up saving me ~1.6MB overall as it was used in multiple places), and still had all the scales I had. |
Literally all I said is "many apps don't use MDI so it looks unnecessary". Especially in a WinForms hello world. It amounts to 300 kB of garbage in the WinForms hello world app. It would also be garbage in tools like Sizoscope that also use WinForms, but don't use MDI. A quick hack I tried: Add a file named ILLink.Substitutions.xml next to the csproj. This is the contents: <linker>
<assembly fullname="System.Windows.Forms">
<type fullname="System.Windows.Forms.Form">
<method signature="System.Void UpdateMdiWindowListStrip()" body="stub" />
</type>
</assembly>
</linker> Then add this to the csproj: <ItemGroup>
<IlcArg Include="--substitution:ILLink.Substitutions.xml" />
</ItemGroup> Et voilà, 300 kB of garbage removed. |
I have very old apps that use MDI and newer ones that don’t. I would love to just see a switch somewhere where I could specify if I need it. My biggest concern is is WebView 2 which is larger than my whole application just to display a Captcha. |
I think it's feasible to just restructure code within WinForms so that this can be trimmed naturally. It just currently can't because This is just one example. A person more familiar with WinForms will probably find more low hanging fruit than just MDI. This is literally a quick sample I found after messing with this for 15 minutes with little WinForms experience. |
@MichalStrehovsky raised a good point in this comment: Which is essentially, are all icon/image embedded resources needed for an application compiled AOT? The other suggestion is to move the icon resources to |
@MichalStrehovsky I had a chat to the team today and they are very interested in a way to trim out the icons. Is there someone who is more familiar with the trimmer that could help here? They are unlikely to accept the PR for the png icons due to backwards compatibility. If we can reduce the risk of breakages then they are more likely to take it on. So adjusting |
have this
|
|
I had a look at the size of WinForms app with PublishAot. I think there's a potential to make the size of AOT-compiled self-contained WinForms apps very attractive (single-digit MB range, fully self-contained).
If I add
<PublishAot>true</PublishAot>
and<_SuppressWinFormsTrimError>true</_SuppressWinFormsTrimError>
to a WinForms default template anddotnet publish
it I get a 54 MB executable. One might say “still better than Electron” and call it good, but it can actually be much better.Drilling into the size with Sizoscope one can immediately see something that should not be there: PresentationFramework.
Half of WPF gets dragged in because WinForms started using a WPF interface:
ICommand
. AndICommand
has a bunch of custom attributes on it that drag in WPF.We can avoid dragging in WPF if we can make sure the string within the custom attribute doesn't actually resolve. One can hack around it by putting this in the csproj for example:
We cannot ship this hack. The only way I can currently think of to fix this would be to divorce WPF and WinForms and stop putting them into the same NuGet/SDK.
With this out of the way, the size of the executable drops to 25.5 MB. We can do better. One thing that Sizoscope will point out is networking stack. It gets brought in from two places – PictureBox and XML. We already have feature switches in place for both PictureBox and XML.
Add this to the csproj to disable these:
With this, the size drops to 21.7 MB.
Looking at what's left in Sizoscope, designers stand out. There's many types that come out of DesignerAttribute and EditorAttribute. Can we make a feature switch to strip them? I experimentally stripped them out and the size drops to 15.2 MB. (Tracked in dotnet/runtime#92043)
A thing that Sizoscope currently doesn't show is manifest resources. Out of the 15.2 MB above, 7.5 MB are embedded resources. They currently cannot be trimmed automatically. Clicking through the manifest resources in WinForms assemblies that are part of the application based on Sizoscope, I see that most (all?) are designer related. Can we get rid of them? If so, the size would drop to ~7 MB.
I think there would be potential to shrink this further (e.g. the
TypeConverter
onTableLayoutSetting+StyleConverter
brings in the entire XML stack withDtdParser
and everything), but 7 MB for a fully self-contained WinForms app is already quite encouraging.The text was updated successfully, but these errors were encountered: