Compilação antecipada do Xamarin.Mac
A compilação AOT (antecipada) é uma técnica de otimização avançada para melhorar o desempenho da inicialização. No entanto, isso também afeta o tempo de build, o tamanho do aplicativo e a execução do programa de maneiras profundas. Para entender as compensações que ele impõe, vamos nos aprofundar um pouco na compilação e execução de um aplicativo.
O código escrito em linguagens gerenciadas, como C# e F#, é compilado para uma representação intermediária chamada IL. Essa IL, armazenada em sua biblioteca e assemblies de programa, é relativamente compacta e portátil entre arquiteturas de processador. A IL, no entanto, é apenas um conjunto intermediário de instruções e, em algum momento, a IL precisará ser convertida em código de computador específico para o processador.
Há dois pontos em que esse processamento pode ser feito:
- JIT (Just in Time) – Durante a inicialização e execução do aplicativo, o IL é compilado na memória para o código do computador.
- AOT (com antecedência) – durante a compilação, o IL é compilado e gravado em bibliotecas nativas e armazenado no pacote de aplicativos.
Cada opção tem uma série de benefícios e compensações:
- JIT
- Tempo de inicialização – a compilação JIT deve ser feita na inicialização. Para a maioria dos aplicativos, isso está na ordem de 100ms, mas para aplicativos grandes desta vez pode ser significativamente mais.
- Execução – como o código JIT pode ser otimizado para o processador específico que está sendo usado, um código ligeiramente melhor pode ser gerado. Na maioria dos aplicativos, isso é alguns pontos percentuais mais rápidos no máximo.
- AOT
- Tempo de inicialização – o carregamento de dylibs pré-compilados é significativamente mais rápido do que os assemblies JIT.
- Espaço em Disco – esses dylibs podem levar uma quantidade significativa de espaço em disco no entanto. Dependendo de quais assemblies são AOTed, ele pode dobrar ou mais o tamanho da parte de código do aplicativo.
- Tempo de Build – a compilação AOT é significativamente mais lenta que o JIT e diminuirá as compilações usando-a. Essa lentidão pode variar de segundos até um minuto ou mais, dependendo do tamanho e do número de assemblies compilados.
- Ofuscação – como o IL, que é significativamente mais fácil de fazer engenharia reversa do que o código da máquina, não é necessariamente necessário que ele possa ser removido para ajudar a ofuscar o código confidencial. Isso requer a opção "Híbrido" descrita abaixo.
As opções AOT serão adicionadas ao painel Build do Mac em uma atualização futura. Até lá, habilitar o AOT requer passar um argumento de linha de comando por meio do campo "Argumentos mmp adicionais" no Build do Mac. As opções são as descritas a seguir:
--aot[=VALUE] Specify assemblies that should be AOT compiled
- none - No AOT (default)
- all - Every assembly in MonoBundle
- core - Xamarin.Mac, System, mscorlib
- sdk - Xamarin.Mac.dll and BCL assemblies
- |hybrid after option enables hybrid AOT which
allows IL stripping but is slower (only valid
for 'all')
- Individual files can be included for AOT via +
FileName.dll and excluded via -FileName.dll
Examples:
--aot:all,-MyAssembly.dll
--aot:core,+MyOtherAssembly.dll,-mscorlib.dll
Durante a execução de um aplicativo macOS, o runtime usa o código do computador carregado das bibliotecas nativas produzidas pela compilação AOT. No entanto, há algumas áreas de código, como trampolins, em que a compilação JIT pode produzir resultados significativamente mais otimizados. Isso requer que os assemblies gerenciados IL estejam disponíveis. No iOS, os aplicativos são restritos de qualquer uso da compilação JIT; essas seções de código também são compiladas com AOT.
A opção híbrida instrui o compilador a compilar essas seções (como o iOS), mas também a pressupor que a IL não estará disponível em runtime. Essa IL pode então ser despojada após o build. Conforme observado acima, o runtime será forçado a usar rotinas menos otimizadas em alguns locais.
As consequências negativas da escala AOT com os tamanhos e o número de assemblies processados. A estrutura de destino completo, por exemplo, contém uma BCL (Biblioteca de Classes Base) significativamente maior do que a Moderna e, portanto, o AOT levará muito mais tempo e produzirá pacotes maiores. Isso é composto pela incompatibilidade da estrutura de destino completa com a Vinculação, que remove o código não utilizado. Considere mover seu aplicativo para Moderno e habilitar a Vinculação para obter os melhores resultados.
Um benefício adicional do AOT vem com interações aprimoradas com cadeias de ferramentas nativas de depuração e criação de perfil. Como a grande maioria da base de código será compilada antecipadamente, ela terá nomes de função e símbolos que são mais fáceis de ler dentro de relatórios nativos de falha, criação de perfil e depuração. As funções geradas por JIT não têm esses nomes e geralmente aparecem como deslocamentos hexadecisões não nomeados que são muito difíceis de resolve.