Geschichten aus den Schützengräben der Optimierung: Besser verwaltetes Code-Stripping mit Unity 2020 LTS

Das Strippen von verwaltetem Code ist ein wichtiger Schritt im Build-Prozess, der dazu beiträgt, die Größe der Binärdateien einer Anwendung zu verringern. Dies geschieht durch die Entfernung von ungenutztem Code.
Durch das Entfernen von Code stellen Sie sicher, dass er nicht kompiliert oder in den endgültigen Build aufgenommen wird. Dies sollte zwar den Speicherverbrauch für Projekte, die mit dem IL2CPP-Backend laufen, etwas reduzieren, aber es besteht immer das Risiko, dass Typen und Methoden zur Laufzeit fehlen (neben anderen Problemen), wenn die Stripping-Ebenen für verwalteten Code höher sind.
Während des Erstellungsprozesses wird ein Teil des Codes als ungenutzt betrachtet und folglich entfernt. Das manuelle Hinzufügen benötigter Assemblies zur Datei link.xml ist nicht unbedingt die einfachste Methode, um sie vor dem Entfernen zu bewahren. Während der Projektprüfungen, die ich im Rahmen meiner Arbeit als Unity Software Development Consultant durchführe, habe ich Fragen von Kunden erhalten, wie sie das Managed Code Stripping besser handhaben können. Deshalb habe ich diese Tipps und Best Practices zusammengestellt, die Ihren Arbeitsablauf mit Unterstützung der neuen Managed Code Stripping Annotation Attribute verbessern können.
Die Entfernung von ungenutztem Code ist besonders wichtig bei der Verwendung des IL2CPP-Skript-Backends. Der Unity-Linker, eine an Unity angepasste Version des Mono IL Linkers, führt eine statische Analyse durch, um verwalteten Code zu entfernen.
Unity unterstützt drei Stufen von verwaltetem Code-Stripping für IL2CPP - Low, Medium und High. Das Handbuch zum Strippen von verwaltetem Code erklärt, wie der Code-Stripping-Prozess funktioniert, welche Faktoren bestimmten Code strippen und wie sich die Stripping-Stufen voneinander unterscheiden. Kurz gesagt: Je höher der Code-Stripping-Level ist, desto mehr bemüht sich der Linker, den nicht verwendeten Code zu finden und zu entfernen. Sie können den Managed Stripping Level in den Player-Einstellungen Ihres Projekts ändern.
Die statische Analyse, die vom Linker zur Identifizierung von ungenutztem Code genutzt wird, kann nicht alle Fälle abdecken, in denen der Typ eines bestimmten Objekts erst zur Laufzeit definiert wird. Diese Fälle führen zu falsch-positiven Ergebnissen. Auch wenn beim Kompilieren kein Verweis auf eine Klasse oder Methode vorhanden ist, wird die Klasse oder Methode zur Laufzeit dennoch von einigen Teilen des Codes benötigt. In diesem Zusammenhang ist der Code, der Reflection verwendet, ein gutes Beispiel. Betrachten Sie den folgenden Ausschnitt:
Dies ist zwar eine gültige und häufig verwendete Art von Code, aber der Linker weiß nicht, ob MyAssembly, MyType und MyMethod zur Laufzeit tatsächlich verwendet werden. Dies kann dazu führen, dass sie nicht mehr angezeigt werden und somit ein Laufzeitfehler auftritt. Weitere Informationen finden Sie im Handbuch für Abisolierbeschränkungen.
Entwickler, die Dependency Injection-Frameworks wie Zenject oder Serialisierungsbibliotheken wie Newtonsoft.Json verwenden, müssen sich bewusst sein, dass falsch-positives Code-Stripping eine Möglichkeit ist, und sollten dies entsprechend berücksichtigen. Hier sind einige der gängigsten Ansätze:
- Der Linker erkennt eine Reihe von Attributen und ermöglicht es Ihnen, Abhängigkeiten zu kommentieren, wenn er sie nicht identifizieren kann. So können Sie das Attribut [Preserve] zu Baugruppen, Klassen und Methoden hinzufügen, die nicht entfernt werden sollen.
- Eine link.xml-Datei ist eine projektspezifische Liste, die beschreibt, wie Baugruppen, Typen und andere Code-Entitäten in ihnen erhalten werden sollen. Sie können benötigte Assemblies, Klassen und Methoden manuell zu link.xml hinzufügen oder die UnityEditor-API GenerateAdditionalLinkXmlFile verwenden, um die link.xml-Datei während des Build-Prozesses zu generieren.
Auch das Addressables-Paket nutzt den LinkXmlGenerator. Das Build-Skript von Addressables überprüft die Liste der Assets in den Gruppen und fügt die von den Assets verwendeten Typen in die Datei link.xml ein. Es fügt auch Typen hinzu, die von Addressables intern über Reflection zur Laufzeit verwendet werden. Im Standard-Build-Skript BuildScriptPackedMode.cs finden Sie weitere Details zur Implementierung einer ähnlichen Lösung als Schritt in Ihrem Build-Prozess, wie bei der skriptfähigen Build-Pipeline.
Unity unterstützt mehrere link.xml-Dateien, die sich im Ordner Assets oder einem seiner Unterordner befinden. Während des Build-Prozesses werden die Einträge mehrerer link.xml-Dateien zusammengeführt und vom Linker berücksichtigt.
Die Verwendung des Attributs [Bewahren] erfordert möglicherweise einige manuelle Arbeit. Wenn Ihr Projekt jedoch bereits auf Unity 2020 LTS läuft, können Sie eine Reihe neuer verwalteter Annotationsattribute zum Code-Stripping verwenden, um einfach und präzise Assemblies, Klassen und Methoden zu markieren, die beim Code-Stripping nicht entfernt werden sollen. Hier sind nur einige von ihnen:
- RequireAttributeUsagesAttribute: Wenn ein Attributtyp markiert ist, werden alle CustomAttributes dieses Typs ebenfalls markiert, was die Komplikationen bei der Arbeit in der High-Stripping-Ebene verringert.
- RequireDerivedAttribute: Wenn ein Typ markiert ist, werden alle von diesem Typ abgeleiteten Typen ebenfalls markiert.
- RequiredInterfaceAttribute: Wenn ein Typ markiert ist, werden alle Schnittstellenimplementierungen der angegebenen Typen markiert.
- RequiredMemberAttribute: Wenn ein Typ markiert ist, werden alle seine Mitglieder mit [RequiredMember] markiert. Dies macht das Code-Stripping präziser, da es verhindert, dass der deklarierende Typ unstrippbar wird. Bitte beachten Sie jedoch, dass, wenn die Klasse selbst nicht verwendet wird, auch die Mitglieder entfernt werden, obwohl sie mit dem Attribut [RequiredMember] gekennzeichnet sind.
- RequireImplementorsAttribute: Wenn der Schnittstellentyp markiert ist, werden alle Typen, die diese Schnittstelle implementieren, markiert. Es ist also nicht nötig, jede Umsetzung zu markieren. Wenn die Schnittstelle jedoch nirgendwo in der Codebasis implementiert ist, wird sie trotzdem entfernt, obwohl sie mit dem Attribut [RequireImplementors] gekennzeichnet ist.
In Unity 2020.1 und 2020.2 erhielt das Tool API-Updates, um dem Mono IL Linker zu entsprechen. Es kann nun einige einfache Reflexionsmuster erkennen, was bedeutet, dass Sie, wenn Sie auf Unity 2020 LTS aktualisiert haben, weniger Gründe haben, link.xml-Dateien zu verwenden.
Weitere Informationen darüber, wie Unity 2020 LTS dazu beitragen kann, Ihre Coding-Workflows zu optimieren, finden Sie in dieser Funktionsübersicht und auf der Update-Seite im Unity 2020 LTS-Handbuch.
Im Rahmen unseres Ziels aus dem Jahr 2021, es Ihnen zu erleichtern, Ihren Testern und Spielern qualitativ hochwertige Builds zur Verfügung zu stellen, haben wir uns auf die Verbesserung von Code-Stripping-Workflows konzentriert. Genauer gesagt, haben wir in der Version 2021.2 eine neue verwaltete Stripping-Stufe namens Minimal hinzugefügt. Dies wird die Standardeinstellung für das IL2CPP-Backend sein, also bleiben Sie dran.

