[我们有多台运行Ubuntu且规格非常相似的计算机。我们运行了一个简单的程序来验证我们在Windows VM中看到的每一个正在运行的问题。在64位Linux计算机上使用gcc 4.8.4进行编译,在64位Windows VM上使用Visual Studio中的v140进行编译。
#include <cmath>
#include <stdio.h>
int main()
{
double num = 1.56497856262158219209;
double numHalf = num / 2.0;
double cosVal = cos(num);
double cosValHalf = cos(numHalf);
printf("num = %a\n", num);
printf("numHalf = %af\n", numHalf);
printf("cosVal(num) = %a\n", cosVal);
printf("cosValHalf(numHalf) = %a\n", cosValHalf);
//system("pause");
return 0;
}
在具有某些CPU的主机上运行相同的二进制文件时,会出现此问题。
在Linux上,所有计算机都产生相同的输出。在Windows VM上,即使VM版本和设置相同,也会产生不同的结果。另外,在每个VM上生成的二进制文件在移到不同的主机时会产生不同的结果。即在VM2中生成但在LM1上执行的二进制文件,返回的结果与VM1生成二进制文件的结果相同。我们甚至复制了VM来确认此行为,并确保它继续存在。
通过上述工作,我认为这不是库差异或VM问题。对于输出,以下CPU产生这些结果:
以前的CPU在Linux和Windows之间产生统一的结果。结果以十六进制表示,因为可读性的影响要小于是否存在差异。
num = 0x1.90a26f616699cp+0
numHalf = 0x1.90a26f616699cp-1
cosVal(num) = 0x1.7d4555e817bdcp-8
cosValHalf(numHalf) = 0x1.6b171bb5e3434p-1
这些CPU在Windows VM上产生的结果与其在Linux VM上产生的结果不同:
我不确定这些结果是如何产生的。 VS2015上的反汇编显示,无论在哪个主机上编译,两个程序均生成相同的指令。
num = 0x1.90a26f616699cp+0
numHalf = 0x1.90a26f616699cp-1
cosVal(num) = 0x1.7d4555e817bdcp-8
cosValHalf(numHalf) = 0x1.6b171bb5e3435p-1
为什么将VM上的Windows放在具有特定CPU的计算机上时对二进制文件的处理方式不同?
[看differences between the CPUs E5-2630 v2 and E5-2630 v3 for example,看来CPU产生不同的结果support AVX2, F16C and FMA3指令集,而以前的CPU没有。但是,如果这是造成差异的原因,我也认为结果将在Linux和Windows之间保持一致。同样,反汇编表明,所使用的寄存器在两个芯片上都相同。通过调试文件并逐步执行每条指令,您会认为行为将相似。
所有这些总结起来,可能是架构上的差异。关于如何确定的任何想法?
资源
[我发现以下问题对于solutions提升cross-platform consistency和使结果deterministic有所帮助。我也花了很长的时间浏览floating-point comparison,对于任何对此主题感兴趣的人都不能推荐它。
您可以在Linux上将程序编译为ELF二进制文件,然后在Linux上运行它。然后,您可以将该ELF二进制文件复制到Windows系统上,并在Linux的Windows子系统下运行。 FP初始化对于两个系统应该相同。现在,您在两个系统上都运行相同的浮点指令,并且浮点结果应该相同。如果不是(不太可能),则是因为初始化不同。
您还可以在不同的体系结构和系统(FreeBSD,...)上运行此ELF二进制文件。结果应该全部相同。那时,您可以排除Windows + Linux编译器中的体系结构+微体系结构和规则+运行时差异。