xamarin / xamarin-macios

.NET for iOS, Mac Catalyst, macOS, and tvOS provide open-source bindings of the Apple SDKs for use with .NET managed languages such as C#
Other
2.42k stars 504 forks source link

iOS application size increase when migrating from legacy xamarin-ios to .net #19381

Open rotanov opened 8 months ago

rotanov commented 8 months ago

Description

Context: Migrating game engine written in c# from legacy xamarin ios to .NET7.

After the migration, when comparing minimal engine project for ios ("EmptyProject") I noticed non neglectable increase in size of application (.NET7: 13.3MB , .NET8 10.8MB)

I built the project with legacy xamarin ios, net7 and net8 and tried to make some analysis and comparison. See Details section. Attachment contains built applications, debug symbols, MachO symbol dumps, some stats, binlogs, see Attachment section.

I understand there are a lot of issues on the matter and some growth is to be expected. But 10Mb (for net8) seems like too much, may be I made some mistakes, or there are methods to reduce application size I'm not aware of? (apart from TrimSafe on engine assembly)

Details

Main object of interest is predicted download size, since that is by what appstore limits availability of download over metered connections. .app/.ipa size is not helpful in that regard, because afaik executable gets encrypted by appstore, making it compression-resistant. We predict download size as uncompressed executable size + everything else in ipa/app compressed size.

SDK legacy-xamarin-ios net7.0-ios net8.0-ios
executable size 23083888 35026384 32599520
executable growth factor x1 x1.51 x1.41
executable growth diff 0 11942496 9515632
rest of the app 8265323 12979960 12168123
rest of the app compressed 4257091 5617476 5588735
predicted app download size 27340979 40643860 38188255
download size growth factor x1 x1.48 x1.39
download size growth diff 0 13302881 10847276

bloaty was used to generate symbols:

bloaty -d segments,sections,symbols,compileunits --debug-file=./bin/Release/EmptyProject.app.dSYM/Contents/Resources/DWARF/EmptyProject ./bin/Release/EmptyProject.app/EmptyProject -n 0 --tsv > symbols.txt

I used slightly modified (included in attachment) script by @ivanpovazan to generate stats. (mentioned here) Generated stats are also in attachment.

Script modifications are:

  1. introduced category, where I tried to couple together everything I think engine and game project are responsible for.
  2. categorized more symbols as CORLIB
  3. 2. broke the diff, so diff is disabled

Even so, the "OTHERS" section grew a lot.

Stats:

legacy xamarin ios ``` -------------------------------------------------------------------------------- Stats for symbols from __TEXT,__text: -------------------------------------------------------------------------------- Total number of _infos: 0 Total number of plts: 35625 Total number of wrappers: 3155 Total number of icus: 0 Total number of Globalizations: 1496 Total number of generics: 10383 -------------------------------------------------------------------------------- Total size of _infos: 0 Total size of plts: 712672 Total size of wrappers: 606384 Total size of icus: 0 Total size of Globalizations: 290856 Total size of generics: 2400912 -------------------------------------------------------------------------------- Total symbols: 102682 Total corelib: 16840 16.40% Total sysios: 2504 2.44% Total mono: 7023 6.84% Total others: 26769 26.07% Total game and engine: 49546 48.25% Total size: 19778536 Total corelib: 3716896 18.79% Total sysios: 337796 1.71% Total mono: 3117024 15.76% Total others: 2240916 11.33% Total game and engine: 10365904 52.41% -------------------------------------------------------------------------------- ```
net7 ``` -------------------------------------------------------------------------------- Stats for symbols from __TEXT,__text: -------------------------------------------------------------------------------- Total number of _infos: 0 Total number of plts: 34430 Total number of wrappers: 4171 Total number of icus: 5648 Total number of Globalizations: 1904 Total number of generics: 19493 -------------------------------------------------------------------------------- Total size of _infos: 0 Total size of plts: 688912 Total size of wrappers: 1049952 Total size of icus: 872852 Total size of Globalizations: 382336 Total size of generics: 5410680 -------------------------------------------------------------------------------- Total symbols: 142057 Total corelib: 37509 26.40% Total sysios: 3032 2.13% Total mono: 6696 4.71% Total others: 43358 30.52% Total game and engine: 51462 36.23% Total size: 30762632 Total corelib: 9797056 31.85% Total sysios: 528448 1.72% Total mono: 4327068 14.07% Total others: 4056944 13.19% Total game and engine: 12053116 39.18% -------------------------------------------------------------------------------- ```
net8 ``` -------------------------------------------------------------------------------- Stats for symbols from __TEXT,__text: -------------------------------------------------------------------------------- Total number of _infos: 0 Total number of plts: 1 Total number of wrappers: 3604 Total number of icus: 5621 Total number of Globalizations: 1323 Total number of generics: 10026 -------------------------------------------------------------------------------- Total size of _infos: 0 Total size of plts: 140 Total size of wrappers: 861976 Total size of icus: 872424 Total size of Globalizations: 533484 Total size of generics: 3182648 -------------------------------------------------------------------------------- Total symbols: 103924 Total corelib: 34695 33.38% Total sysios: 2618 2.52% Total mono: 6379 6.14% Total others: 30602 29.45% Total game and engine: 29630 28.51% Total size: 27785504 Total corelib: 9430576 33.94% Total sysios: 414288 1.49% Total mono: 3943104 14.19% Total others: 4024752 14.49% Total game and engine: 9972784 35.89% -------------------------------------------------------------------------------- ```

