在C ++中使用SSE2 SIMD对两个数组求和的正确方法

时间:2016-09-29 01:23:40

标签: c++ arrays sum sse simd

让我们首先包括以下内容:

#include <vector>
#include <random>
using namespace std;

现在,假设一个人有以下三个std:vector<float>

N = 1048576;
vector<float> a(N);
vector<float> b(N);
vector<float> c(N);

default_random_engine randomGenerator(time(0));
uniform_real_distribution<float> diceroll(0.0f, 1.0f);
for(int i-0; i<N; i++)
{
    a[i] = diceroll(randomGenerator);
    b[i] = diceroll(randomGenerator);
}

现在,假设需要将ab元素相加并将结果存储在c中,其标量形式如下所示:

for(int i=0; i<N; i++)
{
    c[i] = a[i] + b[i];
}

上述代码的SSE2矢量化版本是什么,请记住上面定义的输入为ab(即作为float的集合)和ehe输出为c(也是float)的集合?

经过相当多的研究,我得出了以下结论:

for(int i=0; i<N; i+=4)
{
    float a_toload[4] = { a[i], a[i + 1], a[i + 2], a[i + 3] };
    float b_toload[4] = { b[i], b[i + 1], b[i + 2], b[i + 3] };
    __m128 loaded_a = _mm_loadu_ps(a_toload);
    __m128 loaded_b = _mm_loadu_ps(b_toload);

    float result[4] = { 0, 0, 0, 0 };
    _mm_storeu_ps(result, _mm_add_ps(loaded_a , loaded_b));
    c[i] = result[0];
    c[i + 1] = result[1];
    c[i + 2] = result[2];
    c[i + 3] = result[3];
}

然而,这似乎非常麻烦,而且效率肯定非常低:上面的SIMD版本实际上比初始标量版本慢三倍(当然,在Microsoft VS15的发布模式下进行优化测量,以及之后100万次迭代,而不仅仅是12次。

2 个答案:

答案 0 :(得分:3)

你的for循环可以简化为

const int aligendN = N - N % 4;
for (int i = 0; i < alignedN; i+=4) {
    _mm_storeu_ps(&c[i], 
                  _mm_add_ps(_mm_loadu_ps(&a[i]), 
                  _mm_loadu_ps(&b[i])));
}
for (int i = alignedN; i < N; ++i) {
    c[i] = a[i] + b[i];
}

一些补充说明:
1,处理最后几个浮点数的小循环是常见的,当编译时N%4 != 0或N未知时,它是强制性的。
2,我注意到你选择了未对齐的版本加载/存储,与对齐版本相比有一个小的惩罚。我在stackoverflow找到了这个链接:Is the SSE unaligned load intrinsic any slower than the aligned load intrinsic on x64_64 Intel CPUs?

答案 1 :(得分:2)

您不需要将中间数组加载到SSE寄存器。只需直接从阵列加载。

auto loaded_a = _mm_loadu_ps(&a[i]);
auto loaded_b = _mm_loadu_ps(&b[i]);
_mm_storeu_ps(&c[i], _mm_add_ps(loaded_a, loaded_b));

您也可以省略两个loaded变量并将其合并到add中,尽管编译器应该为您执行此操作。

你需要小心这一点,因为如果向量大小不是4的倍数它将无法正常工作(你将访问数组的末尾,导致未定义的行为,并且写入过去c的结尾可能是有害的。)