Ao desenvolver um sistema utilizando uma linguagem de alto nível, sempre teremos o custo do processo de compilação. Mesmo que não ganhe muito destaque, no final este será o processo que definirá a performance da aplicação.
Linguagens consideradas “mais rápidas”, como C, geram aplicações mais performáticas porque durante o processo de compilação estática são realizadas várias otimizações, visando o ambiente definido. Este é um dos motivos que em ambiente Unix drivers, bibliotecas e outras aplicações, as vezes precisam ser compiladas localmente antes de serem instaladas no sistema.
Em linguagens de alto nível, geralmente o código não é compilado diretamente para a linguagem de máquina. Ele é compilado para uma camada intermediária e dela para a linguagem de máquina. Como pode ser ilustrado pela arquitetura do .NET demostrado na imagem abaixo:
Neste tipo de arquitetura, esta camada intermediária as vezes recebe o nome de máquina virtual, já que pode emular um ambiente, o responsável por executar o código intermediário da aplciação, em qualquer sistema. Um exemplo clássico disso é a Java Virtual Machine (JVM).
E será esta máquina virtual que definirá a performance da aplicação. Quanto mais eficiente for, mais performáticas podem ser as aplicações desenvolvidas. Por esses motivos que existem máquinas virtuais, como a HHVM, que tem por objetivo otimizar as aplicações desenvolvidas em PHP (com o fim do suporte agora em 2018) e Hack.
Entrando no .NET, nele não há uma máquina virtual clara, mas como é possível ver na imagem da arquitetura acima, ele possui uma camada que é responsável por intermediar a execução do código intermediário em código de máquina.
Curso C# (C Sharp) - Introdução ao ASP.NET Core
Conhecer o cursoCoreCLR
Ao desenvolvermos uma aplicação em qualquer linguagem suportada pelo .NET Core, antes dela ser executada, o código é convertido para uma linguagem intermediária (a MSIL, que traduzido literalmente significa linguagem intermediária), e o CoreCLR ficará responsável por converter este código IL em linguagem de máquina.
É esta camada que realiza todo o processo de otimização das aplicações .NET Core. Além que garantir que o código é seguro, não gera vazamento de memória, entre outros recursos. Geralmente ele tem que decidir se irá gerar um código de rápida inicialização, com pouca otimização, ou se irá implementar uma grande otimização, atrasando a sua inicialização.
Sendo um sucessor do CLR do .NET Framework, o CoreCLR tomava esta decisão uma vez, pois se o código de máquina de um método já estivesse gerado, este sempre seria utilizado quando este método fosse referenciado no código.
A primeira vista, otimizar todos os métodos parece ser a melhor opção, mas em alguns casos, o método pode ser pouco utilizado, que não compensa o custo de otimização.
Visando melhorar a performance das aplicações .NET Core, na versão 2.1 desta biblioteca, o CoreCLR foi motificado, adicionando o suporte a tiered compilation (compilação em camadas), também conhecida como otimização adaptativa.
Tiered Compilation
Na otimização adaptiva a máquina virtual tem mais opções. Ela pode gerar um código “sujo” de rápida inicialização, e se notar que o método é muito utilizado, posteriormente pode otimizá-lo para obter máxima performance.
Na implementação deste recurso no CoreCLR, isso significa que agora um método pode ter várias compilações que podem ser substituídas durante a execução da aplicação. Desta forma, pode-se obter por uma rápida inicialização ou por otimização:
-
Rápida inicialização: Quando uma aplicação é iniciada, ela aguarda o código intermediário a ser convertido em linguagem de máquina. A tiered compilation pede para ser gerada uma compilação inicial rapidamente, mesmo que seja necessário sacrificar a qualidade da otimização. Caso o método seja chamado frequentemente, um código mais otimizado é gerado em background, e ele substituí o código gerado inicialmente, para se obter mais performance.
-
Otimização: Em uma aplicação .NET típica, a maioria do código da biblioteca é gerado de imagens pré-compiladas. O que é muito bom para uma rápida inicialização, mas as imagens pré-compiladas possuem restrições de versões e instruções de CPU que limitam a otimização. Para cada método dessas imagens que são frequentemente chamados, a tiered compilation pede para ser gerada em background uma nova compilação mais otimizada, que substituirá a versão pré-compilada.
Resultados
Você pode estar se perguntando, o quão rápido uma aplicação pode ser com este tipo de compilação. Nos testes da equipe do .NET, eles obtiveram uma melhoria de uns 35%. Em alguns casos até mais, como é possível ver nos gráficos abaixo:
Em aplicações que utilizamos internamente aqui na TreinaWeb, obtivemos uma melhora de cerca de 40%.
Testando nas suas aplicações
No momento, este recurso está em fase beta. Caso esteja utilizando a última versão de preview do .NET Core 2.2, ele já está ativo por padrão, mas no .NET Core 2.1 é necessário ativá-lo no seu ambiente.
Caso queria ativar a tiered compilation para todas as suas aplicações, adicione nas variáveis de ambiente do seu sistema a variável abaixo:
COMPlus_TieredCompilation="1"
Para ativá-la em uma aplicação que esteja sendo desenvolvida, adicione no arquivo de configuração do projeto a tag TieredCompilation
:
<PropertyGroup>
<!-- outras definições -->
<TieredCompilation>true</TieredCompilation>
</PropertyGroup>
Já para um projeto que que esteja rodando, você pode adicionar o atributo System.Runtime.TieredCompilation
no arquivo configProperties dele:
{
"runtimeOptions": {
"configProperties": {
"System.Runtime.TieredCompilation": true
}
},
"framework": {
...
}
}
Conclusão
A otimização adaptativa ainda está em fase beta no .NET Core 2.1, mas devido aos seus beneficios, é um recurso que vale a pena ser testado.
Caso encontre qualquer bug, não hesite em informar para a equipe do CoreCLR na sua página no git.