Como o JIT destaca nosso código C # (heurística)

O embutimento é uma das otimizações mais importantes nos compiladores. Ele não apenas remove a sobrecarga da chamada, mas também abre muitas possibilidades para outras otimizações, por exemplo, dobragem constante, eliminação de código morto, etc. Além disso, às vezes inlining leva a uma diminuição no tamanho da função de chamada! Perguntei a várias pessoas se elas sabiam por quais regras as funções em C # estão embutidas, e a maioria respondeu que o JIT analisa o tamanho do código IL e destaca apenas pequenas funções com um tamanho de, digamos, até 32 bytes. Portanto, decidi escrever este post para divulgar detalhes da implementação com a ajuda de um exemplo, que mostrará várias heurísticas em ação ao mesmo tempo:




Você acha que a chamada para o construtor Volume será incluída aqui? Obviamente não. É muito grande, especialmente devido ao peso pesado de novos operadores, o que leva a um codegen bastante ousado. Vamos verificar Disasmo:



Inline! Além disso, todas as exceções e suas ramificações foram excluídas com sucesso! Você pode dizer algo no estilo de "Ah, ok, o jit é muito inteligente e fez uma análise completa de todos os candidatos a inline, olhou o que aconteceria se você passasse argumentos específicos" ou "O jit tenta incorporar tudo o que é possível, executa todas as otimizações e decide lucrativamente it or not ”(selecione combinatória e calcule a complexidade desta operação, por exemplo, para um gráfico de chamada de dez ou dois métodos).

Bem ... não, isso não é realista, especialmente em termos de tempo. Portanto, a maioria dos compiladores usa as chamadas observações e heurísticas para resolver esse problema clássico da mochila e tentar determinar seu próprio orçamento e ajustá-lo da maneira mais eficiente possível (e não, o PGO não é uma panacéia). RyuJIT tem observações positivas e negativas. Aumento positivo do coeficiente de benefício (multiplicador de benefícios). Quanto maior o coeficiente, mais código podemos inserir. Observações negativas, pelo contrário - abaixe-a ou até impeça a inclusão. Vamos ver quais observações o RyuJIT fez para o nosso exemplo:



Essas observações podem ser vistas nos logs do COMPlus_JitDump (por exemplo, no Disasmo):



Todas essas observações simples aumentaram o coeficiente de 1,0para 11.5 e ajudou a superar com êxito o orçamento do inlineer, por exemplo, o fato de passarmos um argumento constante e ser comparado com outra constante nos diz que, com um alto grau de probabilidade após o colapso das constantes, um dos ramos da condição será excluído e o código ficará menor. Ou, por exemplo, o fato de ser um construtor e ser chamado dentro do loop também é uma dica para a justificativa de que deve suavizar os requisitos de embutimento.

Além do multiplicador de benefícios, o RyuJIT também usa observações para prever o tamanho do código da função nativa e seu impacto no desempenho usando constantes mágicas em EstimateCodeSize () e EstimatePerformanceImpact () obtidas usando ML.

A propósito, você percebeu esse truque?

if ((value - 'A') > ('Z' - 'A'))

Esta é uma versão otimizada para:

if (value < 'A' || value > 'Z')

Ambas as expressões são uma e a mesma, mas no primeiro caso, temos uma unidade base e no segundo há três delas. Acontece que o inliner tem um limite estrito no número de blocos base na função e, se exceder 5, não importa o tamanho do nosso multiplicador de benefícios - o inlining é cancelado. Então eu apliquei esse truque para se encaixar nesse requisito estrito. Seria ótimo se Roslyn fizesse isso por mim.

Problema em Roslyn : github.com/dotnet/runtime/issues/13347
do PR no RyuJIT (minha tentativa incômoda): github.com/dotnet/coreclr/pull/27480

, descrevi um exemplo de por que faz sentido fazer não apenas no Jit, mas e no compilador C #.

Métodos inline e virtuais


Tudo está claro aqui, você não pode alinhar sobre o que não há informações na fase de compilação, embora se o tipo ou método estiver selado, por que não .

Incluindo e lançando exceções


Se um método nunca retorna um valor (por exemplo, simplesmente retorna throw new...), esses métodos são automaticamente marcados como auxiliares de lançamento e não se alinham. É uma maneira de varrer o complexo codegen de throw newdebaixo do tapete e apaziguar o inliner.

Atributo Inlining e [AggressiveInlining]


Nesse caso, você recomenda que o alinhador inline o método, mas você deve ser extremamente cuidadoso por dois motivos:

  • Talvez você otimize um caso e piore todos os outros (por exemplo, aprimore o caso de argumentos constantes) pelo tamanho do codegen.
  • O inlining geralmente gera um grande número de variáveis ​​temporárias que podem exceder um certo limite - o número de variáveis ​​cujo ciclo de vida o RyuJIT pode rastrear (512) e depois disso o código começará a crescer em terríveis derramamentos na pilha e a abrandar bastante. Dois bons exemplos: tyts e tyts .

Métodos inline e dinâmicos


Atualmente, esses métodos não se alinham e não se alinham : github.com/dotnet/runtime/issues/34500

Minha tentativa de escrever minha heurística


Recentemente, tentei escrever suas próprias heurísticas para ajudar aqui nessa ocasião:



Em um post anterior , mencionei que recentemente otimizei para o RyuJIT o cálculo do comprimento das seqüências constantes ( "Hello".Length -> 5vemos que, se zainlaynit), e assim, no exemplo acima ^ Validatein Test, obtemos if ("hello".Length > 10)o que é otimizado no if (5 > 10)que é otimizado na remoção de toda a condição / filial. No entanto, o inliner recusou-se a incorporar Validate:



E o principal problema aqui é que ainda não existe heurística que indique ao jit que estamos passando uma string constante System.String::get_Length, o que significa que a chamada callvirt-call provavelmente entrará em colapso em uma constante e todo o ramo será excluído. Na verdade, minha heurísticae adiciona essa observação (o único ponto negativo é que você precisa resolver todas as chamadas de chamada, o que não é muito rápido).

Existem outras restrições, cuja lista pode ser encontrada em geral aqui . E aqui você pode ler os pensamentos de um dos principais desenvolvedores do JIT sobre o design do inliner e seu artigo sobre o uso do Machine Learning para este caso.

All Articles