我刚刚了解了如何获得2个数组的点积(如下面的代码所示):
int A[8] = {1,2,3,4,5,1,2,3};
int B[8] = {2,3,4,5,6,2,3,4};
float result = 0;
for (int i = 0; i < 8; i ++) {
result += A[i] * B[i];
}
等同于(在SIMD中):
int A[8] = {1,2,3,4,5,1,2,3};
int B[8] = {2,3,4,5,6,2,3,4};
float result = 0;
__m128 r1 = {0,0,0,0};
__m128 r2 = {0,0,0,0};
__m128 r3 = {0,0,0,0};
for (int i = 0; i < 8; i += 4) {
float C[4] = {A[i], A[i+1], A[i+2], A[i+3]};
float D[4] = {B[i], B[i+1], B[i+2], B[i+3]};
__m128 a = _mm_loadu_ps(C);
__m128 b = _mm_loadu_ps(D);
r1 = _mm_mul_ps(a,b);
r2 = _mm_hadd_ps(r1, r1);
r3 = _mm_add_ss(_mm_hadd_ps(r2, r2), r3);
_mm_store_ss(&result, r3);
}
我现在好奇如何在SIMD中获取等效代码,如果我想将数组中不连续的元素相乘。例如,如果我想执行以下操作,SIMD中的等效内容是什么?
int A[8] = {1,2,3,4,5,1,2,3};
int B[8] = {2,3,4,5,6,2,3,4};
float result = 0;
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
result += A[foo(i)] * B[foo(j)]
}
}
foo只是一些返回int作为输入参数的函数的函数。
答案 0 :(得分:1)
如果我必须完成这项任务,我会按如下方式进行:
int A[8] = {1,2,3,4,5,1,2,3};
int B[8] = {2,3,4,5,6,2,3,4};
float PA[8], PB[8];
for (int i = 0; i < 8; i++)
{
PA[i] = A[foo(i)];
PB[i] = B[foo(i)];
}
__m128 sums = _mm_set1_ps(0);
for (int i = 0; i < 8; i++)
{
__m128 a = _mm_set1_ps(PA[i]);
for (int j = 0; j < 8; j += 4)
{
__m128 b = _mm_loadu_ps(PB + j);
sums = _mm_add_ps(sums, _mm_mul_ps(a, b));
}
}
float results[4];
_mm_storeu_ps(results, sums);
float result = results[0] + results[1] + results[2] + results[3];
答案 1 :(得分:0)
一般来说,SIMD 不就像随机访问各个元素一样。但是,仍然有一些技巧可以使用。
如果在编译时已知提供的foo
索引,则可以将两个向量混合以正确对齐它们的元素。只需看看swizzle category in the Intrinsics Guide中的内在函数。大多数情况下,您需要_mm_shuffle_ps
和_mm_unpackXX_ps
之类的内容。此外,各种移位/对齐指令可能很有用。
使用AVX2,您可以尝试使用收集指令。对于32位模式的 float 类型,您可以
使用_mm_i32gather_ps
或_mm256_i32gather_ps
内在函数。但是,@ PaulR写here它们并不比琐碎的标量载荷快。
SSSE3中的_mm_shuffle_epi8
instrinsic可能是另一种解决方案。这是一个很棒的指令,允许以单个字节的粒度执行寄存器内收集操作。但是,创建shuffle掩码不是一项简单的任务。 This paper(阅读第3.1和4节)展示了如何将此方法扩展到大于一个XMM寄存器的输入数组,但似乎对于64个或更多元素,它不再比标量代码更好。