(如何)我可以使用 openmp 向量化 `std::complex<double>` 吗?

(How) Can I vectorize `std::complex<double>` using openmp?

我想使用矢量化优化我的应用程序。更具体地说,我想对 std::complex<double> 类型的数学运算进行矢量化。然而,这似乎相当困难。考虑以下示例:

#define TEST_LEN 100
#include <algorithm>
#include <complex>
typedef std::complex<double> cmplx;
using namespace std::complex_literals;

#pragma omp declare simd
cmplx add(cmplx a, cmplx b)
{
     return a + b;
}

#pragma omp declare simd
cmplx mult(cmplx a, cmplx b)
{
     return a * b;
}

void k(cmplx *x, cmplx *&y, int i0, int N)
{
    #pragma omp for simd
    for (int i = i0; i < N; i++)
        y[i] = add(mult(-(1i + 1.0), x[i]), 1i);
}

int main(int argc, char **argv)
{
    cmplx *x = new cmplx[TEST_LEN];
    cmplx *y = new cmplx[TEST_LEN];

    for (int i = 0; i < TEST_LEN; i++)
        x[i] = 0;

    for (int i = 0; i < TEST_LEN; i++)
    {
        int N = std::min(4, TEST_LEN - i);
        k(x, y, i, N);
    }

    delete[] x;
    delete[] y;

    return 1;
}

我正在使用 g++ 编译器。对于这段代码,编译器给出以下警告:

warning: unsupported return type 'cmplx' {aka 'std::complex'} for simd

包含 multadd 函数的行。 似乎无法像这样矢量化 std::complex<double> 类型。

有没有其他方法可以实现这一点?

不容易。当您在接下来的 N 个步骤中具有相同行为方式的值时,SIMD 工作得很好。因此,例如考虑一个二维向量数组:

X Y X Y X Y X Y

如果我们在这里做一个向量加法运算,

X Y X Y X Y X Y
+ + + + + + + +
X Y X Y X Y X Y

编译器会很好地向量化该操作。然而,如果我们想要对 X 和 Y 值做一些不同的事情,内存布局就会成为 SIMD 的问题:

X Y X Y X Y X Y
+ / + / + / + /
X Y X Y X Y X Y

如果你考虑乘法的例子:

(a + bi) (c + di) = (ac - bd)  (ad + bc)i

操作突然在 SIMD 通道之间跳跃,这几乎会扼杀任何合适的矢量化。

快速看一下这个神螺栓:https://godbolt.org/z/rnVVgl 加法归结为一些 vaddps 指令(一次处理 8 个浮点数)。 Multiply 最终使用 vfmadd231ss 和 vmulss(它们一次都在 1 个浮点数上工作)。

唯一简单自动向量化复杂代码的方法是将实部和虚部分成 2 个数组:

struct ComplexArray {
  float* real;
  float* imaginary;
};

在这个 godbolt 中,您可以看到编译器现在正在使用 vfmadd213ps 指令(所以再次回到一次处理 8 个浮点数)。

https://godbolt.org/z/Ostaax