MachO sections comparison:

macho-sections-comparison

Environment

Version information ``` Visual Studio Community 2022 for Mac Version 17.6.6 (build 408) Installation UUID: 7571e8bb-d341-4541-a456-2451824e8830 Runtime .NET 7.0.3 (64-bit) Architecture: X64 Microsoft.macOS.Sdk 13.1.1007; git-rev-head:8afca776a0a96613dfb7200e0917bb57f9ed5583; git-branch:release/7.0.1xx-xcode14.2 Roslyn (Language Service) 5.6.0-3.23180.6+99e956e42697a6dd886d1e12478ea2b27cceacfa NuGet Version: 6.4.0.117 Xamarin Designer Version: 17.6.3.9 Hash: 2648399ae8 Branch: remotes/origin/d17-6 Build date: 2023-10-23 17:40:07 UTC .NET SDK (x64) SDK: /usr/local/share/dotnet/sdk/7.0.403/Sdks SDK Version: 7.0.403 MSBuild SDKs: /Applications/Visual Studio.app/Contents/MonoBundle/MSBuild/Current/bin/Sdks .NET Runtime (x64) Runtime: /usr/local/share/dotnet/dotnet Runtime Version: 7.0.13 Xamarin.Profiler Version: 1.8.0.49 Location: /Applications/Xamarin Profiler.app/Contents/MacOS/Xamarin Profiler Updater Version: 11 Xamarin.Android Not Installed Microsoft Build of OpenJDK Java SDK: Not Found Eclipse Temurin JDK Java SDK: Not Found Android SDK Manager Version: 17.6.0.50 Hash: a715dca Branch: HEAD Build date: 2023-10-23 17:40:12 UTC Android Device Manager Version: 0.0.0.1309 Hash: 06e3e77 Branch: HEAD Build date: 2023-10-23 17:40:12 UTC Apple Developer Tools Xcode: 15.0 22265 Build: 15A240d Xamarin.Mac Version: 9.3.0.18 Visual Studio Community Hash: 9d266025e Branch: xcode14.3 Build date: 2023-09-06 19:52:26-0400 Xamarin.iOS Version: 16.4.0.18 Visual Studio Community Hash: 9d266025e Branch: xcode14.3 Build date: 2023-09-06 19:52:27-0400 Build Information Release ID: 1706060408 Git revision: 3912eca6712af97335aa3a782abaa75ff86ec74a Build date: 2023-10-23 17:38:20+00 Build branch: release-17.6 Build lane: release-17.6 Operating System Mac OS X 14.0.0 Darwin 23.0.0 Darwin Kernel Version 23.0.0 Fri Sep 15 14:42:42 PDT 2023 root:xnu-10002.1.13~1/RELEASE_X86_64 x86_64 ```

Attachment

I'm sorry for the attachment format, max upload size is 20MB and there's extension whitelist.

  1. Download all the parts of the archive
  2. Swap .00x and .zip in the name of each file
  3. Extract. (I used 7zip-gui for windows to pack and split)

net-ios-app-size-analysis.001.zip net-ios-app-size-analysis.002.zip net-ios-app-size-analysis.003.zip net-ios-app-size-analysis.004.zip net-ios-app-size-analysis.005.zip

.
│   calc_stats.bat -- runs stats.py in each of top level directories starting with _
│   stats.py -- modified script
│   summary.md
├───legacy-xamarin-ios
│   │   symbols.txt -- symbols made using bloaty
│   │   symbols_*.list -- symbols filtered by stats.py
│   │   symbols_stats.list -- stats made by stats.py
│   │   msbuild.binlog -- binlog
│   │   
│   ├───EmptyProject.app -- the application
│   └───EmptyProject.app.dSYM -- debug symbols
│                               
├───net7.0-ios -- same structure                         
├───net8.0-ios -- same structure
│                           
├───_legacy-vs-net7 -- this and below are directories for running stats.py in (empty, since I commented out diff calculation in stats.py)
├───_legacy-vs-net8
├───_net7-vs-net8
└───macho-sections-comparison.png
rolfbjarne commented 8 months ago

