在编译行中添加“-march=native”intel 编译器标志会导致 KNL 出现浮点异常

adding "-march=native" intel compiler flag to the compilation line leads to a floating point exception on KNL

我有一个代码,我在 Intel Xeon Phi Knights Landing (KNL) 7210(64 核)处理器(它是一台 PC,本机模式)上启动并使用 Intel c++ 编译器 (icpc) 版本 17.0。 4.我也在英特尔酷睿 i7 处理器上启动了相同的代码,其中 icpc 的版本是 17.0.1。更正确地说,我在我正在启动它的机器上编译代码(在 i7 上编译并在 i7 上启动,对于 KNL 也是如此)。我从不在一台机器上制作二进制文件并将其带到另一台机器上。使用 OpenMP 对循环进行并行化和矢量化。为了获得最佳性能,我使用了英特尔编译器标志:

-DCMAKE_CXX_COMPILER="-march=native -mtune=native -ipo16 -fp-model fast=2 -O3 -qopt-report=5 -mcmodel=large"

在 i7 上一切正常。但在 KNL 上,代码与 -march=native 一起工作,如果添加此选项,程序会立即抛出浮点异常。如果只用标志“-march=native”编译,情况是一样的。如果使用gdb,它指向代码段的pp+=alpha/rd行:

...

the code above is run in 1 thread

double K1=0.0, P=0.0;

#pragma omp parallel for reduction(+:P_x,P_y,P_z, K1,P)
for(int i=0; i<N; ++i)
{
  P_x+=p[i].vx*p[i].m;
  P_y+=p[i].vy*p[i].m;
  P_z+=p[i].vz*p[i].m;
  K1+=p[i].vx*p[i].vx+p[i].vy*p[i].vy+p[i].vz*p[i].vz;
  float pp=0.0;
#pragma simd reduction(+:pp)
  for(int j=0; j<N; ++j) if(i!=j)
  {
    float rd=sqrt((p[i].x-p[j].x)*(p[i].x-p[j].x)+(p[i].y-p[j].y)*(p[i].y-p[j].y)+(p[i].z-p[j].z)*(p[i].z-p[j].z));
    pp+=alpha/rd;
  }
  P+=pp;
}
...

Particle p[N]; - 一个粒子数组,Particle是一个浮点数结构。 N - 最大粒子数。

如果删除标志 -march=native 或将其替换为 -march=knl-march=core-avx2,一切正常。这个标志对程序做了坏事,但是什么 - 我不知道。

我在 Internet (https://software.intel.com/en-us/articles/porting-applications-from-knights-corner-to-knights-landing, https://math-linux.com/linux/tip-of-the-day/article/intel-compilation-for-mic-architecture-knl-knights-landing) 中发现应该使用标志:-xMIC-AVX512。我尝试使用此标志和 -axMIC-AVX512,但它们给出了相同的错误。

所以,我想问的是:

  1. 为什么 -march=native-xMIC-AVX512 不起作用而 -march=knl 起作用; -xMIC-AVX512 是否包含在 KNL 的 -march=native 标志中?

  2. 当我在 KNL 上启动代码时(在 i7 上一切正常),我可以用 -march=knl 替换标志 -march=native 吗,它们是等价的吗?

  3. 如果使用 Intel 编译器,为获得最佳性能而编写的标志集是否是最佳的?

正如 Peter Cordes 所说,当程序在 GDB 中抛出浮点异常时,我将汇编程序输出放在这里: 1)(gdb)disas的输出:

