-mavx
/-mavx2
/-mavx512f
(and -march=
暗示它们具有相关调整设置的选项)让 GCC 在编译代码时使用 AVX / AVX2 / AVX-512 指令来处理它认为是个好主意的任何内容,包括但不限于循环的自动矢量化,如果您也启用了该功能。
SSE 指令的其他用例(如果您告诉 GCC 启用了 AVX,GCC 将使用 AVX 编码)包括复制和零初始化结构体和数组,以及内联小型常量大小的其他情况memset
and memcpy
。还有标量 FP 数学,即使在-O0
在 64 位代码中,其中-mfpmath=sse
是默认值。
代码构建于-mavx
通常无法在没有 AVX 的 CPU 上运行,即使未启用自动矢量化并且您没有使用任何 AVX 内在函数;它使 GCC 对每个 SIMD 指令使用 VEX 编码而不是传统的 SSE。另一方面,除非实际自动矢量化循环,否则通常不会使用 AVX2。它与复制数据或标量 FP 数学无关。如果满足以下条件,GCC 将使用标量 FMA 指令:-mfma
不过已启用。
例子在戈德螺栓上 https://godbolt.org/#z:OYLghAFBqd5TKALEBjA9gEwKYFFMCWALugE4A0BIEAZgQDbYB2AhgLbYgDkAjF%2BTXRMiAZVQtGIHgBYBQogFUAztgAKAD24AGfgCsp5eiyagA%2BudTkVjVEQJDqzTAGF09AK5smIAEzknADIETNgAcp4ARtikvgBs5AAO6ErE9kyuHl6%2BicmpQkEh4WxRMT7x1ti2aSJELKREGZ7ePFbYNnZCNXVEBWGR0b4AzFa19Y1ZLUqjPcF9xQM%2BgwCUVujupKicXMFEANSC6BA7uwBUdaRLAKQA7ABCl1oAgrsvu8dKnruXgwAiu1rfe5PV77Mi7I7CN7fH5aL6DW5QwbOHhaHzSOEIgiXHz3HFXO4PZ4g14fNhfHHQ3bnS4AVluWJpP0BhJBNyZwNepGwRHWTF2pOZTzZhMJAHpRbtsAA3Zi7ADuxCQLwAtGxgMxohJlVzgEplUJ6ABPMUSgDizmcu0w6GwSl2IgA6o8AEpU9wkZUy2xkAgALxYHT5wV2sWkyoixDewmw6tIux1SkJ7iYKWAIUwu1QSDquwihqI2FM6jIEGTqfTmezcbOpAuIoJQsWBBoOBou3MptCCmc5hZr3OcL%2B5gi7gYdiYphYSlJhYkBDT2EwEHO5F2gx8V3hIsWTmbfZe4szRhMbo9XpIpD9tvj2AkRtzLEw94VRCVLHlBBwca5PNITGVRCGgk2CrgqOBMMEwBRikOC7K%2B2C7PQ6DoAkkoykGbakm8dqsLW6BymAYD7rsZbzhWWY5lhlIAluHIvIIcYQnsWK/LCgKIsiqLouxWIUniNxAkSxL8p8tK4JS1J0gyTK0UJXzXOyck/ryIlsIKjzCkKTzWu4ESMPsCQQDpekISwq7GfpET4oJILKX%2BubKiw6maRpTxSugn6SuoRAQO5nknJugl%2BRm4j0IwpC%2BR5mDWcRxySbcPCxLSfyUgJAIKepILYN5y61oF9Y/FwKz0NwNL8N4XA6OQ6DcJaShrBsCHYoMfDkEQ2hFSsADWIA0j4AB00jrtI1yLC1AAcWgAJzXFNhjcNI/BsL1WjkBVVU1Vw/BKCAq3tZVRXkHAsAoBgbAJAw0SUNQZ0XeFaCoMewA8IMWgtHQ9AFqQO0QBEHXkBGuGGtwrVnRwwgAPJMEa/04GwxjAJIB3kIQXJVDKO3I9llTulsrU7G0/30AQESkHUhquDg/1EJey28IdNBGLqABqBDYHKEPARVrWCMIYgSJwMhyMIyhqJoyP6H4T0gOYpiWMTEQ7ZAKwoYGmPKhDgy7MqNBMOgyrJqQbj0MqSEoUo21tJUgaOEwLhuE0BiBLMRQlAYSQpIG4zNDkntpL0rsDJMVtVJ00zewYFSh0wXT1AH/QxJM4cOxMIzdPH8yJys9XrJsBg09geOHSVXBlWt/2beo42xMqoZHgjuwvf1Wj9Tw4LOKu%2BDEGCzUtLsrjnZdcbNRu/D7ToSwrEgt5ftQxULUtK3l8jm3bbtbUdZP5A9YM1yDTSsTV4fWjSLEU0%2BHNJeDOVFfcGPm9HYgECnegg/hddEC3UPaDPTwfgfV9H6f1kaA3JiDfgYNmBEChjDZGcMEZIyqqja2BAMb/WxqgXG4DKDRhLlVBWZNSAUywFsKqNMCB0z4CsRmLAWZsw5lzbBvNRChUFrIZhosND/X0C0aWst5YkyVhAFWCQ1bcA1j4bW8MpTqG1rrfWBdsCemtj6X02BLbtDSLbe2mQfbO0KAnXwfgPZ5HSCnH2JjAwZzdj4PwUdAyxwaOYyOIcHHTGsUHOaUxugRyTunF2hieBTWzg1POLRFHgPnqXG%2BK9uBVxrnXYAqBUCNx4P1SREAO67C7heckLVVwDzutEPJPAlj3wOlvaej4BjCO3iAV6/Vrg8HGlNVEh8XojVPvNLgi1yDLRRKtda/BV5WHXuPQ6x1kBoFfkUigVBP4zO/qgX%2B/8xzRCAf9UBRDsGQMhtDYGcDsDwxMIg/gyD0a2nQeoHGBZsEEzwfwAh5NKakLHrTSJAgmZKFZuzTmzAmHyH5pIIWHCVBcIlvUwwCMZYWEMII%2BAIixFcA1lrDRKCHAQCcL4/wdsPGJ19qY7Flj/YBMzi4zRYcfHOODhSmO7jSVuz8WMalac44MqDiE3Ogs2pciLlEsuQzqpxOrrXdESSUk8DSRkrJOSe6LD7oUoeeTljlInt1epaTxpokWFoGkL0po0imrEeIJdemCpGTtPam8ok%2BBiRtO%2BG8KkrBlN9LR0ggA%3D%3D
void ext(void *);
void caller(void){
int arr[16] = {0};
ext(arr);
}
double fp(double a, double b){
return b-a;
}
使用 AVX 指令进行编译gcc -O2 -fno-tree-vectorize -march=haswell
,因为当启用 AVX 时,GCC 完全避免到处使用传统的 SSE 编码。
caller:
sub rsp, 72
vpxor xmm0, xmm0, xmm0
mov rdi, rsp
vmovdqa XMMWORD PTR [rsp], xmm0 # only 16-byte vectors, not using YMM + vzeroupper
vmovdqa XMMWORD PTR [rsp+16], xmm0
vmovdqa XMMWORD PTR [rsp+32], xmm0
vmovdqa XMMWORD PTR [rsp+48], xmm0
call ext
add rsp, 72
ret
fp:
vsubsd xmm0, xmm1, xmm0
ret
-m
选项不enable自动矢量化;-ftree-vectorize
这样做。播出时间为-O3
和更高。 (或者以有限的形式-O2
对于 GCC12 及更高版本,仅在“非常便宜”时进行矢量化,例如当它知道迭代计数是 4 的倍数或其他任何值时,这样它就可以在没有清理循环的情况下进行矢量化。 clang 完全启用自动矢量化-O2
.)
如果你这样做want启用扩展的自动矢量化,使用-O3
以及,并且最好是-march=native
or -march=znver2
或其他东西而不仅仅是-mavx2
. -march
还设置调整选项,并将启用您可能忘记的其他 ISA 扩展,例如-mfma
and -mbmi2
.
隐含的调整选项-march=haswell
(要不就-mtune=haswell
)对于较旧的 GCC 特别有用,当tune=generic
更关心没有 AVX2 的旧 CPU,或者在某些情况下将未对齐的 256 位加载作为两个独立的部分是一种胜利:为什么 gcc 不将 _mm256_loadu_pd 解析为单个 vmovupd? https://stackoverflow.com/questions/52626726/why-doesnt-gcc-resolve-mm256-loadu-pd-as-single-vmovupd
不幸的是没有类似的东西-mtune=generic-avx2
or -mtune=enabled-extension
仍然关心 AMD 和 Intel CPU,但不关心那些对于您启用的所有扩展而言太旧的 CPU。
When manually使用内在函数进行矢量化时,您只能将内在函数用于已启用的指令集。 (或者是默认打开的,例如 SSE2,它是 x86-64 的基线,即使使用-m32
在现代 GCC 配置中。)
例如如果你使用_mm256_add_epi32
,除非您使用,否则您的代码将无法编译-mavx2
。 (或者更好的是,类似-march=haswell
or -march=native
支持 AVX2、FMA、BMI2 和现代 x86 所具有的其他功能,and设置适当的调整选项。)
这种情况下的 GCC 错误消息是error: inlining failed in call to 'always_inline' '_mm256_loadu_si256': target specific option mismatch
.
在 GCC 术语中,“目标”是您正在编译的机器。 IE。-mavx2
告诉 GCC 目标支持 AVX2。因此,GCC 将生成一个可以在任何地方使用 AVX2 指令的可执行文件,例如用于复制结构体或对本地数组进行零初始化,或以其他方式扩展小型常量大小的 memcpy 或 memset。
它还将定义 CPP 宏__AVX2__
, so #ifdef __AVX2__
可以测试编译时是否可以假设AVX2。
如果那是not你想要的整个程序,你需要确保不使用-mavx2
编译任何在没有运行时检查 CPU 功能的情况下调用的代码。例如将 AVX2 版本的函数放在单独的文件中进行编译-mavx2
,或使用__attribute__((target("avx2")))
。让你的程序在检查后设置函数指针__builtin_cpu_supports("avx2") https://gcc.gnu.org/onlinedocs/gcc/x86-Built-in-Functions.html,或者使用 GCC 的ifunc
进行多版本控制的调度机制。
- https://gcc.gnu.org/onlinedocs/gcc/x86-Function-Attributes.html#index-target-function-attribute-5 https://gcc.gnu.org/onlinedocs/gcc/x86-Function-Attributes.html#index-target-function-attribute-5
- https://gcc.gnu.org/onlinedocs/gcc/Function-Multiversioning.html https://gcc.gnu.org/onlinedocs/gcc/Function-Multiversioning.html
-m
选项做not自行启用自动矢量化
(自动矢量化并不是 GCC 使用 SIMD 指令集的唯一方式。)
-ftree-vectorize
(作为一部分启用-O3
,或者甚至在-O2
在 GCC12 及更高版本中)对于 GCC 自动矢量化是必要的。和/或-fopenmp
如果代码有一些#pragma omp simd
。 (你肯定总是想要至少-O2
or -Os
如果您关心性能;-O3
should最快,但可能并不总是如此。有时,GCC 会出现错过优化的错误,其中 -O3 会使情况变得更糟,或者在大型程序中,可能会发生较大的代码大小会导致更多的 I-cache 和 I-TLB 未命中。)
一般来说,当自动向量化和优化时,GCC(可能)会使用您告诉它可用的任何指令集(带有-m
选项)。例如,-O3 -march=haswell
将使用 AVX2 + FMA 自动矢量化。-O3
没有-m
选项将仅使用 SSE2 自动矢量化。
例如比较在戈德螺栓上 https://godbolt.org/#z:OYLghAFBqd5TKALEBjA9gEwKYFFMCWALugE4A0BIEAZgQDbYB2AhgLbYgDkAjF%2BTXRMiAZVQtGIHgBYBQogFUAztgAKAD24AGfgCsp5eiyagA%2BudTkVjVEQJDqzTAGF09AK5smBpwBkCTNgAcp4ARtikIABMUeQADuhKxPZMrh5eBglJdkL%2BgSFs4ZExVtg2OUwiRCykRGme3jyl5SlVNUR5wWER0bFK1bX1GU397Z0FRb0AlFbo7qSonFwBRADUgugQK6sAVDWkUwCkAOwAQodaAIKrN6vbSp6rhwDMACKrWi/nV7frZKtbYR3F6vLRPZ6nYHPZw8LRRaTgyEEQ5Rc6oo5nC7XX63B5sJ6okGrfaHACsp2RpNeXyxvxO1J%2Bt1I2CI8yYqzxNKu9KxWIA9HzVtgAG7MVYAd2ISBuAFo2MBmBEJDLmcAlDKhPQAJ78wUAcWczlWmHQ2CUqxEAHVLgAlYnuEgy0W2MgEABeLAqd3ZADZpDLQsRvURsArSKtVUose4mElgIFMKtUEgaqtQlqQ6Z1GQIDG4wmkynw3tSAdeZjuVFngQaDgaKtzHqggpnOZabd9uD3uZQu4GHYmKYWEo8dgh/QCPHsJgIPtyKtnlEjhDeVWnDX2zc85OC8nU3iux8udiboJw4C1si3mCvlCYXCEbfkYT0SdviecQeybgiSTyZTqRXRkbh5YCIxZNkOU8Y9QMuLgZnobhSX4bwuB0ch0G4I0lDmBZsAJZ4%2BHIIhtHgmYAGsQFJKIADppEXaRjirQiAA4tAATmOdjDG4aR%2BDYKitHIVD0Mwrh%2BCUEAhJItD4PIOBYBQDA2DiBgIkoahlNUxhIlQVAjBMUktCaOh6BDUhJIgUJSPIQNWFILVuCI5SOGEAB5JhtRsnA2GMYBJFk8hCGZWwCFFSTAuwdRsFQB0liIlYyhsidQlIGotVcHAbKIUgCAE3g5JoIw1QANQIbBxTcuJmCc/hBGEMQJE4GQ5GEZQ1E0QL9FiAyzAsQwCFCSTIBmdA4gqCKZTc55VhlGgmHQGUY1INx6Bleh0DGpQJLKGKKkcJgXDcBofEO8ZukiJosmSIQhkaeJEhuphzsKHoRl20KhDaQZjuGZo9taAYOgCLpXsuqwgbugxRlqF7Jh4GYcPmRYDBy7B4rkxCuGQ4SbLE9QWJ9GU/STXrVlJGitBongAWced8GIf4UUI%2BdXBUtTw2Zpd%2BBknQphmJBsBYHBIggBDeP4wTccCsSJKk4jSP58hKOeY46NJH1Cc1rRpB9diom4rHnhQvHuB5xX5MQCAlPQdmdI0iAtI5tBgB4HhYlM8zLOswK7PS2ryBc5giA8rzAp8vyAvQ4KAfCmyopiuKA8SrH0JStKHMypZ0JyvLapmIqWFK8rKuq1CiPq0RxEkFrK/ajQbP0JpepAcxTEsFLhrFjDxpSSbptm3yFiQEFAhTRg2B2loHAgJwoaaPwQYmN6HuyFJ59Xp64ZX6wAa%2ByHfvu3fPsqIHt/BmG6kP6Gz6Xi6pER3CUaaNGMfF7GTZl7gCaJkngD01YbsaJRFpvTQgJBOZViaKsNm2kIgEQRubWSStBbCx6N3FWVNjg8BYuxOEmseDPEYrrHiXA%2BLkAErCISIl%2BCyysPLXmckFLIDQLbOBFAqCOzYc7VArt3YCH7BEb2Nk/YOQDkHdynlHLh2wL5EwUd%2BAx1CnHSK0VYohmTsIJKgV07pSztlXK%2BU%2BAF2KkoMqFUqo1QKq1KuTUpCyDrioBuXUQDPEMH5Vu/VO7wFGr3IQ/cZpyhqMmEEbomCilIFEKee9vCz0OhvRe%2BR75XUehUDe10Kjn2hh9Co30r7pCPjkwGYw75gxvu0Del8skI1mMjZqxFmRv1ITjGhGFv6E2Jgif%2BqBAE8GAaA1YDMIEINZtwnSBFnhTCQXzCiri%2BksXhFWLQpJCHsVJOxH0PpSHkNaXQyS0lFbvyidLUSZsFbIJmBEpIDhpBAA%3D%3D%3D GCC -O3 -march=nehalem
(SSE4.2) 对比-march=znver2
(AVX2) 用于对整数数组求和。 (编译时常量大小以保持汇编简单)。
如果你使用-O3 -mgeneral-regs-only
(后一个选项通常只在内核代码中使用),GCC 仍然会自动矢量化,但仅在它认为这样做有利可图的情况下SWAR https://en.wikipedia.org/wiki/SWAR(例如,使用 64 位整数寄存器对数组进行异或很简单,甚至可以使用 SWAR 位黑客来阻止/纠正字节之间的进位来计算字节总和)
e.g. gcc -O1 -mavx
仍然只使用标量代码。
通常,如果您想要完全优化而不是自动矢量化,您可以使用类似的东西-O3 -march=znver1 -fno-tree-vectorize
其他编译器
以上所有内容对于 clang 来说也都是正确的,只是它不理解-mgeneral-regs-only
。 (我想你需要-mno-mmx -mno-sse
也许还有其他选择。)
(使用 SSE / AVX Intrinisics 时架构的影响 https://stackoverflow.com/questions/55747789/the-effect-of-architecture-when-using-sse-avx-intrinisics重复其中一些信息)
对于 MSVC / ICC,您can使用 ISA 扩展的内在函数,您没有告诉编译器它可以单独使用。例如,MSVC-O2
没有-arch:AVX
让它使用 SSE2 自动矢量化(因为这是 x86-64 的基线),并使用movaps
用于复制大约 16 字节结构或其他内容。
但使用 MSVC 的目标选项风格,您仍然可以使用 SSE4 内在函数,例如_mm_cvtepi8_epi32
(pmovsxwd
),甚至是 AVX 内在函数,而不告诉编译器它本身允许使用这些指令。
较旧的 MSVC 曾经制作过非常糟糕的汇编 https://stackoverflow.com/questions/7839925/using-avx-cpu-instructions-poor-performance-without-archavx/28356924#28356924当您使用 AVX / AVX2 内在函数而不-arch:AVX
,例如导致在同一函数中混合 VEX 和传统 SSE 编码(例如,对 128 位内在函数使用非 VEX 编码,例如_mm_add_ps
),并且在 256 位向量之后无法使用 vzeroupper,这两者都会对性能造成灾难性的影响。
但我认为现代 MSVC 已经基本解决了这个问题。尽管它仍然没有对内在函数进行太多优化,甚至没有通过它们进行恒定传播。
不优化内在函数可能与 MSVC 让您编写类似代码的能力有关if(avx_supported) { __m256 v = _mm256_load_ps(p); ...
等等。如果它试图优化,它必须跟踪沿着执行路径已经看到的可以到达任何给定内在的最小扩展级别,这样它就知道哪些替代方案是有效的。 ICC也是这样。
出于同样的原因,GCC 无法将具有不同目标选项的函数相互内联。所以你不能使用__attribute__((target("")))
避免运行时调度的成本;您仍然希望避免循环内的函数调用开销,即确保 AVX2 函数内有循环,否则可能不值得使用 AVX2 版本,只需使用 SSE2 版本即可。
我不知道Intel新的OneAPI编译器ICX。我认为它是基于 LLVM 的,所以它可能更像 clang。