Vla*_*gan 30 c++ gcc vectorization avx compiler-optimization
考虑使用以下 float 循环,使用 -O3 -mavx2 -mfma 编译
for (auto i = 0; i < a.size(); ++i) {
a[i] = (b[i] > c[i]) ? (b[i] * c[i]) : 0;
}
Run Code Online (Sandbox Code Playgroud)
Clang 在矢量化方面做得非常出色。它使用 256 位 ymm 寄存器,并了解 vblendps/vandps 之间的差异,以获得尽可能最佳的性能。
.LBB0_7:
vcmpltps ymm2, ymm1, ymm0
vmulps ymm0, ymm0, ymm1
vandps ymm0, ymm2, ymm0
Run Code Online (Sandbox Code Playgroud)
然而,海湾合作委员会的情况要糟糕得多。由于某种原因,它并没有比 SSE 128 位向量更好(-mprefer-vector-width=256 不会改变任何东西)。
.L6:
vcomiss xmm0, xmm1
vmulss xmm0, xmm0, xmm1
vmovss DWORD PTR [rcx+rax*4], xmm0
Run Code Online (Sandbox Code Playgroud)
如果将其替换为普通数组(如指南中所示),gcc 会将其矢量化为 AVX ymm。
int a[256], b[256], c[256];
auto foo (int *a, int *b, int *c) {
int i;
for (i=0; i<256; i++){
a[i] = (b[i] > c[i]) ? (b[i] * c[i]) : 0;
}
}
Run Code Online (Sandbox Code Playgroud)
但是我没有找到如何使用可变长度 std::vector 来做到这一点。gcc 需要什么样的提示才能将 std::vector 向量化为 AVX?
Pet*_*des 35
这不是std::vector问题所在,float而是 GCC 通常糟糕的默认设置-ftrapping-math应该将 FP 异常视为可见的副作用,但并不总是正确地做到这一点,并且错过了一些安全的优化。
在这种情况下,源中存在条件 FP 乘法,因此严格的异常行为可以避免在比较错误的情况下可能引发上溢、下溢、不精确或其他异常。
在这种情况下,GCC 使用标量代码正确地执行了此操作:...ss是单标量,使用 128 位 XMM 寄存器的底部元素,根本没有矢量化。您的 asm 不是 GCC 的实际输出:它使用 加载两个元素vmovss,然后在之前的vcomiss结果上分支,因此如果不是 true,则不会发生乘法。因此,与您的“GCC”asm 不同,我认为 GCC 的实际 asm 正确实现了。 vmulssb[i] > c[i]-ftrapping-math
请注意,自动矢量化的示例使用int *args,而不是float*. 如果您将其更改为float*并使用相同的编译器选项,它也不会自动矢量化,即使使用float *__restrict a(https://godbolt.org/z/nPzsf377b)。
@273K 的答案表明AVX-512float即使使用 也可以自动矢量化-ftrapping-math,因为 AVX-512 屏蔽 ( ymm2{k1}{z}) 抑制屏蔽元素的 FP 异常,不会从 C++ 抽象机中不会发生的任何 FP 乘法引发 FP 异常。
gcc -O3 -mavx2 -mfma -fno-trapping-math自动矢量化所有 3 个函数 ( Godbolt )void foo (float *__restrict a, float *__restrict b, float *__restrict c) {
for (int i=0; i<256; i++){
a[i] = (b[i] > c[i]) ? (b[i] * c[i]) : 0;
}
}
Run Code Online (Sandbox Code Playgroud)
foo(float*, float*, float*):
xor eax, eax
.L143:
vmovups ymm2, YMMWORD PTR [rsi+rax]
vmovups ymm3, YMMWORD PTR [rdx+rax]
vmulps ymm1, ymm2, YMMWORD PTR [rdx+rax]
vcmpltps ymm0, ymm3, ymm2
vandps ymm0, ymm0, ymm1
vmovups YMMWORD PTR [rdi+rax], ymm0
add rax, 32
cmp rax, 1024
jne .L143
vzeroupper
ret
Run Code Online (Sandbox Code Playgroud)
顺便说一句,我建议-march=x86-64-v3使用 AVX2+FMA 功能级别。这还包括 BMI1+BMI2 之类的东西。我认为它仍然只是使用-mtune=generic,但希望将来可以忽略仅对没有 AVX2+FMA+BMI2 的 CPU 重要的调整事情。
这些std::vector函数比较庞大,因为我们没有使用float *__restrict a = avec.data();或类似承诺控制块指向的数据不重叠std::vector(并且大小不知道是向量宽度的倍数),但非清理无重叠情况的循环使用相同的//vmulps进行矢量化。vcmpltpsvandps
也可以看看:
-ftrapping-math根据 GCC 开发者 Marc Glisse 的说法,它已经损坏并且“从未工作过” 。但是https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54192从 2012 年开始建议将其设置为默认值,但仍然处于开放状态。-ffast-math,例如允许内联许多函数,并且对于正常代码来说不是问题,因为正常代码在调用后-fno-math-errno不进行检查或其他任何操作!)errnosqrt-ffast-mathor进行矢量化#pragma omp simd reduction (+:my_sum_var),但 @phuclv 的答案有一些很好的链接)如果无论条件如何,C 源代码中的乘法都会发生,那么 GCC 将被允许以有效的方式对其进行矢量化,而无需 AVX-512 掩码。
// still scalar asm with GCC -ftrapping-math which is a bug
void foo (float *__restrict a, float *__restrict b, float *__restrict c) {
for (int i=0; i<256; i++){
float prod = b[i] * c[i];
a[i] = (b[i] > c[i]) ? prod : 0;
}
}
Run Code Online (Sandbox Code Playgroud)
但不幸的是,GCC -O3 -march=x86-64-v3(带默认值和不带默认值的Godbolt-ftrapping-math)仍然使标量汇编只能有条件地相乘!
这是 中的一个错误-ftrapping-math。它不仅过于保守,错过了自动矢量化的机会:它实际上是有缺陷的,对于抽象机(或调试版本)实际执行的某些乘法没有引发 FP 异常。像这样的垃圾行为是-ftrapping-math不可靠的,并且可能不应该默认启用。
@Ovinus Real 的回答指出,GCC仍然可以通过屏蔽两个输入而不是输出来 -ftrapping-math自动矢量化原始源。从不引发任何 FP 异常,因此它基本上是模拟 AVX-512 零掩码。0.0 * 0.0
这会更昂贵并且有更多的延迟来隐藏乱序执行,但仍然比标量好得多,特别是当 AVX1 可用时,特别是对于在某种级别的缓存中很热的中小型阵列。
(如果使用内在函数编写,只需将输出屏蔽为零,除非您实际上想在循环后检查 FP 环境中的异常标志。)
在标量源中执行此操作不会导致 GCC 生成这样的 asm:GCC 会将其编译为相同的分支标量 asm,除非您使用-fno-trapping-math. 至少这次不是错误,只是错过了优化:b[i]*c[i]当比较为假时,这不会起作用。
// doesn't help, still scalar asm with GCC -ftrapping-math
void bar (float *__restrict a, float *__restrict b, float *__restrict c) {
for (int i=0; i<256; i++){
float bi = b[i];
float ci = c[i];
if (! (bi > ci)) {
bi = ci = 0;
}
a[i] = bi * ci;
}
}
Run Code Online (Sandbox Code Playgroud)
S.M*_*.M. 20
GCC 默认针对较旧的 CPU 架构进行编译。
设置-march=native启用使用 256 位 ymm 寄存器。
.L7:
vmovups ymm1, YMMWORD PTR [rsi+rax]
vmovups ymm0, YMMWORD PTR [rdx+rax]
vcmpps k1, ymm1, ymm0, 14
vmulps ymm2{k1}{z}, ymm1, ymm0
vmovups YMMWORD PTR [rcx+rax], ymm2
Run Code Online (Sandbox Code Playgroud)
设置-march=x86-64-v4启用使用 512 位 zmm 寄存器。
.L7:
vmovups zmm2, ZMMWORD PTR [rsi+rax]
vcmpps k1, zmm2, ZMMWORD PTR [rdx+rax], 14
vmulps zmm0{k1}{z}, zmm2, ZMMWORD PTR [rdx+rax]
vmovups ZMMWORD PTR [rcx+rax], zmm0
Run Code Online (Sandbox Code Playgroud)