[英]Array vs pointer auto-vectorization in gcc
我正在嘗試使用g ++ 5.4( -ftree-vectorize
)進行自動矢量-ftree-vectorize
。 我注意到下面代碼中的數組版本導致編譯器錯過內部循環中的向量化機會,導致與指針版本相比顯着的性能差異。 在這種情況下,有什么可以幫助編譯器的嗎?
void floydwarshall(float* mat, size_t n) {
#if USE_POINTER
for (int k = 0; k < n; ++k) {
for (int i = 0; i < n; ++i) {
auto v = mat[i*n + k];
for (int j = 0; j < n; ++j) {
auto val = v + mat[k*n+j];
if (mat[i*n + j] > val) {
mat[i*n + j] = val;
}
}
}
}
#else // USE_ARRAY
typedef float (*array)[n];
array m = reinterpret_cast<array>(mat);
for (int k = 0; k < n; ++k) {
for (int i = 0; i < n; ++i) {
auto v = m[i][k];
for (int j = 0; j < n; ++j) {
auto val = v + m[k][j];
if (m[i][j] > val) {
m[i][j] = val;
}
}
}
}
#endif
}
兩個版本都使用g ++ 5.4 -O3 -march=haswell
進行矢量化,在內循環中使用vcmpltps / vmaskmovps,因為Marc指出了這一點。
如果你不讓編譯器使用AVX指令,那將會更難。 但是如果我只使用-O3
,那么我根本看不到任何一個版本矢量化(因此只有SSE2可用,因為它是x86-64的基線)。 所以你的原始問題是基於我無法重現的結果。
將if()更改為三元運算符(因此代碼始終存儲到數組中)可讓編譯器加載/ MINPS /無條件存儲。 如果您的矩陣不適合緩存,這會占用大量內存; 也許你可以用不同的方式安排你的循環? 或許不是,因為需要m[i][k]
,並且我認為事情發生的順序很重要。
如果更新非常罕見並且臟數據的回寫導致內存瓶頸,那么如果沒有修改任何向量元素,甚至可能值得分支以避免存儲。
這是一個矢量化的數組版本 ,即使只有SSE2也是如此。 我添加了代碼來告訴編譯器輸入是對齊的,大小是8的倍數(每個AVX向量的浮點數)。 如果您的真實代碼無法做出這些假設,那么請將該部分刪除。 它使矢量化部分更容易找到,因為它沒有隱藏在標量簡介/清理代碼中。 (使用-O2 -ftree-vectorize
不會以這種方式完全展開清理代碼,但-O3
-O2 -ftree-vectorize
。)
我注意到沒有AVX,gcc仍然使用未對齊的加載但對齊的存儲。 也許它沒有意識到如果m[i][j]
對齊,大小是8的倍數應該使m[k][j]
對齊? 這可能是指針版本和數組版本之間的差異。
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用作負載會更便宜。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.