我有一个简单的程序:
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
Peter Cordes..
6
我不知道为什么ICC选择将堆栈对齐2个缓存行:
and rsp, -128 #2.1
sub rsp, 128 #2.1
那很有意思.L2缓存有一个相邻行预取器,它喜欢将成对的行(在一个128字节的对齐组中)拉成L2.但是主要的堆栈帧通常不会被大量使用.在某些程序中可能会分配重要的变量.(这也解释了设置rbp
,以保存旧的RSP,以便它可以在ANDing之后返回.gcc也使用RBP在堆栈框架中使用RBP来协调该堆栈.)
其余的是因为main()
特殊,ICC -ffast-math
默认启用.(这是英特尔"肮脏"的小秘密之一,让它可以开箱即用自动矢量化更多浮点代码.)
这包括在顶部添加代码以main
设置MXCSR(SSE状态/控制寄存器)中的DAZ/FTZ位.有关这些位的更多信息,请参阅英特尔的x86手册,但它们并不复杂:
DAZ:非正规为零:作为SSE / AVX指令的输入,非正规数被视为零.
FTZ:平为零:当舍入的结果的SSE/AVX指令的,将次正规结果被刷新到零.
相关:SSE"denormals是零"选项
(ISO C++禁止调用程序main()
,因此允许编译器在main
其自身中放置一次运行的东西而不是CRT启动文件 .gcc/clang -ffast-math
指定用于设置MXCSR的CRT启动文件中的链接.但是在编译时使用gcc/clang,它只会影响代码生成方面的优化.即将FP add/mul视为关联,当不同的临时意味着它实际上不是.这与设置DAZ/FTZ完全无关).
这里使用非正规作为次正规的同义词:具有最小指数的FP值和隐含前导位为0而不是1的有效数.即,幅度小于FLT_MIN
或DBL_MIN
的值,或者是最小的可表示的标准化浮点数/双精度值.
https://en.wikipedia.org/wiki/Denormal_number.
产生子正常结果的指令可能要慢得多:优化延迟,某些硬件中的快速路径假定规范化结果,如果结果无法规范化,则采用微代码辅助.使用perf stat -e fp_assist.any
计数这样的事件.
来自布鲁斯道森的优秀系列FP文章:这不正常 - 奇怪花车的表现.也:
为什么将0.1f改为0会使性能降低10倍?
避免在C++中使用非正规值
Agner Fog做了一些测试(参见他的microarch pdf),以及Haswell/Broadwell的报告:
下溢和次正常
当浮点运算接近下溢时,会出现次正规数.在某些情况下,处理次正规数非常昂贵,因为次正规结果由微代码异常处理.
Haswell和Broadwell在正常数字运算产生低于正常结果的所有情况下都会有大约124个时钟周期的代价.无论结果是正常还是低于正常,对于正常数和次正规数之间的乘法都存在类似的惩罚.无论结果如何,添加正常数和次正规数都不会受到惩罚.溢出,下溢,无穷大或非数字结果不会受到惩罚.
如果在MXCSR寄存器中设置"清零到零"模式和"非正规为零"模式,则可以避免对次正规数的处罚.
所以在某些情况下,现代英特尔CPU即使在低于正常值的情况下也可以避免惩罚,但是
1> Peter Cordes..:
我不知道为什么ICC选择将堆栈对齐2个缓存行:
and rsp, -128 #2.1
sub rsp, 128 #2.1
那很有意思.L2缓存有一个相邻行预取器,它喜欢将成对的行(在一个128字节的对齐组中)拉成L2.但是主要的堆栈帧通常不会被大量使用.在某些程序中可能会分配重要的变量.(这也解释了设置rbp
,以保存旧的RSP,以便它可以在ANDing之后返回.gcc也使用RBP在堆栈框架中使用RBP来协调该堆栈.)
其余的是因为main()
特殊,ICC -ffast-math
默认启用.(这是英特尔"肮脏"的小秘密之一,让它可以开箱即用自动矢量化更多浮点代码.)
这包括在顶部添加代码以main
设置MXCSR(SSE状态/控制寄存器)中的DAZ/FTZ位.有关这些位的更多信息,请参阅英特尔的x86手册,但它们并不复杂:
DAZ:非正规为零:作为SSE / AVX指令的输入,非正规数被视为零.
FTZ:平为零:当舍入的结果的SSE/AVX指令的,将次正规结果被刷新到零.
相关:SSE"denormals是零"选项
(ISO C++禁止调用程序main()
,因此允许编译器在main
其自身中放置一次运行的东西而不是CRT启动文件 .gcc/clang -ffast-math
指定用于设置MXCSR的CRT启动文件中的链接.但是在编译时使用gcc/clang,它只会影响代码生成方面的优化.即将FP add/mul视为关联,当不同的临时意味着它实际上不是.这与设置DAZ/FTZ完全无关).
这里使用非正规作为次正规的同义词:具有最小指数的FP值和隐含前导位为0而不是1的有效数.即,幅度小于FLT_MIN
或DBL_MIN
的值,或者是最小的可表示的标准化浮点数/双精度值.
https://en.wikipedia.org/wiki/Denormal_number.
产生子正常结果的指令可能要慢得多:优化延迟,某些硬件中的快速路径假定规范化结果,如果结果无法规范化,则采用微代码辅助.使用perf stat -e fp_assist.any
计数这样的事件.
来自布鲁斯道森的优秀系列FP文章:这不正常 - 奇怪花车的表现.也:
为什么将0.1f改为0会使性能降低10倍?
避免在C++中使用非正规值
Agner Fog做了一些测试(参见他的microarch pdf),以及Haswell/Broadwell的报告:
下溢和次正常
当浮点运算接近下溢时,会出现次正规数.在某些情况下,处理次正规数非常昂贵,因为次正规结果由微代码异常处理.
Haswell和Broadwell在正常数字运算产生低于正常结果的所有情况下都会有大约124个时钟周期的代价.无论结果是正常还是低于正常,对于正常数和次正规数之间的乘法都存在类似的惩罚.无论结果如何,添加正常数和次正规数都不会受到惩罚.溢出,下溢,无穷大或非数字结果不会受到惩罚.
如果在MXCSR寄存器中设置"清零到零"模式和"非正规为零"模式,则可以避免对次正规数的处罚.
所以在某些情况下,现代英特尔CPU即使在低于正常值的情况下也可以避免惩罚,但是