如何使用SSE内在函数来减去同一数组的两个不同部分?

时间:2016-03-01 12:03:57

标签: c arrays gcc optimization sse

我有一个循环,里面有另一个循环,从数组中进行一些计算。我想使用SSE优化代码,但有很多部分让我感到困惑,其中最大的部分在问题标题中说明。

原始代码:

for (int j = 0; j < N; j++) {
    for (int i = 0; i < N; i++) {
        float kx = a[j] - a[i];
        float ky = b[j] - b[i];
        float kz = c[j] - c[i];
        float k2 = kx*kx + ky*ky + kz*kz + eps;
        float k2inv = 1.0f / sqrt(k2);
        float k6inv = k2inv * k2inv * k2inv;
        float s = m[j] * k6inv;
        ax[i] += s * kx;
        ay[i] += s * ky;
        az[i] += s * kz;    
    }
}

如何将此代码转换为SSE指令?我想出的代码如下,但在我意识到我需要减去相同数组的两个部分后,我完全被难倒了:

我的尝试:

float *x = malloc(sizeof(float) * N);
float *y = malloc(sizeof(float) * N);
float *z = malloc(sizeof(float) * N); 

for (int j = 0; j < N; j++) {
    for (int i = 0; i < N; i++) {
        __m128 rxj = _mm_load_ps(x+j);
        __m128 rxi = _mm_load_ps(x+i);
        __m128 ry = _mm_load_ps(y+j);
        __m128 ry = _mm_load_ps(y+i);
        __m128 rz = _mm_load_ps(z+j);
        __m128 rz = _mm_load_ps(z+i);
    }
}

1 个答案:

答案 0 :(得分:4)

我认为你不需要任何新的数组来进行矢量化。应用restrict关键字后,(并将sqrt更改为sqrtf),原始来源auto-vectorizes with clang 3.7 with -ffast-math(但不是gcc 5.3)。您应该只使用OpenMP pragma或其他东西来启用i或j上的自动向量化。

// auto-vectorizes with clang and icc, but not gcc :/
void ffunc(float *restrict ax, float *restrict ay, float *restrict az,
           const float *a, const float *b, const float *c,
           int N, float eps, const float *restrict m)
{
  for (int j = 0; j < N; j++) {
    for (int i = 0; i < N; i++) {
        float kx = a[j] - a[i];
        float ky = b[j] - b[i];
        float kz = c[j] - c[i];
        float k2 = kx*kx + ky*ky + kz*kz + eps;
#if 1   // better code when rsqrtps is used (with a refinement step)
        float k2inv = 1.0f / sqrtf(k2);
        float k6inv = k2inv * k2inv * k2inv;
        float s = m[j] * k6inv;
#else   // maybe better code when rcpps isn't used
        float k2sqrt = sqrtf(k2);
        float k6sqrt = k2sqrt * k2sqrt * k2sqrt;
        float s = m[j] / k6sqrt;
#endif
        ax[i] += s * kx;
        ay[i] += s * ky;
        az[i] += s * kz;    
    }
  }
}

请参阅my answer on the OP's followup question获取手动矢量化版本,该版本明显优于gcc或clang使用此版本。

如果你可以为编译器提供一些对齐保证(尤其是gcc,它喜欢使用intro / outro块来达到对齐边界,而不是使用未对齐的ops),你也可能得到更好的代码。

看起来您可以使用SSE一次执行内部循环的四次迭代。并行执行四个i值或并行执行四个j值是很好的,因为一次迭代的结果不是另一次迭代的输入。

所以你的向量中会有a[i+3] a[i+2] a[i+1] a[i](从左(高元素)到右(低元素))的向量。你将有三个向量只在外循环中改变,三个向量每次都通过内循环改变。您将广播a[j]到另一个向量的所有位置(在内部循环之外)。

实际上,您可能希望交换循环,因此累加器(ax[i] += ...)可以在整个内循环中位于寄存器中。您必须每次通过内部循环加载m[j],但每次都不必加载/存储ax[i]ay[i]az[i],这样就可以平衡。

您还应该考虑在逆sqrt中需要多少精度:有一个倒数sqrt指令,并且使用一个或两个newton-raphson迭代可能是吞吐量获胜。另外,像这样写你的来源:

    // better code *if* the compiler isn't going to use rsqrtps
    // otherwise worse code
    float k2sqrt = sqrtf(k2);        // note the sqrtf to not request double-precision sqrt
    float k6sqrt = k2sqrt * k2sqrt * k2sqrt;
    float s = m[j] / k6sqrt;

具有相同数量的分区(一个),但乘法次数减少一个。