次のような非常に単純なコードがあるとします。
double array[SIZE_OF_ARRAY];
double sum = 0.0;
for (int i = 0; i < SIZE_OF_ARRAY; ++i)
{
sum += array[i];
}
基本的にSSE2を使って同じ操作をしたいです。どうやってやるの?
非常に単純なSSE3の実装は次のとおりです。
#include <emmintrin.h>
__m128d vsum = _mm_set1_pd(0.0);
for (int i = 0; i < n; i += 2)
{
__m128d v = _mm_load_pd(&a[i]);
vsum = _mm_add_pd(vsum, v);
}
vsum = _mm_hadd_pd(vsum, vsum);
double sum = _mm_cvtsd_f64(vsum0);
複数のアキュムレータを使用してFP追加のレイテンシを非表示にすることで、ループを展開してパフォーマンスを大幅に向上させることができます(@Mysticialで提案されています)。複数の「合計」ベクトルを使用して3〜4回展開し、FP追加レイテンシ(3または4サイクルごとに1つ)ではなく、負荷とFP追加スループット(クロックサイクルごとに1つまたは2つ)のボトルネックにします。
__m128d vsum0 = _mm_setzero_pd();
__m128d vsum1 = _mm_setzero_pd();
for (int i = 0; i < n; i += 4)
{
__m128d v0 = _mm_load_pd(&a[i]);
__m128d v1 = _mm_load_pd(&a[i + 2]);
vsum0 = _mm_add_pd(vsum0, v0);
vsum1 = _mm_add_pd(vsum1, v1);
}
vsum0 = _mm_add_pd(vsum0, vsum1); // vertical ops down to one accumulator
vsum0 = _mm_hadd_pd(vsum0, vsum0); // horizontal add of the single register
double sum = _mm_cvtsd_f64(vsum0);
配列a
は16バイトに整列されているn
と想定され、要素の数は2の倍数(展開されたループの場合は4)であると想定されていることに注意してください。
ループの外側で水平合計を実行する別の方法については、x86で水平フロートベクトル合計を実行する最速の方法も参照してください。SSE3のサポートは完全に普遍的ではありません(特にAMD CPUはIntelよりも後でサポートしていました)。
また、_mm_hadd_pd
通常、それをサポートするCPUでも最速の方法ではないため、SSE2のみのバージョンが最新のCPUで悪化することはありません。ただし、これはループの外側にあり、どちらの方法でも大きな違いはありません。