1

次のスニペットを検討してください。

double dot(double* a, double* b, int n) {
  double sum = 0;
  for (int i = 0; i < n; ++i) sum += a[i] * b[i];
  return sum;
}

組み込み関数またはアセンブラーを使用して高速化するにはどうすればよいですか?

ノート:

  • AVX 拡張機能を含む最新のアーキテクチャを想定している可能性があります。
  • n数百です。
  • ドット自体がタイトなループで使用されます
4

1 に答える 1

7

以下は単純な SSE の実装です。

#include "pmmintrin.h"

__m128d vsum = _mm_set1_pd(0.0);
double sum = 0.0;
int k;

// process 2 elements per iteration
for (k = 0; k < n - 1; k += 2)
{
    __m128d va = _mm_loadu_pd(&a[k]);
    __m128d vb = _mm_loadu_pd(&b[k]);
    __m128d vs = _mm_mul_pd(va, vb);
    vsum = _mm_add_pd(vsum, vs);
}

// horizontal sum of 2 partial dot products
vsum = _mm_hadd_pd(vsum, vsum);
_mm_store_sd(&sum, vsum);

// clean up any remaining elements
for ( ; k < n; ++k)
{
    sum += a[k] * b[k];
}

a と b が 16 バイト アラインされていることを保証できる場合は、特に古い (Nehalem より前の) CPU では、パフォーマンスに役立つ可能性があるのでは_mm_load_pdなく、むしろ使用できることに注意してください。_mm_loadu_pd

また、ロードの数に対して算術命令が非常に少ないこのようなループでは、パフォーマンスがメモリ帯域幅によって制限される可能性があり、ベクトル化による期待される速度向上が実際には実現されない可能性があることにも注意してください。


AVX で CPU をターゲットにしたい場合は、上記の SSE 実装から、反復ごとに 2 ではなく 4 要素を処理するためのかなり単純な変換です。

#include "immintrin.h"

__m256d vsum = _mm256_set1_pd(0.0);
double sum = 0.0;
int k;

// process 4 elements per iteration
for (k = 0; k < n - 3; k += 4)
{
    __m256d va = _mm256_loadu_pd(&a[k]);
    __m256d vb = _mm256_loadu_pd(&b[k]);
    __m256d vs = _mm256_mul_pd(va, vb);
    vsum = _mm256_add_pd(vsum, vs);
}

// horizontal sum of 4 partial dot products
vsum = _mm256_hadd_pd(_mm256_permute2f128_pd(vsum, vsum, 0x20), _mm256_permute2f128_pd(vsum, vsum, 0x31));
vsum = _mm256_hadd_pd(_mm256_permute2f128_pd(vsum, vsum, 0x20), _mm256_permute2f128_pd(vsum, vsum, 0x31));
_mm256_store_sd(&sum, vsum);

// clean up any remaining elements
for ( ; k < n; ++k)
{
    sum += a[k] * b[k];
}
于 2013-06-09T17:19:06.863 に答える