如何使用 SSE Intrinsics 减去同一数组的两个不同部分?
How to use SSE Intrinsics to subtract two different parts of the same array?
我有一个循环,其中有另一个循环从数组中进行一些计算。我想使用 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);
}
}
我认为您不需要任何新数组来向量化。应用 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 或 clang 的版本。
如果你能给编译器一些对齐保证,你也可能会得到更好的代码(特别是对于 gcc,它喜欢做 intro/outro 块来达到对齐边界,而不是使用未对齐的操作。)
看来您可以使用 SSE 一次对内循环进行四次迭代。并行执行四个 i
值或并行执行四个 j
值是可以的,因为一次迭代的结果不是另一次迭代的输入。
所以你将有一个带有 a[i+3] a[i+2] a[i+1] a[i]
的矢量(在你的矢量中从左(高元素)到右(低元素))。您将拥有三个只在外循环中发生变化的向量,以及三个每次在内循环中都会发生变化的向量。您将 广播 a[j]
到另一个向量的所有位置 (在内循环之外)。
实际上,您可能想要交换循环,因此累加器 (ax[i] += ...
) 可以在整个内部循环中只位于寄存器中。你必须每次通过内部循环加载 m[j]
,但这通过不必 load/store 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;
除法次数相同(一),但乘法少了一次。
我有一个循环,其中有另一个循环从数组中进行一些计算。我想使用 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);
}
}
我认为您不需要任何新数组来向量化。应用 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 或 clang 的版本。
如果你能给编译器一些对齐保证,你也可能会得到更好的代码(特别是对于 gcc,它喜欢做 intro/outro 块来达到对齐边界,而不是使用未对齐的操作。)
看来您可以使用 SSE 一次对内循环进行四次迭代。并行执行四个 i
值或并行执行四个 j
值是可以的,因为一次迭代的结果不是另一次迭代的输入。
所以你将有一个带有 a[i+3] a[i+2] a[i+1] a[i]
的矢量(在你的矢量中从左(高元素)到右(低元素))。您将拥有三个只在外循环中发生变化的向量,以及三个每次在内循环中都会发生变化的向量。您将 广播 a[j]
到另一个向量的所有位置 (在内循环之外)。
实际上,您可能想要交换循环,因此累加器 (ax[i] += ...
) 可以在整个内部循环中只位于寄存器中。你必须每次通过内部循环加载 m[j]
,但这通过不必 load/store 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;
除法次数相同(一),但乘法少了一次。