NOTE: The original post is outdated. Please refer to this knowledge base article instead - https://support.unity3d.com/hc/en-us/articles/208412186
Deprecated original article:
We created a small guide on IL2CPP build sizes. What is important, what can be improved and how you can improve it. We constantly work on the improvements and could already gain massive size improvements in the latest patches. Ensure always to use the latest patch, as we fix bug on daily basis and work on size improvements in parallel. Although, many parts are similar to WebGL this guide is mainly targeted to iOS. But all the fixes we make for IL2CPP have direct affect on WebGL and iOS builds.
Why is the the binary size of the IL2CPP project larger that the mono version?
Two things apply:
- If you make a universal build, thereâs a 32bit slice and a 64bit slice, which contains the exact same executable for 2 different architectures so nearly doubled size.
- IL2CPP produced bigger builds even if you just compare ARMv7 Mono with ARMv7 IL2CPP; this had to do with how we dealt with the type metadata. It was resolved in Unity 4.6.4p2.
With âUniversalâ builds, you will always yield larger binary sizes than just having ARMv7 or ARM64. The removal of the managed assemblies already made the builds smaller (in Unity 4.6.3f1), the real improvement came when we were able to construct at least generic typeinfos at runtime, but this is now partly possible (since Unity 4.6.4p1). But we have some more improvements in production, so stay tuned. Universal builds include 32-bit and 64-bit slices in the fat executable which results in at least doubled size of standard mono 32bit.
We made improvements to generic type and array generation in Unity 4.6.4p1 too. Apart from that, thereâs information here about reducing your iOS build size: Unity - Manual: Optimizing the size of the built iOS Player. For additional information read in Stripping below.
Please always make a comparison table with mono (ARMv7), IL2CPP (ARMv7), IL2CPP (ARM64), IL2CPP Universal and which striping settings you use to have all information to compare!
Itâs always worth to check the actual binary size, as this is the one which is affected by the source code compilation referred to as the fat file containing binary from ARMv7 and ARM64 if it is a universal build and therefore nearly doubled size of a single ARMv7 mono or IL2CPP build. If you make a universal build, thereâs a 32bit slice and a 64bit slice, which contains the exact same executable for 2 different architectures so nearly doubled size. If you want to compare the output directly please compare ARMv7 mono with ARMv7 IL2CPP.
Which sizes limit does Apple check?
Apple has a 80MB limit for the 32bit+64bit code segment in total if you support a minimum OS of less than iOS 7.0. Apps that are set to minimum OS of 7.0 or greater have a binary size limit of 60MB (60 000 000 bytes) per architecture slice. 100MB (mobile data) application limit (user needs to download via Wifi if >100MB) and max 4GB application limit. 80MB and 60MB/60MB is the limit for code segment size included in the fat binary for the 32bit and 64bit slice. You need to use otool to get the right numbers. 100MB applies to to the .ipa size (Use the estimate button in Xcode after archiving to get the right estimate), this determines if users need a WiFi connection to download your app. Follow following steps to get the right numbers:
- Build your app in release mode. Do not use debug mode, as it does not represent your final app. Ensure you use correct optimisation, more information can be found here: Unity - Manual: Optimizing the size of the built iOS Player.
- Archive your app in XCode.
- Use the estimate button to get the estimated size of your app to ensure if you are above or below 100MB or the overall size of 4GB.
- Note:* since XCode 6.3 there is no estimate button anymore. You can calculate it by using following formula, but be aware that the compression coefficient might vary:
app_store_size = sizeof(.ipa/(zip with exec_binary removed)) + sizeof(codesegment(exec_binary)) + 0.2 * sizeof(datasegment(exec_binary))
The formula is coming from broader knowledge that only the code segment gets encrypted and the data segment compression ratio can be verified pretty easy.
Extract the data segment from executable via dd command (you can specify byte offset + length) and then try to compress it. The code segment gets scrambled to look like a perfect noise. You need to descramble it with a decryption key before executing. iTunes/App Store app manages there keys. Thatâs why we add code segment as whole without adjusting for compression ratio.
-
Once you have archived your app, show the .xcodearchive in your finder
-
Show Package Content
-
Go to Products/Applications/Productname.app. (Which should have nearly the same size as the estimated size button in XCode returns)
-
Show the content of the Productname.app
-
Locate the ProductName binary (exec_binary) on the top level.
-
Use otool to generate the output for the 80MB limit.
otool -l path_to_exec_binary
or even size path_to_exec_binary
-
Now you can retrieve the output depending on the architecture (armv7 + a section arm64 if itâs a universal build) Gather the information for armv7 (LC_SEGMENT) and do the same for arm64 if applicable (LC_SEGMENT_64)
-
Locate the LC_SEGMENT with segname __TEXT and take the filesize
code segment size = 30474240 ~= 30MB -
Locate the LC_SEGMENT with segname __DATA and take the filesize
data segment size (mostly metadata) = 10420224 ~= 10 MB
This results in following otool result table:
architecture armv7:
code segment size = 30474240 ~= 30MB
data segment size (mostly metadata) = 10420224 ~= 10 MB
segment + data segment = 30 + 10 = 40MB
architecture arm64:
code segment size = 25559040 ~= 26MB
data segment size (mostly metadata) = 17727488 ~= 18 MB
segment + data segment = 26 + 18 = 44MB
Apple uses for their check armv7(code segment)+arm64 (code segment) which results in a otool report of 30MB + 26MB = 56MB in this example which is below the 80MB for <7.0 and 30MB & 26MB are each below 60MB >=7.0 for a universal build.
Itâs easy to test this, make a test app in release mode and submit it to the app store, the static project checker in the beginning should alert you if a slice is over a certain limit. For the estimated size XCode should take all the DRM measurements into account, but if you encounter a different behaviour please let us know! Again, as soon as you uploaded a test app to iTunesConnect, it should show you any pitfalls and you should see also the app size in the Version Summary page.
Stripping
The IL2CPP scripting backend always does byte code stripping, no matter what the âStripping Levelâ setting is in the editor. The best option for a user is to set the âStripping Levelâ option in the editor to a value of âDisabledâ, as the affect of any other âStripping Levelâ option will likely be minimal on executable size, because IL2CPP will strip anyway. If the you choose a âStripping Levelâ value other than âDisabledâ you could run into problems, because then the IL2CPP build toolchain in the editor will attempt to determine which native engine code is used by you scripting assemblies, and only register that native code when the player starts. If you encounter problems with your stripping settings and you believe they are wrong, please submit a bug report.
Exceptions
By default Mono exceptions are caught in Xcode, but IL2CPP exceptions are not.
You can make the Xcode debugger break on exceptions by adding an Exception breakpoint as described here: https://developer.apple.com/library/mac/recipes/xcode_help-breakpoint_navigator/articles/adding_an_exception_breakpoint.html
Replace the text âstd::underflow_errorâ with âIl2CppExceptionWrapperâ in the text-box shown on that page. You may need to restart Xcode, or possibly just the debug session.
In general the il2cpp managed callstacks are currently unreliable and on iOS only partially working - the way we do them often prints wrong functions. So for now you need to check the real callstacks int the XCode callstack window. We work on improvements on these areas though.
A managed exception is not necessarily an app crash. But to break on a managed exception in Xcode, set a breakpoint at il2cpp_codegen_raise_exception or in case of a nullreference exception you can set a breakpoint in NullCheck method in il2cpp-codegen.h You donât want to break when the method is called, as it will be called very often, but you can break after the if check at the start of the NullCheck method.
5.3.x build size increase due to BitCode
Recently we are seeing number of questions scattered around forums regarding build size increase when building iOS applications with Unity 5.3.x often due to BitCode support enabled which is new in 5.3.x. This post aims to clarify some aspects of it in single place. Unity 5.3.x build size increase FAQ - Unity Engine - Unity Discussions
Some more information to read about IL2CPP and 64bit can be found on our blog:
IL2CPP blog posts
iOS 64bit support
I hope this helps and if you have any questions or suggestions to improve the post, please let me know!