我有一个简单的program:
int main()
{
return 2*7;
}
GCC和clang优化启动时会生成2指令二进制,但icc会产生奇怪的输出。
push rbp #2.1
mov rbp, rsp #2.1
and rsp, -128 #2.1
sub rsp, 128 #2.1
xor esi, esi #2.1
mov edi, 3 #2.1
call __intel_new_feature_proc_init #2.1
stmxcsr DWORD PTR [rsp] #2.1
mov eax, 14 #3.12
or DWORD PTR [rsp], 32832 #2.1
ldmxcsr DWORD PTR [rsp] #2.1
mov rsp, rbp #3.12
pop rbp #3.12
ret
我不知道为什么ICC选择将堆栈对齐2个缓存行:
and rsp, -128 #2.1
sub rsp, 128 #2.1
那很有意思。 L2缓存有一个相邻行预取器,它喜欢将成对的行(在一个128字节的对齐组中)拉成L2。但是主要的堆栈帧通常不会被大量使用。在某些程序中可能会分配重要的变量。 (这也解释了如何设置rbp
,以保存旧的RSP,以便它可以在ANDing之后返回.gcc也会在与该堆栈对齐的函数中使用RBP进行堆栈帧。)
其余的是因为main()
是特殊的,ICC默认启用-ffast-math
。 (这是英特尔“肮脏”的小秘密之一,让它可以开箱即用自动矢量化更多浮点代码。)
这包括将代码添加到main
的顶部以设置MXCSR(SSE状态/控制寄存器)中的DAZ / FTZ位。有关这些位的更多信息,请参阅英特尔的x86手册,但它们并不复杂:
相关:SSE "denormals are zeros" option
(ISO C ++禁止一个程序从回调到main()
,因此允许编译器在main
本身而不是CRT启动文件中运行一次.gcc / clang与-ffast-math
指定用于链接设置MXCSR的CRT启动文件中的链接。但是当使用gcc / clang进行编译时,它只会影响代码生成方面的优化。即将FP add / mul视为关联,当不同的临时值意味着它实际上不是。这与设置DAZ / FTZ完全无关)。
这里使用非正规作为次正规的同义词:具有最小指数的FP值和隐含前导位为0而不是1的有效数。即,幅度小于FLT_MIN
or DBL_MIN
的值,最小可表示的归一化浮点/双精度。
https://en.wikipedia.org/wiki/Denormal_number。
产生子正常结果的指令可能要慢得多:优化延迟,某些硬件中的快速路径假定规范化结果,如果结果无法规范化,则采用微代码辅助。使用perf stat -e fp_assist.any
来计算此类事件。
来自布鲁斯道森的优秀系列FP文章:That’s Not Normal–the Performance of Odd Floats。也:
Agner Fog做了一些测试(见他的microarch pdf),并报道了Haswell / Broadwell:
下溢和次正常
当浮点运算接近下溢时,会出现次正规数。在某些情况下,处理次正规数非常昂贵,因为次正规结果由微代码异常处理。
Haswell和Broadwell在正常数字运算产生低于正常结果的所有情况下都会有大约124个时钟周期的代价。无论结果是正常还是低于正常,对于正常数和次正规数之间的乘法都存在类似的惩罚。无论结果如何,添加正常数和次正规数都不会受到惩罚。溢出,下溢,无穷大或非数字结果不会受到惩罚。
如果在MXCSR寄存器中设置“清零到零”模式和“非正规为零”模式,则可以避免对次正规数的处罚。
所以在某些情况下,现代英特尔CPU即使在低于正常值的情况下也可以避免惩罚,但是