Récits des tranchées de l'optimisation : Un décapage de code mieux géré avec Unity 2020 LTS

ALEXANDER DEGTYAREV / UNITY TECHNOLOGIESSenior Software Development Consultant
Oct 25, 2021|10 Min
Récits des tranchées de l'optimisation : Un décapage de code mieux géré avec Unity 2020 LTS
Cette page a été traduite automatiquement pour faciliter votre expérience. Nous ne pouvons pas garantir l'exactitude ou la fiabilité du contenu traduit. Si vous avez des doutes quant à la qualité de cette traduction, reportez-vous à la version anglaise de la page web.

Le découpage de code géré est une étape critique du processus de création qui permet de réduire la taille des fichiers binaires d'une application. Cela se produit grâce à la suppression du code inutilisé.

En supprimant le code, vous vous assurez qu'il ne sera pas compilé ou inclus dans la version finale. Bien que cela devrait réduire légèrement l'utilisation de la mémoire pour les projets exécutés avec le backend IL2CPP, il existe toujours le risque de manquer des types et des méthodes au moment de l'exécution (entre autres problèmes) avec des niveaux de suppression de code géré plus élevés.

Tout au long du processus de construction, une partie du code est considérée comme inutilisée et, par conséquent, supprimée. L'ajout manuel des assemblys nécessaires au fichier link.xml n'est peut-être pas l'approche la plus simple pour les préserver de la suppression. Lors des revues de projet que j'effectue, dans le cadre de mon travail en tant que consultant en développement logiciel Unity, j'ai reçu des questions de clients sur la manière dont ils peuvent mieux gérer le découpage de code géré. C'est pourquoi j'ai rassemblé ces conseils et bonnes pratiques qui peuvent améliorer votre flux de travail avec le support de nouveaux attributs d'annotation de suppression de code géré.

Suppression de code gérée avec le linker Unity

La suppression du code inutilisé est particulièrement importante lors de l'utilisation du backend de script IL2CPP. Le linker Unity, une version du linker Mono IL personnalisée pour fonctionner avec Unity, effectue une analyse statique pour supprimer le code géré.

Unity prend en charge trois niveaux de suppression de code géré pour IL2CPP : faible, moyen et élevé. Le manuel de suppression de code géré explique comment fonctionne le processus de suppression de code, quels facteurs suppriment certains codes et comment les niveaux de suppression diffèrent les uns des autres. En bref: Plus le niveau de suppression du code est élevé, plus le linker essaie de trouver et de supprimer le code inutilisé. Vous pouvez modifier le niveau de suppression géré dans les paramètres du lecteur de votre projet.

L'analyse statique, exploitée par le linker pour l'identification du code inutilisé, ne peut pas couvrir tous les cas où le type d'un certain objet n'est défini qu'au moment de l'exécution. Ces cas conduisent à des résultats faussement positifs. Même s'il n'y a aucune référence à une classe ou à une méthode lors de la compilation, la classe ou la méthode est toujours requise par certaines parties du code au moment de l'exécution. Dans ce contexte, le code qui utilise Reflection sert de bon exemple. Considérez l’extrait suivant :

Bien qu'il s'agisse d'un type de code valide et couramment utilisé, le linker ne sait pas si MyAssembly, MyType et MyMethod sont réellement utilisés au moment de l'exécution. Cela peut entraîner leur démontage et, par extension, entraîner une erreur d'exécution. Consultez le manuel des restrictions de décapage pour plus d’informations.

Les développeurs qui utilisent des frameworks d'injection de dépendances comme Zenject ou des bibliothèques de sérialisation comme Newtonsoft.Json doivent être conscients que le décapage de code faussement positif est une possibilité et doivent y remédier en conséquence. Voici quelques-unes des approches les plus courantes :

  • Le linker reconnaît un certain nombre d'attributs et vous permet d'annoter les dépendances lorsqu'il ne peut pas les identifier. En tant que tel, vous pouvez ajouter l’ attribut [Preserve] aux assemblages, classes et méthodes qui ne doivent pas être supprimés.
  • Un fichier link.xml est une liste par projet qui décrit comment conserver les assemblys, les types et d'autres entités de code en leur sein. Vous pouvez ajouter manuellement les assemblages, les classes et les méthodes nécessaires à link.xml, ou utiliser l'API UnityEditor GenerateAdditionalLinkXmlFile pour générer le fichier link.xml pendant le processus de génération.