Program received signal SIGFPE, Arithmetic exception.
0x000000000040e3cc in randomizeBodies() ()
Missing separate debuginfos, use: debuginfo-install libgcc-4.8.5- 
16.el7.x86_64 libstdc++-4.8.5-16.el7.x86_64
(gdb) disas
Dump of assembler code for function _Z15randomizeBodiesv:
0x000000000040da70 <+0>:    push   %rbp
0x000000000040da71 <+1>:    mov    %rsp,%rbp
0x000000000040da74 <+4>:    and    [=12=]xffffffffffffffc0,%rsp
0x000000000040da78 <+8>:    sub    [=12=]x100,%rsp
0x000000000040da7f <+15>:   vpxor  %xmm0,%xmm0,%xmm0
0x000000000040da83 <+19>:   vmovups %xmm0,(%rsp)
0x000000000040da88 <+24>:   vxorpd %xmm5,%xmm5,%xmm5
0x000000000040da8c <+28>:   vmovq  %xmm0,0x10(%rsp)
0x000000000040da92 <+34>:   mov    [=12=]x77359400,%ecx
0x000000000040da97 <+39>:   xor    %eax,%eax
0x000000000040da99 <+41>:   movabs [=12=]x5deece66d,%rdx
0x000000000040daa3 <+51>:   mov    %ecx,%ecx
0x000000000040daa5 <+53>:   imul   %rdx,%rcx
0x000000000040daa9 <+57>:   add    [=12=]xb,%rcx
0x000000000040daad <+61>:   mov    %ecx,0x9a3b00(,%rax,8)
0x000000000040dab4 <+68>:   mov    %ecx,%esi
0x000000000040dab6 <+70>:   imul   %rdx,%rsi
0x000000000040daba <+74>:   add    [=12=]xb,%rsi
0x000000000040dabe <+78>:   mov    %esi,0x9e3d00(,%rax,8)
0x000000000040dac5 <+85>:   mov    %esi,%edi
0x000000000040dac7 <+87>:   imul   %rdx,%rdi
0x000000000040dacb <+91>:   add    [=12=]xb,%rdi
0x000000000040dacf <+95>:   mov    %edi,0xa23f00(,%rax,8)
0x000000000040dad6 <+102>:  mov    %edi,%r8d
0x000000000040dad9 <+105>:  imul   %rdx,%r8
0x000000000040dadd <+109>:  add    [=12=]xb,%r8
0x000000000040dae1 <+113>:  mov    %r8d,0xa64100(,%rax,8)
0x000000000040dae9 <+121>:  mov    %r8d,%r9d
0x000000000040daec <+124>:  imul   %rdx,%r9
0x000000000040daf0 <+128>:  add    [=12=]xb,%r9
0x000000000040daf4 <+132>:  mov    %r9d,0xaa4300(,%rax,8)
0x000000000040dafc <+140>:  mov    %r9d,%r10d
0x000000000040daff <+143>:  imul   %rdx,%r10
0x000000000040db03 <+147>:  add    [=12=]xb,%r10
0x000000000040db07 <+151>:  mov    %r10d,0x9a3b04(,%rax,8)
0x000000000040db0f <+159>:  mov    %r10d,%r11d
0x000000000040db12 <+162>:  imul   %rdx,%r11
0x000000000040db16 <+166>:  add    [=12=]xb,%r11
0x000000000040db1a <+170>:  mov    %r11d,0x9e3d04(,%rax,8)
0x000000000040db22 <+178>:  mov    %r11d,%ecx
0x000000000040db25 <+181>:  imul   %rdx,%rcx
0x000000000040db29 <+185>:  add    [=12=]xb,%rcx
0x000000000040db2d <+189>:  mov    %ecx,0xa23f04(,%rax,8) 

2) p $mxcsr 的输出:

(gdb) p $mxcsr
1 = [ ZE PE DAZ DM PM FZ ]

3) p $ymm0.v8_float的输出:

 = {3, 3, 3, 3, 3, 3, 3, 3}

4) p $zmm0.v16_float的输出:

gdb) p $zmm0.v16_float
 = {3 <repeats 16 times>}.

我还应该提到,为了检测浮点异常,我使用了标准

void handler(int sig)
{
  printf("Floating Point Exception\n");
  exit(0);
}
...
int main(int argc, char **argv)
{
  feenableexcept(FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW);
  signal(SIGFPE, handler);
  ...
}

我应该强调 我已经在使用 feenableexcept 当我得到这个错误时。我从程序调试开始就使用它,因为我们在代码中有错误(浮点异常)并且必须更正它们。

您使用 feenableexcept 来揭露一些 FP 异常,因此创建无效临时结果的优化将使您的程序崩溃。

带有 -fp-model fast=2 的英特尔编译器,如 gcc -ffast-math,假定 FP 异常被屏蔽,因此它可以在某些临时计算中的某些 SIMD 元素中导致 FE_INVALID,只要一切正常最后出来(例如混合以修复 recip-sqrt 出错的元素)。我假设这就是这里发生的事情。

如果您 post 反汇编出错的实际指令(而不是在该函数的最开始进行一堆整数相乘),我们可以准确地找出是什么优化导致了什么临时无效,但是在通常,在编译打开 FP 异常的构建时,您需要使用不太激进的 FP 选项。


根据Intel's documentation

-fp-model fast[=1|2] or /fp:fast[=1|2]

Floating-point exception semantics are disabled by default and they cannot be enabled because you cannot specify fast and except together in the same compilation. To enable exception semantics, you must explicitly specify another keyword (see other keyword descriptions for details).

如果您希望编译器尊重 FP 异常是 可见 副作用这一事实,则需要使用 -fp-model except 默认情况下

如果您要调用修改 FP 环境的函数,ISO C 说您应该使用 #pragma STDC FENV_ACCESS ON,否则,对 FP 环境的修改就没有“意义”。 “否则,实现可以自由地假设浮点控制模式始终是默认模式,并且永远不会测试或修改浮点状态标志。”我不确定启用异常是否真的很重要。可能并不重要,只要您在程序启动时执行一次,否则计算是否在启用异常之前或之后发生就很重要了。


与 gcc 类似,-ffast-math 包括 -fno-trapping-math,它向编译器承诺 FP 指令不会引发 SIGFPE,只是在 MXCSR 中静默设置粘性状态位并产生 NaN(无效), +-无穷大(溢出),或 0.0(下溢)。