由于 Marc 指出的原因,两个版本都使用 g++5.4 在内部循环中使用 vcmpltps/vmaskmovps 进行矢量化。-O3 -march=haswell
如果您不让编译器使用 AVX 指令,那将会更加困难。但是如果我只是使用的话,我根本看不到任何一个版本的矢量化-O3
(所以只有 SSE2 可用,因为它是 x86-64 的基线)。所以你原来的问题是基于我无法重现的结果。
将 if() 更改为三元运算符(因此代码始终存储到数组中)让编译器加载/MINPS/无条件存储。如果您的矩阵不适合缓存,这会占用大量内存;也许你可以用不同的方式安排你的循环?或者可能不需要,因为m[i][k]
需要,我认为事情发生的顺序很重要。
如果更新非常不频繁并且脏数据的回写导致内存瓶颈,那么如果没有修改任何向量元素,甚至可能值得分支以避免存储。
这是一个矢量化的数组版本,即使只有 SSE2。我添加了代码来告诉编译器输入是对齐的,并且大小是 8 的倍数(每个 AVX 向量的浮点数)。如果你的真实代码不能做出这些假设,那么就把那部分去掉。它使矢量化部分更容易找到,因为它没有隐藏在标量介绍/清理代码中。(使用-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 作为负载也会更便宜。