Prefácio
O .NET8 foi otimizado ainda mais com base no .NET7, como a tecnologia de otimização CHRL (nome completo: CORINFO_HELP_RNGCHKFAIL). CORINFO_HELP_RNGCHKFAIL é uma verificação de limite. No .NET7 foi parcialmente otimizado, mas no .NET8 continua a otimizar, semelhante a manual Inteligente, o .NET8 está ciente de certos problemas de desempenho e pode otimizá-los. Vamos dar uma olhada neste artigo
Endereço original: .NET8 Ultimate Performance Optimization CHRL
Visão geral
O JIT verificará os limites do intervalo de matrizes e strings. Por exemplo, se o índice da matriz está dentro do intervalo de comprimento da matriz e não pode excedê-lo. Portanto, o JIT gerará etapas de verificação de limites.
public class Tests
{
private byte[] _array = new byte[8];
private int _index = 4;
public void Get() => Get(_array, _index);
[MethodImpl(MethodImplOptions.NoInlining)]
private static byte Get(byte[] array, int index) => array[index];
}
O ASM da função Get .NET7 é o seguinte:
; Tests.Get(Byte[], Int32)
sub rsp,28
cmp edx,[rcx+8]
jae short M01_L00
mov eax,edx
movzx eax,byte ptr [rcx+rax+10]
add rsp,28
ret
M01_L00:
call CORINFO_HELP_RNGCHKFAIL
int 3
A instrução cmp compara o comprimento do array deslocado em 8 posições do MT do array (tabela de métodos) com o índice do array atual.Se os dois índices forem maiores que (o último) ou iguais (jae) ao comprimento do array (o primeiro). Ele irá pular para CORINFO_HELP_RNGCHKFAIL para verificação de limite, o que pode causar uma exceção IndexOutOfRangeException que excede o intervalo do índice. Mas na verdade, o acesso a este código requer apenas dois movs, um é o índice do array e o outro é (MT (tabela de métodos) + 0x10 + índice) e retorna o valor. Portanto, há uma otimização claramente visível aqui.
O .NET8 aprendeu alguma otimização inteligente dos limites do intervalo. Em outras palavras, a verificação dos limites não é necessária em alguns lugares, portanto, a verificação dos limites pode ser otimizada para melhorar o desempenho do código. Exemplo abaixo:
private readonly int[] _array = new int[7];
public int GetBucket() => GetBucket(_array, 42);
private static int GetBucket(int[] buckets, int hashcode) =>
buckets[(uint)hashcode % buckets.Length];
.NET7 seu ASM é o seguinte:
; Tests.GetBucket()
sub rsp,28
mov rcx,[rcx+8]
mov eax,2A
mov edx,[rcx+8]
mov r8d,edx
xor edx,edx
idiv r8
cmp rdx,r8
jae short M00_L00
mov eax,[rcx+rdx*4+10]
add rsp,28
ret
M00_L00:
call CORINFO_HELP_RNGCHKFAIL
int 3
Ele ainda executa a verificação de limites, mas o JIT do .NET8 pode reconhecer automaticamente que o índice de (uint)hashcode%buckets.Length não pode exceder o comprimento da matriz, que é buckets.Length. Portanto, o .NET8 pode omitir a verificação de limites, como segue.NET8 ASM
; Tests.GetBucket()
mov rcx,[rcx+8]
mov eax,2A
mov r8d,[rcx+8]
xor edx,edx
div r8
mov eax,[rcx+rdx*4+10]
ret
Vejamos outro exemplo:
public class Tests
{
private readonly string _s = "\"Hello, World!\"";
public bool IsQuoted() => IsQuoted(_s);
private static bool IsQuoted(string s) =>
s.Length >= 2 && s[0] == '"' && s[^1] == '"';
}
IsQuoted verifica se a string tem pelo menos dois caracteres, e o início e o fim da string terminam com aspas. s[^1] significa s[s.Length - 1], que é o comprimento da string. .NET7 ASM é o seguinte:
; Tests.IsQuoted(System.String)
sub rsp,28
mov eax,[rcx+8]
cmp eax,2
jl short M01_L00
cmp word ptr [rcx+0C],22
jne short M01_L00
lea edx,[rax-1]
cmp edx,eax
jae short M01_L01
mov eax,edx
cmp word ptr [rcx+rax*2+0C],22
sete al
movzx eax,al
add rsp,28
ret
M01_L00:
xor eax,eax
add rsp,28
ret
M01_L01:
call CORINFO_HELP_RNGCHKFAIL
int 3
Observe como o .NET7 realmente executa uma verificação de limites, mas verifica apenas uma porque possui apenas um salto de instrução jae. Por que é isso? O JIT já sabe que não há necessidade de realizar uma verificação de limites em s[0], porque s.Length >= 2 já foi verificado e nenhuma verificação é necessária enquanto o índice for menor que 2 (porque o índice não tem sinal e não há números negativos). No entanto, a verificação de limite ainda é realizada em s[s.Length - 1], portanto, embora o .NET7 também seja astuto, ele não é completo o suficiente.
Vamos dar uma olhada no .NET8 que é completamente sexy
; Tests.IsQuoted(System.String)
mov eax,[rcx+8]
cmp eax,2
jl short M01_L00
cmp word ptr [rcx+0C],22
jne short M01_L00
dec eax
cmp word ptr [rcx+rax*2+0C],22
sete al
movzx eax,al
ret
M01_L00:
xor eax,eax
ret
Sem nenhuma verificação de limites, o JIT não apenas percebe que s[0] é seguro porque s.Length >= 2 foi verificado. Como verifiquei s.Length >= 2, também percebi que s.length> s.Length-1 >=1. Portanto, não há necessidade de verificação de limites, tudo é otimizado.
Você pode ver o quão poderosa é a otimização de desempenho do .NET8. Basicamente, ela esgota o mecanismo JIT e permite que ele seja otimizado ao máximo grau de inteligência.
Clique abaixo para participar do grupo de discussão técnica: