[英]The correct way to sum two arrays with SSE2 SIMD in C++
讓我們從包括以下內容開始:
#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);
}
現在,假設需要按元素對a
和b
求和並將結果存儲在c
,標量形式如下所示:
for(int i=0; i<N; i++)
{
c[i] = a[i] + b[i];
}
上面代碼的 SSE2 向量化版本是什么,記住輸入是上面定義的a
和b
(即作為float
的集合),而輸出是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 版本實際上比初始標量版本慢了三倍(當然,在微軟 VS15 的發布模式下,經過 100 萬次迭代后測量,優化,不僅僅是 12)。
您的 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];
}
一些額外的解釋:
N%4 != 0
或 N 在編譯時未知時,這是強制性的。您不需要將中間數組加載到 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
的末尾可能會損壞)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.