Compilação antecipada do Xamarin.Mac

Visão geral

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.

Habilitando o AOT

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

AOT híbrido

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.

Considerações adicionais

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.