[c#] Vector2.Normalize ()의 결과가 동일한 입력으로 34 번 호출 된 후 왜 변경됩니까?

다음은 간단한 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를 일찍 시작하고 Normalizeed 벡터 를 계산하는 코드로 조금 내려 가면 다음 어셈블리를 볼 수 있습니다.

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


답변