This is great diagnostics!

I had a look at the symbols_SYSIOS.list files, and for all the symbols I checked that were in multiple files (around a dozen), almost all were bigger in .NET 7/8 (slightly smaller in .NET 8 vs .NET 7, but both significantly bigger than legacy Xamarin).

Selecting a symbol at random (AVFoundation_AVMediaTypesExtensions_get_AVMediaTypeClosedCaption), it's 124 bytes in legacy Xamarin, 176 bytes in .NET 7 and 172 bytes in .NET 8. While this doesn't seem significant, it'll add up if something similar happens to every symbol (there were a few that had the same size in all versions, but none that I checked were smaller in .NET than legacy Xamarin).

Then I checked the AOT-compiled code for this method, and this is what it shows (note that bulid settings might be different from the original reporter, so the actual assembly is likely different).

Legacy Xamarin

Size: 124 bytes

bin/iPhone/Release/testapp.app/testapp:
(__TEXT,__text) section
_Xamarin_iOS_AVFoundation_AVMediaTypesExtensions_get_AVMediaTypeClosedCaption:
00000001001b2944        stp     x29, x30, [sp, #-0x10]!
00000001001b2948        adrp    x8, 526 ; 0x1003c0000
00000001001b294c        add     x8, x8, #0x280
00000001001b2950        ldrb    w8, [x8, #0x1aa]
00000001001b2954        cbz     w8, 0x1001b2994
00000001001b2958        adrp    x8, 551 ; 0x1003d9000
00000001001b295c        add     x8, x8, #0xe30
00000001001b2960        ldr     x9, [x8, #0x610]
00000001001b2964        ldr     x9, [x9]
00000001001b2968        cbz     x9, 0x1001b29a0
00000001001b296c        ldr     w10, [x9, #0x18]
00000001001b2970        cmp     w10, #0x3
00000001001b2974        b.ls    0x1001b29b0
00000001001b2978        ldr     x10, [x8, #0x618]
00000001001b297c        ldr     x1, [x8, #0x638]
00000001001b2980        add     x2, x9, #0x38
00000001001b2984        ldr     x0, [x10]
00000001001b2988        bl      _Xamarin_iOS_ObjCRuntime_Dlfcn_CachePointer_intptr_string_intptr_
00000001001b298c        ldp     x29, x30, [sp], #0x10
00000001001b2990        ret
00000001001b2994        mov     w0, #0x1aa
00000001001b2998        bl      _mono_aot_Xamarin_iOS_init_method
00000001001b299c        b       0x1001b2958
00000001001b29a0        adr     x1, #0x0
00000001001b29a4        nop
00000001001b29a8        orr     w0, wzr, #0x7f
00000001001b29ac        bl      _p_2
00000001001b29b0        adr     x1, #0x0
00000001001b29b4        nop
00000001001b29b8        mov     w0, #0x6c
00000001001b29bc        bl      _p_2

.NET 8

Size: 172 bytes

bin/Release/net8.0-ios/ios-arm64/MySimpleApp.app/MySimpleApp:
(__TEXT,__text) section
_Microsoft_iOS_AVFoundation_AVMediaTypesExtensions_get_AVMediaTypeClosedCaption:
0000000100f34338        stp     x20, x19, [sp, #-0x20]!
0000000100f3433c        stp     x29, x30, [sp, #0x10]
0000000100f34340        adrp    x8, 1471 ; 0x1014f3000
0000000100f34344        adrp    x19, 1450 ; 0x1014de000
0000000100f34348        nop
0000000100f3434c        ldrb    w20, [x19, #0xb1e]
0000000100f34350        ldr     x8, [x8, #0x868]
0000000100f34354        cbnz    x8, 0x100f3439c
0000000100f34358        cbz     w20, 0x100f343a4
0000000100f3435c        adrp    x8, 1449 ; 0x1014dd000
0000000100f34360        ldr     x8, [x8, #0x348]
0000000100f34364        ldr     x8, [x8]
0000000100f34368        ldr     w9, [x8, #0x18]
0000000100f3436c        cmp     w9, #0x3
0000000100f34370        b.ls    0x100f343d0
0000000100f34374        adrp    x9, 1449 ; 0x1014dd000
0000000100f34378        adrp    x10, 1449 ; 0x1014dd000
0000000100f3437c        add     x2, x8, #0x38
0000000100f34380        ldr     x9, [x9, #0x350]
0000000100f34384        ldr     x1, [x10, #0x370]
0000000100f34388        ldr     x0, [x9]
0000000100f3438c        bl      _Microsoft_iOS_ObjCRuntime_Dlfcn_CachePointer_intptr_string_intptr_
0000000100f34390        ldp     x29, x30, [sp, #0x10]
0000000100f34394        ldp     x20, x19, [sp], #0x20
0000000100f34398        ret
0000000100f3439c        bl      _mono_aot_Microsoft_iOS_icall_cold_wrapper_264
0000000100f343a0        cbnz    w20, 0x100f3435c
0000000100f343a4        adrp    x0, 920 ; 0x1012cc000
0000000100f343a8        add     x0, x0, #0x19d
0000000100f343ac        bl      _mono_aot_Microsoft_iOS_init_method
0000000100f343b0        mov     w8, #0x1
0000000100f343b4        strb    w8, [x19, #0xb1e]
0000000100f343b8        b       0x100f3435c
0000000100f343bc        adrp    x1, 1267 ; 0x101427000
0000000100f343c0        mov     w0, #0xeb
0000000100f343c4        ldr     x1, [x1, #0xe40]
0000000100f343c8        bl      plt_Microsoft_iOS__jit_icall_llvm_throw_corlib_exception_abs_trampoline
0000000100f343cc        brk     #0x1
0000000100f343d0        adrp    x1, 1267 ; 0x101427000
0000000100f343d4        mov     w0, #0xc9
0000000100f343d8        ldr     x1, [x1, #0xe48]
0000000100f343dc        bl      plt_Microsoft_iOS__jit_icall_llvm_throw_corlib_exception_abs_trampoline
0000000100f343e0        brk     #0x1

This is the IL (identical between legacy Xamarin and .NET):

System.IntPtr AVFoundation.AVMediaTypesExtensions::get_AVMediaTypeClosedCaption () (internal compilercontrolled hidebysig reuseslot specialname static)
    2 local variables:
        System.IntPtr* V_0
        System.IntPtr& V_1
    IL_0000: ldsfld System.IntPtr[] AVFoundation.AVMediaTypesExtensions::values
    IL_0005: ldc.i4.3
    IL_0006: ldelema System.IntPtr
    IL_000b: stloc.1
    IL_000c: ldloc.1
    IL_000d: conv.u
    IL_000e: stloc.0
    IL_000f: ldsfld System.IntPtr ObjCRuntime.Libraries/AVFoundation::Handle
    IL_0014: ldstr "AVMediaTypeClosedCaption"
    IL_0019: ldloc.0
    IL_001a: call System.IntPtr ObjCRuntime.Dlfcn::CachePointer(System.IntPtr,System.String,System.IntPtr*)
    IL_001f: ret

@ivanpovazan not sure who would be able to explain this difference in the AOT-compiled size?

ivanpovazan commented 8 months ago

@vargaz could you please take a look at Rolf's comment above?

vargaz commented 8 months ago

Some differences:

vargaz commented 8 months ago

Most of the code size increase is probably caused by bigger input assemblies, i.e. corlib is probably bigger in net7/8.

ivanpovazan commented 6 months ago

@rotanov thank you for the detailed analysis!

As you investigated differences in size of your iOS application, I was wondering if you considered trying out NativeAOT?

In .NET8 we released experimental support for targeting iOS platforms with NativeAOT which shows great potential in size savings - reaching 50% smaller apps, but has at the same time much stricter limitations regarding trimming and use of AOT-incompatible code than our MonoAOT offering: https://github.com/xamarin/xamarin-macios/blob/main/docs/nativeaot.md

Additionally, the sample code you reported that crashes with MonoAOT Repro.MakeChecker_Crashing in: https://github.com/dotnet/runtime/issues/94063 works fine with NativeAOT.

AndreyFromGomel commented 1 month ago

Hi, I'm working on similar project as original poster and want to highlight that AppExtensions are significantly larger for .Net 8. For Xamarin App extension compiles to 2.7 MB appex, for .Net it is ~12 MB with the same project settings. I tried to change project parameters to resolve this issue, the best scenario I got is 7.5 MB when I set to to use interpreter for app extension. When https://github.com/xamarin/xamarin-macios/issues/17877 be fixed it reduce appex size on additional 2 MB, but still it 2x size compared to Xamarin version. Building appex with NativeAOT fails with following exception:

  System.Exception: No entrypoint module
     at ILCompiler.Program.Run() + 0x26bc
     at ILCompiler.ILCompilerRootCommand.<>c__DisplayClass227_0.<.ctor>b__0(ParseResult result) + 0x304

so I was not able to check this option.

For reference on this image the same .Net 8 | Xamarin appex.

image
rolfbjarne commented 1 month ago

Hi, I'm working on similar project as original poster and want to highlight that AppExtensions are significantly larger for .Net 8.

This is know, we have a tracking issue for app extension improvements we'd like to get done at some point: https://github.com/xamarin/xamarin-macios/issues/10051

Building appex with NativeAOT fails with following exception:

We never tried NativeAOT on an app extension, so I filed https://github.com/xamarin/xamarin-macios/issues/20653 to have a look and see if it's possible.