質問
ループにいくつかのコードがあります
for(int i = 0; i < n; i++)
{
u[i] = c * u[i] + s * b[i];
}
したがって、UとBは同じ長さのベクトルであり、CとSはスカラーです。このコードは、スピードアップを取得するためにSSEで使用するためのベクトル化の良い候補ですか?
アップデート
私はベクトル化を学んだことがあり(内因性を使用するとそれほど難しくないことがわかります)、SSEでループを実装しました。ただし、VC ++コンパイラにSSE2フラグを設定すると、自分のSSEコードと同じパフォーマンスが得られます。一方、Intelコンパイラは、私のSSEコードまたはVC ++コンパイラよりもはるかに高速でした。
これが私が参照のために書いたコードです
double *u = (double*) _aligned_malloc(n * sizeof(double), 16);
for(int i = 0; i < n; i++)
{
u[i] = 0;
}
int j = 0;
__m128d *uSSE = (__m128d*) u;
__m128d cStore = _mm_set1_pd(c);
__m128d sStore = _mm_set1_pd(s);
for (j = 0; j <= i - 2; j+=2)
{
__m128d uStore = _mm_set_pd(u[j+1], u[j]);
__m128d cu = _mm_mul_pd(cStore, uStore);
__m128d so = _mm_mul_pd(sStore, omegaStore);
uSSE[j/2] = _mm_add_pd(cu, so);
}
for(; j <= i; ++j)
{
u[j] = c * u[j] + s * omegaCache[j];
}
解決
はい、これはベクトル化の優れた候補です。しかし、あなたがそうする前に、 コードをプロファイルしていることを確認してください これが実際に最適化する価値があることを確認するために。とはいえ、ベクトル化は次のようになります:
int i;
for(i = 0; i < n - 3; i += 4)
{
load elements u[i,i+1,i+2,i+3]
load elements b[i,i+1,i+2,i+3]
vector multiply u * c
vector multiply s * b
add partial results
store back to u[i,i+1,i+2,i+3]
}
// Finish up the uneven edge cases (or skip if you know n is a multiple of 4)
for( ; i < n; i++)
u[i] = c * u[i] + s * b[i];
さらにパフォーマンスを得るには、さらに配列要素をプリフェッチすること、および/またはループを展開して使用することを検討できます。 ソフトウェアパイプライン 別の反復からメモリにアクセスして、1つのループで計算をインターリーブします。
他のヒント
おそらくはい、しかし、あなたはいくつかのヒントでコンパイラーを助けなければなりません。__restrict__
ポインターに配置されたのは、2つのポインターの間にエイリアスがないことをコンパイラに伝えます。ベクトルのアラインメントがわかっている場合は、コンパイラにそれを伝えます(Visual C ++には何らかの施設がある場合があります)。
私は視覚的なC ++に精通していませんが、ベクトル化には良くないと聞いています。代わりにIntelコンパイラの使用を検討してください。 Intelは、生成されたアセンブリをかなり細かく制御できます。 http://www.intel.com/software/products/compilers/docs/clin/main_cls/cref_cls/common/cppref_pragma_vector.htm
_mm_set_pd
ベクトル化されていません。文字通り撮影すると、スカラー操作を使用して2つのダブルを読み取り、2つのスカラーダブルを組み合わせてSSEレジスタにコピーします。使用する _mm_load_pd
代わりは。
はい、これはVectorizatonの優れた候補者です。UとBアレイの重複がないと仮定します。ただし、コードはメモリアクセス(ロード/ストア)にバインドされています。ベクトル化は、ループあたりのサイクルを削減するのに役立ちますが、uおよびbアレイのキャッシュミスのために命令は失速します。 Intel C/C ++コンパイラは、Xeon X5500プロセッサのデフォルトフラグを使用して次のコードを生成します。コンパイラはループを8で展開し、XMM [0-16] SIMDレジスタを使用してSIMD ADD(ADDPD)を使用し、乗算(MULPD)命令を採用します。各サイクルで、プロセッサは、レジスタにデータの準備が整っていると仮定して、4ウェイスカラーILPを生成する2つのSIMD命令を発行できます。
ここでは、u、b、c、およびsは二重精度(8バイト)です。
..B1.14: # Preds ..B1.12 ..B1.10
movaps %xmm1, %xmm3 #5.1
unpcklpd %xmm3, %xmm3 #5.1
movaps %xmm0, %xmm2 #6.12
unpcklpd %xmm2, %xmm2 #6.12
# LOE rax rcx rbx rbp rsi rdi r8 r12 r13 r14 r15 xmm0 xmm1 xmm2 xmm3
..B1.15: # Preds ..B1.15 ..B1.14
movsd (%rsi,%rcx,8), %xmm4 #6.21
movhpd 8(%rsi,%rcx,8), %xmm4 #6.21
mulpd %xmm2, %xmm4 #6.21
movaps (%rdi,%rcx,8), %xmm5 #6.12
mulpd %xmm3, %xmm5 #6.12
addpd %xmm4, %xmm5 #6.21
movaps 16(%rdi,%rcx,8), %xmm7 #6.12
movaps 32(%rdi,%rcx,8), %xmm9 #6.12
movaps 48(%rdi,%rcx,8), %xmm11 #6.12
movaps %xmm5, (%rdi,%rcx,8) #6.3
mulpd %xmm3, %xmm7 #6.12
mulpd %xmm3, %xmm9 #6.12
mulpd %xmm3, %xmm11 #6.12
movsd 16(%rsi,%rcx,8), %xmm6 #6.21
movhpd 24(%rsi,%rcx,8), %xmm6 #6.21
mulpd %xmm2, %xmm6 #6.21
addpd %xmm6, %xmm7 #6.21
movaps %xmm7, 16(%rdi,%rcx,8) #6.3
movsd 32(%rsi,%rcx,8), %xmm8 #6.21
movhpd 40(%rsi,%rcx,8), %xmm8 #6.21
mulpd %xmm2, %xmm8 #6.21
addpd %xmm8, %xmm9 #6.21
movaps %xmm9, 32(%rdi,%rcx,8) #6.3
movsd 48(%rsi,%rcx,8), %xmm10 #6.21
movhpd 56(%rsi,%rcx,8), %xmm10 #6.21
mulpd %xmm2, %xmm10 #6.21
addpd %xmm10, %xmm11 #6.21
movaps %xmm11, 48(%rdi,%rcx,8) #6.3
addq $8, %rcx #5.1
cmpq %r8, %rcx #5.1
jl ..B1.15 # Prob 99% #5.1
それはあなたがどのようにuとbをメモリに置いたかに依存します。両方のメモリブロックが互いにほど遠い場合、SSEはこのシナリオではあまり強化されません。
Array UとBは、SOA(アレイの構造)の代わりにAOE(構造の配列)であることが示唆されています。これは、両方の登録に単一の命令に登録できるためです。