다음은 간단한 C # .NET Core 3.1 프로그램 System.Numerics.Vector2.Normalize()
으로, 모든 호출에서 동일한 입력으로 루프 를 호출 하고 결과 정규화 된 벡터를 인쇄합니다.
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}");
}
}
}
그리고 내 컴퓨터에서 해당 프로그램을 실행 한 결과는 다음과 같습니다 (간단하게 잘립니다).
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>
...
내 질문은 그래서, 왜 호출 한 결과 수행 Vector2.Normalize(v)
에서의 변화 <0.9750545, -0.22196561>
에를 <0.97505456, -0.22196563>
그 34 배를 호출 한 후를? 이것이 예상됩니까, 아니면 언어 / 런타임의 버그입니까?
답변
그래서 내 질문은 Vector2.Normalize (v) 호출 결과가 34 번 호출 된 후 <0.9750545, -0.22196561>에서 <0.97505456, -0.22196563>으로 변경되는 이유는 무엇입니까?
먼저 변경이 발생하는 이유. 해당 값을 계산하는 코드도 변경되기 때문에 변경 사항이 관찰됩니다.
코드의 첫 번째 실행에서 WinDbg를 일찍 시작하고 Normalize
ed 벡터 를 계산하는 코드로 조금 내려 가면 다음 어셈블리를 볼 수 있습니다.
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]
~ 30 회 실행 후 (이 번호에 대해서는 나중에 더) 코드는 다음과 같습니다.
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
다른 opcode, 다른 확장-SSE vs AVX 그리고 다른 opcode로 계산의 정밀도가 다릅니다.
이제 그 이유에 대해 더 자세히? .NET Core (버전에 대해서는 확실하지 않지만 3.0으로 가정하지만 2.1에서 테스트 됨)에는 “계층 JIT 컴파일”이라는 것이 있습니다. 처음에는 빠르게 생성되는 코드를 생성하지만 최적이 아닐 수도 있습니다. 나중에 런타임에서 코드가 많이 사용됨을 감지하면 새롭고 최적화 된 코드를 생성하는 데 시간이 더 걸립니다. 이것은 .NET Core의 새로운 기능이므로 이러한 동작은 이전에 관찰되지 않을 수 있습니다.
또한 왜 34 번의 전화를합니까? 계층화 된 컴파일이 시작되는 임계 값이므로 약 30 회의 실행이 발생할 것으로 예상되므로 약간 이상합니다. 상수는 coreclr 의 소스 코드에서 볼 수 있습니다 . 시작될 때 약간의 추가 변동이있을 수 있습니다.
이런 경우인지 set COMPlus_TieredCompilation=0
확인하기 위해 실행을 다시 실행 하고 확인하여 환경 변수를 설정하여 계층화 된 컴파일을 비활성화 할 수 있습니다 . 이상한 효과가 사라졌습니다.
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>
이것이 예상됩니까, 아니면 언어 / 런타임의 버그입니까?
이미보고 된 버그가 있습니다- 문제 1119