4

次のような非常に単純なコードがあるとします。

double array[SIZE_OF_ARRAY];
double sum = 0.0;

for (int i = 0; i < SIZE_OF_ARRAY; ++i)
{
    sum += array[i];
}

基本的にSSE2を使って同じ操作をしたいです。どうやってやるの?

4

1 に答える 1

5

非常に単純な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で悪化することはありません。ただし、これはループの外側にあり、どちらの方法でも大きな違いはありません。

于 2012-10-01T22:24:25.833 に答える