有没有一种更快的方法可以在SIMD上乘以2(不使用多重应用程序)

Is there a faster way to multiply by 2 on SIMD (without using muliplication)?

本文关键字:应用程序 SIMD 一种 有没有 方法      更新时间:2023-10-16

旧浮点的一个技巧是从不乘以2,而是将操作数与自身相加,如2*A=A+A。旧技巧是否仍然适用于SSE/SSE2/SSSE3/NEON/。。。指令集等等?我的操作数是一个向量(比如说,4个浮点,我想乘以2)。乘以3,4…怎么样。。。?

我仍在努力寻找一个例子,说明这会在哪里产生影响。我的直觉是,如果延迟是一个问题,那么在某些情况下x+x会更好,但如果延迟不是一个问题并且只影响吞吐量,那么情况可能会更糟。但首先让我们讨论一些硬件。

让我坚持使用英特尔x86处理器,因为这是我最了解的。让我们考虑以下几代硬件:Core2/Nehalem、SandyBridge/IvyBridge和Haswell/Broadwell

SIMD浮点指针算术运算的延迟和吞吐量:

  • 添加的延迟为3
  • 除了Broadwell,乘法的延迟是5
  • 在Broadwell上,乘法的延迟为3
  • 添加的吞吐量为1
  • 除了Haswell和Broadwell,乘法运算的吞吐量是1
  • 在Haswell和Broadwell上,multipliccatin的吞吐量为2
  • 在没有FMA的情况下,加法和乘法的吞吐量为2
  • FMA的延迟为5
  • FMA的吞吐量为2。这相当于4的加法和乘法吞吐量

这是我实际用于生成因子为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,但我认为它会更糟。顺便说一句,事实证明,到目前为止,我在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

编译器编写者非常聪明。对于浮点数x,2.0*x和x+x是绝对相同的。因此,编译器完全可以用x+x替换2.0*x,反之亦然,这取决于速度更快。

这可能很复杂。添加通常更快。但考虑一个处理器,它可以在每个循环中进行一次乘法和一次加法。然后,您需要将2*x和2*y替换为2*x和y+y。如果你有一个运算2*x和y+z,那么你不想用x+x代替2*x,因为你有两个加法,只能在两个循环中进行。然后是具有融合乘加的处理器,它可以在一次运算中计算a*b+c。所以你不想把2*x+y改成(x+x)+y。

最好把它留给编译器。