旧浮点数的一个技巧过去常常不会乘以2而是添加一个自身的操作数,因为2 * a = a + a。今天使用SSE / SSE2 / SSSE3 / NEON / ...指令集等的旧技巧仍然可行吗?我的操作数将是一个向量(例如,4个浮点数,我想乘以2)。如何乘以3,4 ......
答案 0 :(得分:5)
编译器编写者很聪明。对于浮点数x,2.0 * x和x + x绝对相同。因此,编译器能够用x + x替换2.0 * x,反之亦然,具体取决于速度更快。
这可能很复杂。增加通常更快。但考虑一个处理器,它可以说每个周期一个乘法和一个加法。然后你想用2 * x和y + y代替2 * x和2 * y。如果你有2 * x和y + z的操作,那么你不想用x + x替换2 * x,因为你有两个加法,你只能在两个周期内完成。然后存在具有融合乘法加法的处理器,其可以在一次操作中计算a * b + c。因此,您不希望将2 * x + y更改为(x + x)+ y。
最好留给编译器。
答案 1 :(得分:5)
我仍然试图找到一个可以带来改变的例子。我的直觉是,如果延迟是一个问题,有些情况x+x
会更好,但如果延迟不是问题,只有吞吐量很重要,那么可能会更糟。但首先让我们讨论一些硬件。
让我坚持使用英特尔x86处理器,因为这是我最了解的。让我们考虑以下几代硬件: Core2 / Nehalem,SandyBridge / IvyBridge和Haswell / Broadwell 。
SIMD 浮点指针算术运算的延迟和吞吐量:
以下是我实际用于生成因子为2 的Mandelbrot集合的情况。在主循环中,两个最关键的代码行是:
x = x*x - y*y + x0;
y = 2*xtemp*y + y0;
这里的所有变量都是SIMD(SSE或AVX)寄存器,所以我一次作用于多个像素(4个用SSE,8个用AVX用于单个浮点)。为此,我使用围绕内在函数的SIMD类。对于y
我可以改为
y = xtemp*y + xtemp*y + y0
FMA怎么样?
y = fma(2*xtemp, y, y0)
或
y = xtemp*y + fma(xtemp, y, y0);
可以尝试许多变化。我没有尝试y=xtemp*y + xtemp*y + y0
,但我认为情况会更糟。顺便提一下,FMA结果,到目前为止我在Haswell系统上实现它的方式,并没有多大帮助。我的帧速率仅使用FMA增加了15%左右,而当我使用带有SSE的4个像素到使用AVX的8个像素时,它几乎翻了一倍。
编辑:在某些情况下,我会有所作为,但要么他们不在实践中,要么就没有意义。
考虑这个案例
for(int i=0; i<n; i++) y[i] = 2*x[i];
在这种情况下,延迟无关紧要,吞吐量很重要。在Haswell和Broadwell上,乘法的吞吐量是两次加法,因此在这种情况下,似乎做x+x
会更糟,但是由于Haswell / Broadwell每个时钟周期只能写32个字节,所以它不会差。
以下是使用x+x
似乎更好的情况。
for(int i=0; i<n; i++) prod = prod * (2*x[i]);
相反,你可以这样做:
for(int i=0; i<n; i++) prod = prod * (x[i]+x[i]);
在这两种情况下,它都没有区别,因为它们受prod
乘法的延迟支配。但是,如果你将循环展开足够多次以使延迟无关紧要那么通常情况下第二种情况会更好,因为所有处理器都可以至少在每个时钟周期进行加法和乘法。虽然Haswell和Broadwell每个时钟周期可以进行两次乘法,它们也可以使用FMA在每个时钟周期进行两次乘法和加法,所以即使在它们上也会更好。
然而,在这种情况下,聪明的事情是
for(int i=0; i<n; i++) prod *= x[i];
prod *= pow(2,n);
因此没有必要x+x
代替2*x
。