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/13347do PR no RyuJIT (minha tentativa incômoda): github.com/dotnet/coreclr/pull/27480Lá , 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 new
debaixo 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/34500Minha 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 -> 5
vemos que, se zainlaynit), e assim, no exemplo acima ^ Validate
in 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.