为什么Windows VM会产生与Linux不同的浮点输出?

Jun*_*uno 5 linux windows precision cpu-architecture virtual-machine

问题

我们有多台运行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;
}
Run Code Online (Sandbox Code Playgroud)

在具有某些CPU的主机上运行相同的二进制文件时会出现此问题.

结果

在Linux上,所有机器都产生相同的输出.在Windows VM上,即使VM版本和设置相同,也会产生不同的结果.另外,每个VM上生成的二进制文件在移动到不同的主机时会产生不同的结果.即在VM2中生成但在LM1上执行的二进制文件返回与VM1生成二进制文件相同的结果.我们甚至复制了虚拟机来确认这种行为,并且确定它仍在继续.

通过上述努力,我认为它不是库差异或VM问题.至于输出,以下CPU产生以下结果:

  • 英特尔®至强(R)CPU E5-2630 0
  • 英特尔®至强(R)CPU E5-2630 v2

以前的CPU在Linux和Windows之间产生统一的结果.结果是十六进制,因为可读性比是否存在差异更重要.

num = 0x1.90a26f616699cp+0
numHalf = 0x1.90a26f616699cp-1
cosVal(num) = 0x1.7d4555e817bdcp-8
cosValHalf(numHalf) = 0x1.6b171bb5e3434p-1
Run Code Online (Sandbox Code Playgroud)

这些CPU在Windows VM上产生的结果与Linux相同:

  • 英特尔®至强(R)CPU E5-2630 v3
  • 英特尔®至强(R)CPU E3-1270 v5

我不确定这些结果是如何产生的.VS2015上的反汇编表明,无论编译哪台主机,两个程序都会生成相同的指令.

num = 0x1.90a26f616699cp+0
numHalf = 0x1.90a26f616699cp-1
cosVal(num) = 0x1.7d4555e817bdcp-8
cosValHalf(numHalf) = 0x1.6b171bb5e3435p-1
Run Code Online (Sandbox Code Playgroud)

当放在具有特定CPU的计算机上时,为什么VM上的Windows会以不同的方式处理二进制文件?

例如,看看CPU E5-2630 v2和E5-2630 v3之间差异,产生不同结果的CPU似乎支持AVX2,F16C和FMA3指令集,而前者的CPU不支持.但是,如果这是出现差异的原因,我也会认为Linux和Windows之间的结果会保持一致.此外,拆卸显示所使用的寄存器在任一芯片上仍然相同.通过调试文件并单步执行每条指令,您会认为行为类似.

总而言之,这可能是建筑的差异.关于我如何确定的任何想法?

资源

我发现以下问题对于促进跨平台一致性和使结果更具确定性的解决方案有些有用.我也花了很长时间来进行浮点比较,对于那些对这个话题感兴趣的人来说,我不能推荐它.

Ols*_*ist 1

您可以在 Linux 上将程序编译为 ELF 二进制文件,然后在 Linux 上运行它。然后,您可以将该 ELF 二进制文件复制到 Windows 系统上,并在适用于 Linux 的 Windows 子系统下运行它。两个系统的 FP 初始化应该相同。现在,您在两个系统上运行相同的浮点指令,并且浮点结果应该相同。如果不是(这不太可能),那是因为初始化不同。

您还可以在不同的体系结构和系统(FreeBSD,...)上运行此 ELF 二进制文件。结果应该都是一样的。那时,您可以排除 Windows 和 Linux 编译器 + 运行时中的体系结构 + 微体系结构和规则差异。

您还可以使用 Visual Studio 编译为 ELF 二进制文件,并针对不同的系统和体系结构重复此操作。这些结果应该与 Linux GCC/Clang ELF 相同但可能不同。