どちらのバージョンも、g++5.4-O3 -march=haswell
を使用してベクトル化し、Marc が指摘する理由により、内側のループで vcmpltps/vmaskmovps を使用します。
コンパイラが AVX 命令を使用できないようにすると、処理が難しくなります。しかし、使用するだけでは、どちらのバージョンもベクトル化されません-O3
(x86-64 のベースラインであるため、SSE2 のみが利用可能です)。したがって、元の質問は、再現できない結果に基づいています。
if() を三項演算子に変更すると (コードは常に配列に格納されるため)、コンパイラはロード/MINPS/無条件に格納できます。マトリックスがキャッシュに収まらない場合、これは大量のメモリ トラフィックです。ループを別の方法でアレンジできますか?あるいはそうではないかもしれません。なぜならm[i][k]
必要だからです。
更新が非常にまれで、ダーティ データの書き戻しがメモリ ボトルネックの原因になっている場合は、ベクター要素が変更されていなければ、ストアを回避するために分岐する価値さえあるかもしれません。
これは、SSE2 だけでも適切にベクトル化される配列バージョンです。入力がアラインされており、サイズが 8 の倍数 (AVX ベクトルあたりの float の数) であることをコンパイラに伝えるコードを追加しました。実際のコードでこれらの仮定を行うことができない場合は、その部分を削除してください。スカラーのイントロ/クリーンアップ コードに埋もれていないため、ベクトル化された部分を見つけやすくなります。(使用-O2 -ftree-vectorize
しても、この方法でクリーンアップ コードが完全に展開されるわけではありませんが、展開されます-O3
。)
AVX がないと、gcc はまだアラインされていないロードを使用しますが、アラインされたストアを使用していることに気付きました。m[k][j]
サイズが 8 の倍数である場合、サイズを揃える必要があることに気付いていないのm[i][j]
でしょうか。これは、ポインター バージョンと配列バージョンの違いである可能性があります。
Godbolt コンパイラ エクスプローラーのコード
void floydwarshall_array_unconditional(float* mat, size_t n) {
// try to tell gcc that it doesn't need scalar intro/outro code
// The vectorized inner loop isn't particularly different without these, but it means less wading through scalar cleanup code (and less bloat if you can use this in your real code).
// works with gcc6, doesn't work with gcc5.4
mat = (float*)__builtin_assume_aligned(mat, 32);
n /= 8;
n *= 8; // code is simpler if matrix size is always a multiple of 8 (floats per AVX vector)
typedef float (*array)[n];
array m = reinterpret_cast<array>(mat);
for (size_t k = 0; k < n; ++k) {
for (size_t i = 0; i < n; ++i) {
auto v = m[i][k];
for (size_t j = 0; j < n; ++j) {
auto val = v + m[k][j];
m[i][j] = (m[i][j]>val) ? val : m[i][j]; // Marc's suggested change: enables vectorization with unconditional stores.
}
}
}
}
gcc5.4 では、ベクトル化された部分のスカラー イントロ/クリーンアップ コードを回避できませんでしたが、gcc6.2 では回避できます。ベクトル化された部分は、基本的に両方のコンパイラ バージョンで同じです。
## The inner-most loop (with gcc6.2 -march=haswell -O3)
.L5:
vaddps ymm0, ymm1, YMMWORD PTR [rsi+rax]
vminps ymm0, ymm0, YMMWORD PTR [rdx+rax] #### Note use of minps and unconditional store, enabled by using the ternary operator instead of if().
add r14, 1
vmovaps YMMWORD PTR [rdx+rax], ymm0
add rax, 32
cmp r14, r13
jb .L5
外部の次のループでは、(いくつかの setcc を使用して) 整数カウンターのチェックをvmovss xmm1, DWORD PTR [rax+r10*4]
行い、別のvbroadcastss ymm1, xmm1
. おそらく、ジャンプ先のスカラークリーンアップはブロードキャストを必要とせず、gcc は、ブロードキャスト部分が必要ない場合でも、ロードとして VBROADCASTSS を使用するだけで全体的に安価になることを知りません。