在编译行中添加"-march=native"英特尔编译器标志会导致KNL上的浮点异常。

3

我有一份代码,我在拥有64个核心的Intel Xeon Phi Knights Landing(KNL)7210处理器上运行它(它是原生模式下的PC),并使用Intel c ++编译器(icpc)版本17.0.4。同时,我在Intel Core 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;
}
...

粒子p[N]; - 一个由浮点数构成的结构体数组。N为粒子数量上限。

如果删除标志-march=native或将其替换为-march=knl-march=core-avx2,则一切正常。这个标志对程序有负面影响,但我不知道是什么。

我在互联网上找到了 (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上运行代码时,是否可以用-march=knl替换标志-march=native(在i7上一切正常)?它们是等效的吗?

  3. 如果使用英特尔编译器,所写的标志集是否最优以获得最佳性能?

正如Peter Cordes所说,当程序在GDB中抛出浮点异常时,我在这里放置了汇编输出:

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    $0xffffffffffffffc0,%rsp
0x000000000040da78 <+8>:    sub    $0x100,%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    $0x77359400,%ecx
0x000000000040da97 <+39>:   xor    %eax,%eax
0x000000000040da99 <+41>:   movabs $0x5deece66d,%rdx
0x000000000040daa3 <+51>:   mov    %ecx,%ecx
0x000000000040daa5 <+53>:   imul   %rdx,%rcx
0x000000000040daa9 <+57>:   add    $0xb,%rcx
0x000000000040daad <+61>:   mov    %ecx,0x9a3b00(,%rax,8)
0x000000000040dab4 <+68>:   mov    %ecx,%esi
0x000000000040dab6 <+70>:   imul   %rdx,%rsi
0x000000000040daba <+74>:   add    $0xb,%rsi
0x000000000040dabe <+78>:   mov    %esi,0x9e3d00(,%rax,8)
0x000000000040dac5 <+85>:   mov    %esi,%edi
0x000000000040dac7 <+87>:   imul   %rdx,%rdi
0x000000000040dacb <+91>:   add    $0xb,%rdi
0x000000000040dacf <+95>:   mov    %edi,0xa23f00(,%rax,8)
0x000000000040dad6 <+102>:  mov    %edi,%r8d
0x000000000040dad9 <+105>:  imul   %rdx,%r8
0x000000000040dadd <+109>:  add    $0xb,%r8
0x000000000040dae1 <+113>:  mov    %r8d,0xa64100(,%rax,8)
0x000000000040dae9 <+121>:  mov    %r8d,%r9d
0x000000000040daec <+124>:  imul   %rdx,%r9
0x000000000040daf0 <+128>:  add    $0xb,%r9
0x000000000040daf4 <+132>:  mov    %r9d,0xaa4300(,%rax,8)
0x000000000040dafc <+140>:  mov    %r9d,%r10d
0x000000000040daff <+143>:  imul   %rdx,%r10
0x000000000040db03 <+147>:  add    $0xb,%r10
0x000000000040db07 <+151>:  mov    %r10d,0x9a3b04(,%rax,8)
0x000000000040db0f <+159>:  mov    %r10d,%r11d
0x000000000040db12 <+162>:  imul   %rdx,%r11
0x000000000040db16 <+166>:  add    $0xb,%r11
0x000000000040db1a <+170>:  mov    %r11d,0x9e3d04(,%rax,8)
0x000000000040db22 <+178>:  mov    %r11d,%ecx
0x000000000040db25 <+181>:  imul   %rdx,%rcx
0x000000000040db29 <+185>:  add    $0xb,%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 的输出:

$2 = {3, 3, 3, 3, 3, 3, 3, 3}

4) p $zmm0.v16_float 的输出:

