考虑到这一功能,
float mulHalf(float x) {
return x * 0.5f;
}
以下函数使用normal个输入/输出生成相同的结果.
float mulHalf_opt(float x) {
__m128i e = _mm_set1_epi32(-1 << 23);
__asm__ ("paddd\t%0, %1" : "+x"(x) : "xm"(e));
return x;
}
这是-O3 -ffast-math
的程序集输出.
mulHalf:
mulss xmm0, DWORD PTR .LC0[rip]
ret
mulHalf_opt:
paddd xmm0, XMMWORD PTR .LC1[rip]
ret
-ffast-math
启用-ffinite-math-only
,"假定参数和结果不是NaN或+-Infs"[1].
因此,如果mulHalf
的编译输出在-ffast-math
的容差范围内生成更快的代码,则最好使用paddd
和-ffast-math
.
我从Intel Intrinsics Guide中得到了以下表格.
(MULSS)
Architecture Latency Throughput (CPI)
Skylake 4 0.5
Broadwell 3 0.5
Haswell 5 0.5
Ivy Bridge 5 1
(PADDD)
Architecture Latency Throughput (CPI)
Skylake 1 0.33
Broadwell 1 0.5
Haswell 1 0.5
Ivy Bridge 1 0.5
显然,paddd
是一个更快的指令.然后我想可能是因为整数和浮点单元之间的旁路延迟.
This answer显示来自Agner Fog的表格.
Processor Bypass delay, clock cycles
Intel Core 2 and earlier 1
Intel Nehalem 2
Intel Sandy Bridge and later 0-1
Intel Atom 0
AMD 2
VIA Nano 2-3
看到这一点,paddd
似乎仍然是赢家,尤其是在Sandy Bridge之后的CPU上,但为最近的CPU指定-march
只是将mulss
改为vmulss
,这具有类似的延迟/吞吐量.
为什么GCC和Clang不将乘法优化为2^n,浮点为paddd
,甚至-ffast-math
?