这是一个简单的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)
的结果在调用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
不同的操作码,不同的扩展名-SSE与AVX,我想,使用不同的操作码,我们可以获得不同的计算精度。
那么现在更多关于为什么? .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>
已经有一个针对此的错误报告-Issue 1119
关于c# - 为什么使用相同的输入调用Vector2.Normalize()的结果34次后会更改?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/59446883/