我有一个循环,里面有另一个循环,从数组中进行一些计算。我想使用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);
}
}
答案 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;
}
}
}
如果你可以为编译器提供一些对齐保证(尤其是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;
具有相同数量的分区(一个),但乘法次数减少一个。