考虑这个简单的代码:
#include <complex.h>
complex float f(complex float x) {
return x*x;
}
如果使用英特尔编译器使用-O3 -march=core-avx2 -fp-model strict
编译它,则会得到:
f:
vmovsldup xmm1, xmm0 #3.12
vmovshdup xmm2, xmm0 #3.12
vshufps xmm3, xmm0, xmm0, 177 #3.12
vmulps xmm4, xmm1, xmm0 #3.12
vmulps xmm5, xmm2, xmm3 #3.12
vaddsubps xmm0, xmm4, xmm5 #3.12
ret
这比从gcc
和clang
得到的代码简单得多,而且比在线复制数字的代码简单得多。例如,它没有明确地用于处理复杂的NaN或无穷大。
这个程序集是否符合C99复数乘法的规范?
答案 0 :(得分:20)
代码不符合。
附件G第5.1节第4段内容
*
和/
运算符满足所有实数,虚数和复数操作数的以下无穷大属性:- 如果一个操作数是无穷大而另一个操作数是非零有限数或无穷大,那么*运算符的结果是无穷大;
因此,如果 z = a * i b是无限的且 w = c * i d是无限的,数字 z * w 必须是无限的。
同一附件第3节第1段定义了复数无限的含义:
具有至少一个无限部分的复数或虚数值被视为无穷大(即使其另一部分是NaN)。
所以如果a或b都是 z 是无限的 这确实是一个明智的选择,因为它反映了数学框架 1 。
但是,如果我们让 z =∞+ i ∞(无限值)和 w = i ∞(和无限值)英特尔代码的结果是 z * w = NaN + i NaN由于∞·0中间体< SUP> 2
这足以将其标记为不合格。
我们可以通过查看第一个引用的脚注(此处未报告脚注)进一步确认这一点,它提到了CX_LIMITED_RANGE
pragma指令。
第7.3.4节,第1段内容
复数乘法,除法和绝对值的通常数学公式是有问题的,因为它们对无穷大的处理以及由于不适当的溢出和下溢。
CX_LIMITED_RANGE
编译指示可用于通知实现(状态为''on'')通常的数学公式[产生NaN]是可接受的。
在这里,标准委员会正试图减轻复杂乘法(和除法)的巨大工作量 In fact GCC has a flag to control this behaviour:
-fcx-limited-range
启用时,此选项表示执行复杂除法时不需要范围缩减步骤。此外,没有检查复数乘法或除法的结果是否为NaN + I * NaN,试图在这种情况下挽救这种情况。
默认值为
-fno-cx-limited-range
,但由-ffast-math
启用。
此选项控制ISO C99CX_LIMITED_RANGE
编译指示的默认设置。
单独使用makes GCC generate slow code and additional checks这个选项,没有它生成的代码与英特尔的代码有相同的缺陷(我将源代码翻译成C ++)
f(std::complex<float>):
movq QWORD PTR [rsp-8], xmm0
movss xmm0, DWORD PTR [rsp-8]
movss xmm2, DWORD PTR [rsp-4]
movaps xmm1, xmm0
movaps xmm3, xmm2
mulss xmm1, xmm0
mulss xmm3, xmm2
mulss xmm0, xmm2
subss xmm1, xmm3
addss xmm0, xmm0
movss DWORD PTR [rsp-16], xmm1
movss DWORD PTR [rsp-12], xmm0
movq xmm0, QWORD PTR [rsp-16]
ret
没有它,代码就是
f(std::complex<float>):
sub rsp, 40
movq QWORD PTR [rsp+24], xmm0
movss xmm3, DWORD PTR [rsp+28]
movss xmm2, DWORD PTR [rsp+24]
movaps xmm1, xmm3
movaps xmm0, xmm2
call __mulsc3
movq QWORD PTR [rsp+16], xmm0
movss xmm0, DWORD PTR [rsp+16]
movss DWORD PTR [rsp+8], xmm0
movss xmm0, DWORD PTR [rsp+20]
movss DWORD PTR [rsp+12], xmm0
movq xmm0, QWORD PTR [rsp+8]
add rsp, 40
ret
和__mulsc3
function几乎与标准C99推荐的复数乘法相同
它包括上述检查。
1 其中数字的模数从实例扩展| z |对于复杂的“‖z”,保持无限的定义是无限制的结果。简单地说,在复平面中存在无限值的整个圆周,并且只需要一个“坐标”就可以获得无限模数。
2 如果我们记得 z = NaN + i ∞或 z =∞,情况会变得更糟+ i NaN是有效的无限值
答案 1 :(得分:10)
我从-O2 -march=core-avx2 -ffast-math
处的clang 3.8获得了类似但不完全相同的代码:我对最近的x86浮点功能并不十分熟悉,但我认为它正在进行相同的计算但是使用不同的指令来重排寄存器中的值。
f:
vmovshdup %xmm0, %xmm1 # xmm1 = xmm0[1,1,3,3]
vaddss %xmm0, %xmm0, %xmm2
vmulss %xmm2, %xmm1, %xmm2
vmulss %xmm1, %xmm1, %xmm1
vfmsub231ss %xmm0, %xmm0, %xmm1
vinsertps $16, %xmm2, %xmm1, %xmm0 # xmm0 = xmm1[0],xmm2[0],xmm1[2,3]
retq
GCC 6.3,具有相同的选项,似乎再次进行相同的计算,但以第三种方式改变值:
f:
vmovq %xmm0, -8(%rsp)
vmovss -4(%rsp), %xmm2
vmovss -8(%rsp), %xmm0
vmulss %xmm2, %xmm2, %xmm1
vfmsub231ss %xmm0, %xmm0, %xmm1
vmulss %xmm2, %xmm0, %xmm0
vmovss %xmm1, -16(%rsp)
vaddss %xmm0, %xmm0, %xmm0
vmovss %xmm0, -12(%rsp)
vmovq -16(%rsp), %xmm0
ret
如果没有-ffast-math
,两个编译器都会生成截然不同的代码 来检查NaN,至少。
我从中得出结论,即使使用-fp-model strict
,英特尔的编译器也不生成完全符合IEEE标准的复数乘法。可能还有一些其他命令行开关可以生成完全符合IEEE标准的代码。
这是否有资格违反 C99 取决于英特尔的编译器是否符合附件F和G(其中规定了C的实现对提供IEEE兼容性的意义)真实的和复杂的算术),如果是,你必须给出什么命令行选项来获得符合模式。