Même le package Addressables exploite le LinkXmlGenerator. Le script de construction d'Adressables examine la liste des actifs dans les groupes et ajoute les types utilisés par les actifs dans le fichier link.xml. Il ajoute également les types utilisés par Addressables en interne via Reflection lors de l'exécution. Pensez à consulter le script de build par défaut, BuildScriptPackedMode.cs, pour plus de détails sur la mise en œuvre d'une solution similaire en tant qu'étape de votre processus de build, comme avec le pipeline de build scriptable.

Unity prend en charge plusieurs fichiers link.xml situés dans le dossier Assets ou l'un de ses sous-dossiers. Pendant le processus de construction, les entrées de plusieurs fichiers link.xml sont fusionnées et prises en compte par l'éditeur de liens.

Nouveautés concernant le découpage de code géré dans Unity 2020 LTS

L'utilisation de l'attribut [Préserver] peut nécessiter un travail manuel. Mais si votre projet est déjà sur Unity 2020 LTS, vous pouvez utiliser un certain nombre de nouveaux attributs d'annotation de suppression de code gérés pour marquer facilement et précisément les assemblages, les classes et les méthodes qui ne doivent pas être supprimés lors de la suppression de code. En voici quelques-uns :

  • RequireAttributeUsagesAttribute: Lorsqu'un type d'attribut est marqué, tous les CustomAttributes de ce type seront également marqués, réduisant ainsi les complications lorsque vous travaillez au niveau de décapage élevé.
  • Attribut dérivé requis : Lorsqu'un type est marqué, tous les types dérivés de ce type seront marqués de la même manière.
  • Attribut d'interface requis : Lorsqu'un type est marqué, toutes les implémentations d'interface des types spécifiés seront marquées.
  • Attribut membre requis : Lorsqu'un type est marqué, tous ses membres avec [RequiredMember] seront marqués. Cela rend le dépouillement du code plus précis car cela empêchera le type déclarant de devenir impossible à dépouiller. Veuillez noter cependant que si la classe elle-même n'est pas utilisée, les membres seront également supprimés, même s'ils sont marqués avec l'attribut [RequiredMember].
  • RequireImplementorsAttribute : Lorsque le type d'interface est marqué, tous les types implémentant cette interface seront marqués. Il n’est donc pas nécessaire de marquer chaque implémentation. Cependant, si l'interface n'est implémentée nulle part dans la base de code, elle sera quand même supprimée, malgré le fait qu'elle soit marquée avec l'attribut [RequireImplementors].

Dans Unity 2020.1 et 2020.2, l'outil a reçu des mises à jour d'API pour correspondre au lien Mono IL. Il peut désormais détecter certains modèles de réflexion simples, ce qui signifie que si vous avez effectué une mise à niveau vers Unity 2020 LTS, vous avez moins de raisons d'utiliser les fichiers link.xml.

Pour plus d'informations sur la manière dont Unity 2020 LTS peut vous aider à optimiser vos flux de travail de codage, consultez cette présentation des fonctionnalités et la page des mises à jour dans le manuel Unity 2020 LTS.

Regard vers l'avenir

Dans le cadre de notre objectif 2021 visant à vous permettre de fournir plus facilement des builds de haute qualité à vos testeurs et joueurs, nous sommes restés concentrés sur l'amélioration des flux de travail de suppression de code. Plus précisément, nous avons ajouté un nouveau niveau de suppression géré appelé Minimal à la version 2021.2. Ce sera la valeur par défaut pour le backend IL2CPP, alors assurez-vous de rester à l'écoute.

Niveau de décapage géré