Contos das trincheiras de otimização: Melhor gerenciamento de remoção de código com Unity 2020 LTS

A remoção de código gerenciada é uma etapa crítica no processo de construção que ajuda a diminuir o tamanho dos arquivos binários de um aplicativo. Isso ocorre por meio da remoção de código não utilizado.
Ao remover o código, você garante que ele não será compilado ou incluído na compilação final. Embora isso deva reduzir um pouco o uso de memória para projetos executados com o backend IL2CPP, sempre há o risco de tipos e métodos ausentes no tempo de execução (entre outros problemas) com níveis mais altos de remoção de código gerenciado.
Durante o processo de construção, parte do código é considerada não utilizada e, consequentemente, simplificada. Adicionar manualmente os assemblies necessários ao arquivo link.xml pode não ser a abordagem mais simples para preservá-los da remoção. Durante as revisões de projetos que realizo, como parte do meu trabalho como consultor de desenvolvimento de software da Unity, recebi perguntas de clientes sobre como eles podem lidar melhor com a remoção de código gerenciada. É por isso que reuni essas dicas e práticas recomendadas que podem melhorar seu fluxo de trabalho com o suporte de novos atributos de anotação de remoção de código gerenciado.
A remoção de código não utilizado é especialmente importante ao usar o backend de script IL2CPP. O vinculador Unity, uma versão do vinculador Mono IL personalizado para funcionar com o Unity, executa uma análise estática para remover o código gerenciado.
O Unity oferece suporte a três níveis de remoção de código gerenciado para IL2CPP: baixo, médio e alto. O manual de remoção de código gerenciado explica como o processo de remoção de código funciona, quais fatores removem determinado código e como os níveis de remoção diferem entre si. Resumidamente: Quanto maior o nível de remoção de código, mais o vinculador tenta encontrar e remover o código não utilizado. Você pode modificar o Nível de remoção gerenciado nas configurações do player do seu projeto.
A análise estática, aproveitada pelo vinculador para a identificação de código não utilizado, não pode cobrir todos os casos em que um determinado tipo de objeto é definido apenas em tempo de execução. Esses casos levam a resultados falso-positivos. Embora possa não haver nenhuma referência a uma classe ou método durante a compilação, a classe ou método ainda é necessário para algumas partes do código em tempo de execução. Nesse contexto, o código que usa Reflection serve como um bom exemplo. Considere o seguinte trecho:
Embora esse seja um tipo de código válido e comumente usado, o vinculador não sabe se MyAssembly, MyType e MyMethod são realmente usados em tempo de execução. Isso pode fazer com que eles sejam simplificados e, por extensão, resultar em um erro de tempo de execução. Confira o manual de restrições de remoção para mais informações.
Desenvolvedores que usam estruturas de injeção de dependência como Zenject ou bibliotecas de serialização como Newtonsoft.Json precisam estar cientes de que a remoção de código de falsos positivos é uma possibilidade e devem lidar com isso adequadamente. Aqui estão algumas das abordagens mais comuns:
- O vinculador reconhece vários atributos e permite que você anote dependências quando ele não consegue identificá-las. Dessa forma, você pode adicionar o atributo [Preserve] a assemblies, classes e métodos que não devem ser simplificados.
- Um arquivo link.xml é uma lista por projeto que descreve como preservar assemblies, tipos e outras entidades de código dentro deles. Você pode adicionar manualmente os assemblies, classes e métodos necessários ao link.xml ou usar a API GenerateAdditionalLinkXmlFile do UnityEditor para gerar o arquivo link.xml durante o processo de compilação.
Até mesmo o pacote Addressables utiliza o LinkXmlGenerator. O script de construção do Addressables analisa a lista de ativos nos grupos e adiciona tipos usados pelos ativos no arquivo link.xml. Ele também adiciona tipos usados internamente pelos Addressables via Reflection em tempo de execução. Considere revisar o script de compilação padrão, BuildScriptPackedMode.cs, para obter mais detalhes sobre como implementar uma solução semelhante como uma etapa no seu processo de compilação, como com o Scriptable Build Pipeline.
O Unity suporta vários arquivos link.xml localizados dentro da pasta Assets ou em uma de suas subpastas. Durante o processo de construção, entradas de vários arquivos link.xml são mescladas e consideradas pelo vinculador.
Usar o atributo [Preserve] pode exigir algum trabalho manual. Mas se o seu projeto já estiver no Unity 2020 LTS, você pode usar uma série de novos atributos de anotação de remoção de código gerenciados para marcar de forma fácil e precisa assemblies, classes e métodos que não devem ser removidos durante a remoção de código. Aqui estão apenas alguns deles:
- RequireAttributeUsagesAttribute: Quando um tipo de atributo é marcado, todos os CustomAttributes desse tipo também serão marcados, reduzindo complicações ao trabalhar no nível de remoção alto.
- RequireDerivedAttribute: Quando um tipo é marcado, todos os tipos derivados desse tipo serão marcados de forma semelhante.
- RequiredInterfaceAttribute: Quando um tipo é marcado, todas as implementações de interface dos tipos especificados serão marcadas.
- Atributo de membro obrigatório: Quando um tipo é marcado, todos os seus membros com [RequiredMember] serão marcados. Isso torna a remoção de código mais precisa, pois impedirá que o tipo declarante se torne não removível. Observe, no entanto, que se a classe em si não for usada, os membros também serão removidos, apesar de estarem marcados com o atributo [RequiredMember].
- Atributo RequireImplementors: Quando o tipo de interface é marcado, todos os tipos que implementam essa interface serão marcados. Dessa forma, não há necessidade de marcar cada implementação. No entanto, se a interface não for implementada em nenhum lugar da base de código, ela ainda será removida, apesar de estar marcada com o atributo [RequireImplementors].
No Unity 2020.1 e 2020.2, a ferramenta recebeu atualizações de API para corresponder ao vinculador Mono IL. Agora ele pode detectar alguns padrões de reflexão simples, o que significa que se você atualizou para o Unity 2020 LTS, terá menos motivos para usar arquivos link.xml.
Para obter mais informações sobre como o Unity 2020 LTS pode ajudar a otimizar seus fluxos de trabalho de codificação, confira esta visão geral dos recursos e a página de atualizações no manual do Unity 2020 LTS.
Como parte da nossa meta para 2021 de facilitar a entrega de compilações de alta qualidade para seus testadores e jogadores, continuamos focados em melhorar os fluxos de trabalho de remoção de código. Mais especificamente, adicionamos um novo nível de remoção gerenciado chamado Mínimo à versão 2021.2. Este será o padrão para o backend IL2CPP, portanto fique atento.

