Aqui está um programa C # .NET Core 3.1 simples que chama System.Numerics.Vector2.Normalize()
um loop (com entrada idêntica a cada chamada) e imprime o vetor normalizado resultante:
using System;
using System.Numerics;
using System.Threading;
namespace NormalizeTest
{
class Program
{
static void Main()
{
Vector2 v = new Vector2(9.856331f, -2.2437377f);
for(int i = 0; ; i++)
{
Test(v, i);
Thread.Sleep(100);
}
}
static void Test(Vector2 v, int i)
{
v = Vector2.Normalize(v);
Console.WriteLine($"{i:0000}: {v}");
}
}
}
E aqui está a saída da execução desse programa no meu computador (truncado por questões de brevidade):
0000: <0.9750545, -0.22196561>
0001: <0.9750545, -0.22196561>
0002: <0.9750545, -0.22196561>
...
0031: <0.9750545, -0.22196561>
0032: <0.9750545, -0.22196561>
0033: <0.9750545, -0.22196561>
0034: <0.97505456, -0.22196563>
0035: <0.97505456, -0.22196563>
0036: <0.97505456, -0.22196563>
...
Então, minha pergunta é: por que o resultado da chamada Vector2.Normalize(v)
muda de <0.9750545, -0.22196561>
para <0.97505456, -0.22196563>
depois de 34 vezes? Isso é esperado ou é um bug no idioma / tempo de execução?
Respostas:
Então, primeiro - por que a mudança ocorre. A alteração é observada porque o código que calcula esses valores também muda.
Se entrarmos no WinDbg logo no início das primeiras execuções do código e entrarmos um pouco no código que calcula o
Normalize
vetor ed, poderemos ver o seguinte assembly (mais ou menos - cortei algumas partes):e após ~ 30 execuções (mais sobre esse número mais tarde), este seria o código:
Opcodes diferentes, extensões diferentes - SSE vs AVX e, eu acho, com opcodes diferentes, obtemos uma precisão diferente dos cálculos.
Então agora mais sobre o porquê? O .NET Core (não tem certeza da versão - assumindo a versão 3.0 - mas foi testada na 2.1) possui algo chamado "compilação em camadas do JIT". O que faz é no início produzir código que é gerado rapidamente, mas pode não ser super ideal. Somente mais tarde, quando o tempo de execução detectar que o código é altamente utilizado, ele gastará algum tempo adicional para gerar um código novo e mais otimizado. Isso é algo novo no .NET Core, portanto esse comportamento pode não ser observado anteriormente.
Também por que 34 chamadas? Isso é um pouco estranho, pois eu esperaria que isso acontecesse em torno de 30 execuções, pois esse é o limite no qual a compilação em camadas entra em ação . A constante pode ser vista no código-fonte do coreclr . Talvez haja alguma variabilidade adicional para quando ele entra em ação.
Apenas para confirmar que esse é o caso, você pode desativar a compilação em camadas configurando a variável ambiental emitindo
set COMPlus_TieredCompilation=0
e verificando a execução novamente. O efeito estranho se foi.Já existe um bug relatado para isso - Edição 1119
fonte