Então, minha pergunta é: por que o resultado da chamada Vector2.Normalize (v) muda de <0.9750545, -0.22196561> para <0.97505456, -0.22196563> após chamá-lo 34 vezes?
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):
movss xmm0,dword ptr [rax]
movss xmm1,dword ptr [rax+4]
lea rax,[rsp+40h]
movss xmm2,dword ptr [rax]
movss xmm3,dword ptr [rax+4]
mulss xmm0,xmm2
mulss xmm1,xmm3
addss xmm0,xmm1
sqrtss xmm0,xmm0
lea rax,[rsp+40h]
movss xmm1,dword ptr [rax]
movss xmm2,dword ptr [rax+4]
xorps xmm3,xmm3
movss dword ptr [rsp+28h],xmm3
movss dword ptr [rsp+2Ch],xmm3
divss xmm1,xmm0
movss dword ptr [rsp+28h],xmm1
divss xmm2,xmm0
movss dword ptr [rsp+2Ch],xmm2
mov rax,qword ptr [rsp+28h]
e após ~ 30 execuções (mais sobre esse número mais tarde), este seria o código:
vmovsd xmm0,qword ptr [rsp+70h]
vmovsd qword ptr [rsp+48h],xmm0
vmovsd xmm0,qword ptr [rsp+48h]
vmovsd xmm1,qword ptr [rsp+48h]
vdpps xmm0,xmm0,xmm1,0F1h
vsqrtss xmm0,xmm0,xmm0
vinsertps xmm0,xmm0,xmm0,0Eh
vshufps xmm0,xmm0,xmm0,50h
vmovsd qword ptr [rsp+40h],xmm0
vmovsd xmm0,qword ptr [rsp+48h]
vmovsd xmm1,qword ptr [rsp+40h]
vdivps xmm0,xmm0,xmm1
vpslldq xmm0,xmm0,8
vpsrldq xmm0,xmm0,8
vmovq rcx,xmm0
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.
C:\Users\lukas\source\repos\FloatMultiple\FloatMultiple\bin\Release\netcoreapp3.1
λ FloatMultiple.exe
0000: <0,9750545 -0,22196561>
0001: <0,9750545 -0,22196561>
0002: <0,9750545 -0,22196561>
...
0032: <0,9750545 -0,22196561>
0033: <0,9750545 -0,22196561>
0034: <0,9750545 -0,22196561>
0035: <0,97505456 -0,22196563>
0036: <0,97505456 -0,22196563>
^C
C:\Users\lukas\source\repos\FloatMultiple\FloatMultiple\bin\Release\netcoreapp3.1
λ set COMPlus_TieredCompilation=0
C:\Users\lukas\source\repos\FloatMultiple\FloatMultiple\bin\Release\netcoreapp3.1
λ FloatMultiple.exe
0000: <0,97505456 -0,22196563>
0001: <0,97505456 -0,22196563>
0002: <0,97505456 -0,22196563>
...
0032: <0,97505456 -0,22196563>
0033: <0,97505456 -0,22196563>
0034: <0,97505456 -0,22196563>
0035: <0,97505456 -0,22196563>
0036: <0,97505456 -0,22196563>
Isso é esperado ou é um bug no idioma / tempo de execução?
Já existe um bug relatado para isso - Edição 1119