gdb) p $zmm0.v16_float
$3 = {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。自从开始程序调试以来,我就一直在使用它,因为我们的代码中存在错误(浮点异常),必须加以纠正。


1
你总是在i7上编译,然后在KNL上运行吗?在i7上,-march=native与使用-march=skylake或其他类似的编译效果相同。Native意味着生成的代码假定它正在运行的机器与编译它的机器相同,因此您不应该期望它能在其他机器上运行。 - Peter Cordes
如果您需要最快的可执行文件,并从代码中获取最后一点性能,那么您应该将代码编译两次(或者您拥有的任何不同平台数量)- 一次本地为KNL,另一次本地为i7。 - Severin Pappadeux
代码在同一台机器上编译和运行:在i7上工作时,在i7上运行;在KNL上工作时,在KNL上运行。我只想说这个标志在i7上有效,在KNL上无效。当然,当在i7上启动可执行文件时,我会在i7上编译它;当在KNL上启动时,则在KNL上编译。 - And
在那个点上rd == 0.0还是什么?你的KNL系统上是否有FP异常未屏蔽?不同的编译器选项可能会产生不同的FP行为(Intel的编译器启用了等效于-ffast-math,因此它可能使用AVX512ER(仅限KNL)VRSQRT28PS来获得高精度快速逆平方根近似值,比普通AVX512的vrsqrt14ps或普通SSE/AVX1的12位vrsqrtps要好得多)。 - Peter Cordes
哪个汇编指令会触发FP异常,为什么?(寄存器中的哪些值解释了它引发异常?那么寄存器中有什么值,MXCSR是什么?)通常情况下,在Linux上只有整数除法才会导致SIGFPE,因为FP异常被屏蔽了。查看MXCSR将告诉我们是否取消了FP异常的屏蔽。 - Peter Cordes
显示剩余7条评论
1个回答

1
你使用了feenableexcept来取消一些FP异常,因此会导致创建无效临时结果的优化会使你的程序崩溃。
Intel编译器使用-fp-model fast=2(例如gcc -ffast-math)假定FP异常被屏蔽,因此它可能会在一些临时计算中的一些SIMD元素中引发FE_INVALID,只要最终结果是正确的(例如,混合以修复倒数平方根错误的元素)。我认为这就是问题所在。
如果您发布实际指令的反汇编代码(而不是该函数开头的一堆整数乘法),我们可以确定到底是哪个优化导致了哪个无效的临时结果,但通常情况下,在编译打开FP异常的构建时,您需要使用较少激进的FP选项。
根据Intel文档
- fp-model fast[=1|2] 或 /fp:fast[=1|2] 浮点异常语义默认情况下被禁用,因为在同一编译中无法同时指定快速和except。要启用异常语义,必须显式指定另一个关键字(有关详细信息,请参见其他关键字描述)。
如果您希望编译器尊重FP异常是可见副作用这一事实,则需要使用-fp-model except。 默认情况下不会开启此功能。
如果您要调用修改FP环境的函数,ISO C建议您使用#pragma STDC FENV_ACCESS ON,否则对FP环境的修改将不具有“意义”。 “否则实现可以自由地假定浮点控制模式始终是默认模式,并且浮点状态标志永远不会被测试或修改。” 我不确定启用异常是否真的重要。只要在程序启动时执行一次,否则计算发生在启用异常之前还是之后就很重要了。
同样地,对于gcc编译器,-ffast-math包含了-fno-trapping-math选项,它向编译器保证浮点指令不会引发SIGFPE错误,只会在MXCSR寄存器中设置粘性状态位并产生NaN(无效)、+-Infinity(溢出)或者0.0(下溢)。

修改浮点数运算环境的函数 - 这意味着抛出浮点异常以修改浮点数环境的标志位吗?我尝试写 #pragma FENV_ACCESS ON,#pragma STDC FENV_ACCESS ON(https://en.cppreference.com/w/cpp/preprocessor/impl),但编译器会输出:警告#161:无法识别#pragma。 - And
@And:不,feenableexcept是一种通过更改异常掩码来修改FP环境的函数,因此在该调用之后进行的计算行为会有所不同。我不确定这是否重要,或者仅更改舍入模式是否重要。(您需要阻止编译器在fesetenv跨越计算时重新排序,因为在更改舍入模式之前或之后计算某些内容很重要。) - Peter Cordes
我还有一个问题,如果要在KNL上运行代码,标志“-march=native”和“-march=knl”是否相等(据我所见,它们并不相等,但为什么?)你知道吗? - And
1
@And:当你在KNL上编译时,我认为它们对于gcc来说是等效的。我以为ICC也是一样的,但我不确定。 - Peter Cordes
非常感谢您,Peter Cordes! - And

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接