兩個版本做矢量化,使用g ++ 5.4 -O3 -march=haswell
,使用vcmpltps /在對馬克指出的原因內環vmaskmovps。
如果您不讓編譯器使用AVX指令,那將會花費更多時間。但是如果我只使用-O3
(所以只有SSE2可用,因爲它是x86-64的基準),所以我沒有看到任何版本的矢量化。所以你原來的問題是基於我無法複製的結果。
將if()更改爲三元運算符(因此代碼始終存儲到數組中)可讓編譯器加載/ MINPS /無條件地存儲。如果您的矩陣不適合緩存,這是很多內存流量;也許你可以用不同的方式安排你的循環?或者可能不是,因爲需要m[i][k]
,並且我認爲這很重要。
如果更新非常少,並且髒數據的回寫會導致內存瓶頸,那麼如果沒有向量元素被修改,甚至可能需要分支以避免存儲。
這裏是一個數組版本,以及矢量化,即使只有SSE2。我添加了代碼以告訴編譯器輸入已對齊,並且大小是8的倍數(每個AVX向量的浮點數)。如果你的真實代碼無法做出這些假設,那就把這部分拿出來。它使得矢量化部分更容易找到,因爲它並沒有被埋在標量的介紹/清除代碼中。 (使用-O2 -ftree-vectorize
沒有完全展開的清理代碼這種方式,但-O3
一樣。)
我注意到,沒有AVX,GCC仍然使用未對齊的加載,但對準商店。如果m[i][j]
對齊,也許它不知道尺寸是8的倍數應該使m[k][j]
對齊?這可能是指針版本和數組版本之間的區別。
code on the Godbolt compiler explorer
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作爲負載也會更便宜。
避免分支'mat [i * n + j] =(mat [i * n + j]> val)?val:mat [i * n + j];'(無條件寫入)更容易矢量化。 –
我把你的代碼分成兩個獨立的函數(同時查看)和[把它們放在Godbolt編譯器資源管理器](https://godbolt.org/g/i0ByaJ)上。它們都使用gcc5自動矢量化。4'-O3 -march = haswell',在內部循環中使用vcmpltps/vmaskmovps是Marc指出的原因。指針版本在內部循環中有兩個來自內存的整數加載,並且數組版本的內部循環綁定在內存中('cmp r11,QWORD PTR [rbp-72]'),所以它們都有問題。你使用了哪些目標選項?只需要基線SSE2? –