Search Unity

Tales from the optimization trenches: Better managed code stripping with Unity 2020 LTS

October 25, 2021 in Engine & platform | 10 min. read
Population one
Population one

Managed code stripping is a critical step in the build process that helps decrease the size of an application’s binary files. This occurs through the removal of unused code. 

By removing code, you ensure that it won’t be compiled or included in the final build. While this should slightly reduce memory usage for projects running with the IL2CPP backend, there’s always the risk of missing types and methods at runtime (among other issues) with higher managed code stripping levels.

Throughout the build process, some code is considered unused, and consequently, stripped down. Manually adding needed assemblies to the link.xml file might not be the simplest approach to preserve them from removal. During project reviews that I conduct, as part of my work as a Unity Software Development Consultant, I’ve received questions from customers on how they can better handle managed code stripping. That’s why I’ve gathered these tips and best practices which may improve your workflow with support from new managed code stripping annotation attributes.

Managed code stripping with the Unity linker

The removal of unused code is especially important when using the IL2CPP scripting backend. The Unity linker, a version of the Mono IL linker customized to work with Unity, performs a static analysis to strip managed code.

Unity supports three levels of managed code stripping for IL2CPP – Low, Medium, and High. The managed code stripping manual explains how the code stripping process works, which factors strip down certain code, and how stripping levels differ from each other. In short: The higher the code stripping level, the harder the linker tries to find and remove the unused code. You can modify the Managed Stripping Level in your project’s Player Settings.

Static analysis, leveraged by the linker for the identification of unused code, cannot cover all cases when a certain object’s type is only defined at runtime. These cases lead to false-positive results. Though there might not be any reference to a class or method while compiling, the class or method is still required by some parts of the code at runtime. In this context, the code that uses Reflection serves as a good example. Consider the following snippet:

While this is a valid and commonly used type of code, the linker doesn’t know whether MyAssembly, MyType, and MyMethod are actually used at runtime. This can cause them to be stripped down, and by extension, result in a runtime error. Check out the stripping restrictions manual for more information.

Developers that use Dependency Injection frameworks like Zenject, or serialization libraries like Newtonsoft.Json, have to be aware that false-positive code stripping is a possibility, and should address it accordingly. Here are some of the most common approaches:

  • The linker recognizes a number of attributes and allows you to annotate dependencies when it cannot identify them. As such, you can add the [Preserve] attribute to assemblies, classes, and methods that should not be stripped down.
  • A link.xml file is a per-project list that describes how to preserve assemblies, types, and other code entities within them. You can manually add needed assemblies, classes, and methods to link.xml, or use the UnityEditor API GenerateAdditionalLinkXmlFile to generate the link.xml file during the build process.

Even the Addressables package harnesses the LinkXmlGenerator. The Addressables’ build script reviews the list of assets in the groups and adds types used by assets into the link.xml file. It also adds in types used by Addressables internally via Reflection at runtime. Consider reviewing the default build script, BuildScriptPackedMode.cs, for more details on implementing a similar solution as a step in your build process, like with the Scriptable Build Pipeline.

Unity supports multiple link.xml files located inside of the Assets folder or one of its subfolders. During the build process, entries of multiple link.xml files are merged and considered by the linker.

What’s new for managed code stripping in Unity 2020 LTS

Using the [Preserve] attribute might require some manual work. But if your project is already on Unity 2020 LTS, you can use a number of new managed code stripping annotation attributes to easily and precisely mark assemblies, classes, and methods that shouldn’t be removed during code stripping. Here are just some of them:

  • RequireAttributeUsagesAttribute: When an attribute type is marked, all CustomAttributes of that type will also be marked, reducing complications when working in the High stripping level.
  • RequireDerivedAttribute: When a type is marked, all types derived from that type will similarly be marked.
  • RequiredInterfaceAttribute: When a type is marked, all interface implementations of specified types will be marked.
  • RequiredMemberAttribute: When a type is marked, all of its members with [RequiredMember] will be marked. This makes code stripping more precise as it will stop the declaring type from becoming unstrippable. Please note, however, that if the class itself is not used, members will also be stripped, despite being marked with the [RequiredMember] attribute.
  • RequireImplementorsAttribute: When the interface type is marked, all types implementing that interface will be marked. As such, there’s no need to mark every implementation. If the interface is not implemented anywhere in the code base, however, it’ll still be removed, despite the fact that it’s marked with the [RequireImplementors] attribute.

In Unity 2020.1 and 2020.2, the tool received API updates to match Mono IL linker. It can now detect some simple reflection patterns, which means that if you’ve upgraded to Unity 2020 LTS, you have fewer reasons to use link.xml files.

For more information on how Unity 2020 LTS can help optimize your coding workflows, check out this feature overview and the updates page in the Unity 2020 LTS manual.

Looking ahead

As part of our 2021 goal of making it easier for you to deliver high-quality builds to your testers and players, we’ve stayed focused on improving code stripping workflows. More specifically, we’ve added a new Managed Stripping Level called Minimal to the 2021.2 release. This will be the default for the IL2CPP backend, so be sure to stay tuned.

Managed Stripping Level
Stay tuned for new options under the Managed Stripping Level property.
October 25, 2021 in Engine & platform | 10 min. read