(更新:以前のバージョンではインデックスが間違っていました。このバージョンはかなり簡単に自動ベクトル化されます)。
ポインタ(ポインタへのポインタの配列ではなく)のC多次元配列、またはi*cols + j
でインデックス付けされたフラット1D配列を使用して、メモリが連続しているようにします。これは、メモリ帯域幅を有効に活用するために、ハードウェアプリフェッチの有効性に大きな違いをもたらします。負荷が実際に別の負荷から来ている場合は、パフォーマンスが低下し、逆に予測可能なアドレスがあらかじめわかっていれば、負荷が必要になる前に開始することができます。
また、@ user31264の答えは正しいです。j
のループが最も外側になるように、ループを交換する必要があります。これは良いですが、それだけでは十分ではありません。
これにより、コンパイラは自動的にベクトル化することもできます。実際、gccに自動ベクトル化をさせるのは驚くほど苦労しました。 (私はインデックスが間違っていましたので、私はコードのみで初めて見えたので、しかし、それは、おそらくです。コンパイラは、我々は連続したメモリをループし、知りませんでしたので。)
は私がそれで遊んGodbolt compiler explorer。
私は最終的にAとCのようなフラット1Dアレイを取り、インデックス自体を行い、このバージョンから良い素敵なコンパイラの出力を、得た:
void MatrixAddition_contiguous(int rows, int n, const vector<int>& list,
const int *__restrict__ A, int *__restrict__ C, int cluster)
// still auto-vectorizes without __restrict__, but checks for overlap and
// runs a scalar loop in that case
{
const int cols = 4; // or global constexpr or something
int *__restrict__ Ccluster = C + ((long)cluster) * rows * cols;
for(int i=0;i<rows;i++)
//#pragma omp simd
for(int k=0;k<4;k++)
Ccluster[cols*i + k]=0;
for(int j=0;j < cols;++j) { // loop over clusters in A in the outer-most loop
const int *__restrict__ Alistj = A + ((long)list[j]) * rows * cols;
// #pragma omp simd // Doesn't work: only auto-vectorizes with -O3
// probably only -O3 lets gcc see through the k=0..3 loop and treat it like one big loop
for (int i = 0; i < rows; i++) {
long row_offset = cols*i;
//#pragma omp simd // forces vectorization with 16B vectors, so it hurts AVX2
for(int k=0;k<4;k++)
Ccluster[row_offset + k] += Alistj[row_offset + k];
}
}
}
手動list[j]
は間違いなくC
に保存することを実現し、コンパイラを助けた巻き上げ読み込まれるインデックスには影響しませんlist[j]
。おそらく他のものを手動で持ち上げる必要はなかったでしょう。
ホイストA[list[j]]
ではなく、list[j]
は、previous approach where I had the indexing wrongのアーチファクトです。可能であれば、list[j]
から負荷を持ち上げる限り、list
がC
と重複していないことがわからなくても、コンパイラはうまくいく可能性があります。GCC 5.3は、x86-64の-O3 -Wall -march=haswell -fopenmp
(および-fverbose-asm
)を標的と
内側のループは、次のとおりです。
.L26:
vmovdqu ymm0, YMMWORD PTR [r8+rax] # MEM[base: Ccluster_20, index: ivtmp.91_26, offset: 0B], MEM[base: Ccluster_20, index: ivtmp.91_26, offset: 0B]
vpaddd ymm0, ymm0, YMMWORD PTR [rdx+rax] # vect__71.75, MEM[base: Ccluster_20, index: ivtmp.91_26, offset: 0B], MEM[base: vectp.73_90, index: ivtmp.91_26, offset: 0B]
add r12d, 1 # ivtmp.88,
vmovdqu YMMWORD PTR [r8+rax], ymm0 # MEM[base: Ccluster_20, index: ivtmp.91_26, offset: 0B], vect__71.75
add rax, 32 # ivtmp.91,
cmp r12d, r9d # ivtmp.88, bnd.66
jb .L26 #,
だから、8をやっている非整列ロードおよび背面に整列していない店で、AVX2 vpaddd
で、一度に追加C.
自動ベクトル化されているので、ARM NEON、PPC Altivec、またはパック32ビット追加を行うことができるもので良いコードにする必要があります。
-ftree-vectorizer-verbose=2
で何かを教えてもらえませんでしたが、clangの-Rpass-analysis=loop-vectorize
は少し役に立ちました。
間接参照の3つのレベルだけでは、おそらくこの関数が終了しています。あなたがキャッシュに優しくない*しようとしていたら、おめでとう、あなたは成功したと思います。 – WhozCraig
これらの配列の割り当て方法を表示する必要があります。もしあなたがそれを素朴にしたら、@WhozCraigが述べたように、これは良くありません。あなたが1つの巨大なメモリプールを割り当て、そのメモリ内の正しい場所にポインタを指していたなら、それは別の話です。 – PaulMcKenzie
ベクトル "リスト"を呼び出すことは悪い考えです。それは、リスト "ベクトル"を呼び出す、セット "マップ"を呼び出す、またはあなたの猫 "犬"を呼び出すようなものです。 – user31264