我在我的C/C代码中明确使用了英特尔SIMD扩展的内在特性。为了编译代码,我需要在命令行中指定-mavx或-mavx 512,或类似的内容。我对这些都很在行。但是,通过阅读gcc手册页,我们并不清楚这些命令行标志是否也会告诉gcc编译器尝试使用英特尔SIMD指令自动矢量化C/C代码。有人知道是不是这样吗?-mavx标志是否只是允许您手动将SIMD内部函数插入代码中,还是它也告诉编译器在编译C/C++代码时使用SIMD指令?
bkhjykvo1#
-mavx/-mavx2/-mavx512f(以及-march=选项,这些选项通过相关调优设置暗示了它们)允许GCC在编译代码时将AVX /AVX 2/ AVX-512指令用于它认为是好主意的任何事情,包括但不限于循环的自动矢量化,如果您也启用了该功能。SSE指令的其他用例(如果您告诉GCC启用了AVX,GCC将使用AVX编码)包括复制和零初始化结构体和数组,以及内联小的恒定大小memset和memcpy的其他用例。还有标量FP数学,即使在-O0的64位代码中,-mfpmath=sse是默认值。使用-mavx构建的代码通常不能在没有AVX的CPU上运行,即使没有启用自动矢量化,并且没有使用任何AVX内部函数;它使GCC对每个SIMD指令都使用VEX编码,而不是传统的SSE。另一方面,AVX 2通常不使用,除非在实际自动矢量化循环时。它与复制数据或标量FP数学无关。不过,如果启用-mfma,GCC将使用标量FMA指令。Godbolt示例
-mavx
-mavx2
-mavx512f
-march=
memset
memcpy
-O0
-mfpmath=sse
-mfma
void ext(void *); void caller(void){ int arr[16] = {0}; ext(arr); } double fp(double a, double b){ return b-a; }
使用gcc -O2 -fno-tree-vectorize -march=haswell编译AVX指令,因为当启用AVX时,GCC完全避免了任何地方的传统SSE编码。
gcc -O2 -fno-tree-vectorize -march=haswell
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选项不 * 启用 * 自动矢量化; -ftree-vectorize可以做到这一点。**它在-O3和更高版本中启用。(或者在使用GCC 12和更高版本的-O2中启用,如使用clang。)
-m
-ftree-vectorize
-O3
-O2
如果您确实 * 希望 * 启用扩展的自动矢量化,也可以使用-O3,最好是-march=native或-march=znver2等,而不仅仅是-mavx2。-march还设置了调优选项,并将启用您可能忘记的其他伊萨扩展,如-mfma和-mbmi2。-march=haswell(或仅-mtune=haswell)所暗示的调优选项在较旧的GCC上特别有用,当tune=generic更关心没有AVX 2的旧CPU时,或者当在某些情况下将未对齐的256位加载作为两个单独的部分来执行时,-march=haswell(或仅-mtune=haswell)所暗示的调优选项是一种优势:Why doesn't gcc resolve _mm256_loadu_pd as single vmovupd?不幸的是,没有什么像-mtune=generic-avx2或-mtune=enabled-extension仍然关心AMD和英特尔CPU,但不关心那些太旧的扩展您启用。当 * 手动 * 使用内部函数进行矢量化时,您只能为已启用的指令集使用内部函数。(或者默认启用的指令集,如SSE 2,它是x86-64的基线,并且即使在现代GCC配置中使用-m32也经常启用。例如,如果您使用_mm256_add_epi32,则除非您使用-mavx2,否则您的代码将无法编译。(或者更好的做法是,使用-march=haswell或-march=native,它们支持AVX 2、FMA、BMI 2以及现代x86所具有的其他功能,* 并且 * 设置适当的调优选项。)在这种情况下,GCC错误消息为error: inlining failed in call to 'always_inline' '_mm256_loadu_si256': target specific option mismatch。在GCC术语中,“目标”是您正在编译的机器。例如,-mavx2告诉GCC目标支持AVX 2。因此GCC将生成一个可执行文件,它可以在任何地方使用AVX 2指令,例如复制一个结构体或零初始化一个本地数组,或者扩展一个小的恒定大小的memcpy或memset。它还将定义CPP宏__AVX2__,因此#ifdef __AVX2__可以测试是否可以在编译时假定AVX 2。如果这不是你想要的整个程序,你需要确保不要使用-mavx2来编译任何在没有运行时检查CPU特性的情况下被调用的代码。例如,把你的AVX 2版本的函数放在一个单独的文件中,用-mavx2来编译,或者使用__attribute__((target("avx2")))。让你的程序在检查__builtin_cpu_supports("avx2")后设置函数指针。或者使用GCC的ifunc调度机制进行多版本化。
-march=native
-march=znver2
-march
-mbmi2
-march=haswell
-mtune=haswell
tune=generic
-mtune=generic-avx2
-mtune=enabled-extension
-m32
_mm256_add_epi32
error: inlining failed in call to 'always_inline' '_mm256_loadu_si256': target specific option mismatch
__AVX2__
#ifdef __AVX2__
__attribute__((target("avx2")))
__builtin_cpu_supports("avx2")
ifunc
(自动矢量化并不是GCC使用SIMD指令集的唯一方式。)-ftree-vectorize(作为-O3的一部分启用,或者甚至在GCC 12和更高版本的-O2中启用)是GCC自动矢量化所必需的。和/或-fopenmp,如果代码中包含一些#pragma omp simd。(如果您关心性能,您肯定总是希望至少使用-O2或-Os; -O3 * 应该 * 是最快的,但并不总是最快的。有时GCC会出现优化缺失错误,其中-O3会使情况变得更糟,或者在大型程序中,可能会发生代码越大,I缓存和I TLB缺失就越多的情况。)当自动矢量化和优化时,GCC将(可能)使用您告诉它可用的任何指令集(带有-m选项)。例如,-O3 -march=haswell将使用AVX 2 + FMA自动矢量化。没有-m选项的-O3将仅使用SSE 2自动矢量化。例如,比较Godbolt GCC -O3 -march=nehalem(SSE4.2)与-march=znver2(AVX 2)对整数数组的求和。(编译时常量大小使asm保持简单)。
-fopenmp
#pragma omp simd
-Os
-O3 -march=haswell
-O3 -march=nehalem
如果你使用-O3 -mgeneral-regs-only(后一个选项通常只在内核代码中使用),GCC仍然会自动矢量化,但只有在它认为执行SWAR是有利可图的情况下(例如,使用64位整数寄存器直接对数组进行异或,或者甚至使用SWAR位黑客来阻止/纠正字节之间的进位的字节和)例如gcc -O1 -mavx仍然仅仅使用标量代码。通常,如果您需要完全优化而不是自动矢量化,则应使用类似-O3 -march=znver1 -fno-tree-vectorize的代码
-O3 -mgeneral-regs-only
gcc -O1 -mavx
-O3 -march=znver1 -fno-tree-vectorize
以上所有这些对于clang也是正确的,只是它不理解-mgeneral-regs-only。(我认为您需要-mno-mmx -mno-sse,也许还需要其他选项。)(The Effect of Architecture When Using SSE / AVX Intrinisics重复了其中的一些信息)对于MSVC / ICC,您 * 可以 * 使用您没有告诉编译器它可以自己使用的伊萨扩展的内部函数。因此,例如,没有-arch:AVX的MSVC -O2将允许它使用SSE 2自动矢量化(因为这是x86-64的基线),并使用movaps复制大约16字节的结构体或其他内容。但是使用MSVC风格的目标选项,您仍然可以使用_mm_cvtepi8_epi32(pmovsxwd)等SSE 4内部函数,甚至AVX内部函数,而无需告诉编译器允许自己使用这些指令。旧版MSVC在使用AVX /AVX 2内部函数而不使用-arch:AVX时会产生非常糟糕的asm,例如导致在同一个函数中混合VEX和传统SSE编码(例如对_mm_add_ps这样的128位内部函数使用非VEX编码),以及在256位向量后无法使用vzeroupper,这两种情况对性能都是灾难性的。但是我认为现代的MSVC已经基本解决了这个问题,尽管它仍然没有对内部函数进行太多的优化,比如甚至没有通过它们进行常量传播。不优化intrinsic可能与MSVC允许你编写if(avx_supported) { __m256 v = _mm256_load_ps(p); ...等代码的能力有关。如果它试图优化,它必须跟踪沿着执行路径已经看到的最小扩展级别,这些路径可以到达任何给定的intrinsic,这样它就知道什么替代方案是有效的。ICC也是这样。出于同样的原因,GCC不能将具有不同目标选项的函数内联到彼此中,因此不能使用__attribute__((target("")))来避免运行时调度的开销;您仍然希望避免循环内的函数调用开销,即确保AVX 2函数内有循环,否则可能不值得使用AVX 2版本,只需使用SSE 2版本。我对英特尔新推出的OneAPI编译器ICX不太了解,我觉得它是基于LLVM的,所以可能更像是clang。
-mgeneral-regs-only
-mno-mmx -mno-sse
-arch:AVX
movaps
_mm_cvtepi8_epi32
pmovsxwd
_mm_add_ps
if(avx_supported) { __m256 v = _mm256_load_ps(p); ...
__attribute__((target("")))
mzillmmw2#
目前使用的是gcc 11.3.1或更高版本。我不是程序员,但能区分C和C++。我在github /doom 9论坛上制作最新的编解码器已经有三年了。在我的旧英特尔“核心”(R)(TM)i5- 2500 K CPU@3. 30 GHz我注意到了。在C语言中,你可以播放SIMD AVX 2 ex.汇编器编解码器的非SIMD处理器。我们可以使用论坛上发布的编解码器吗?谁知道呢。例如,libjpeg,dav 1d与SIMD没有mavx 2。xeve,xevd,uvg 266,无人机战斗系统3e,无人机战斗系统3d,空中通讯,libavif在C++ SIMD AVX 2中,你甚至不会打开帮助。第二件事是线程和Unix与Windows的兼容性。在C中,这比在C中工作得更快。在C中,你还必须添加一些未经测试的特殊附加项,如mingw-std-thread到g中,以使一切正常工作。关于C的另一个好奇心。MSYS 2 GCC 12.1.0。在AVX 2/AVX 3中制作的编解码器可以在旧的处理器上打开。它是如何制作的?我不知道,但不具备上述功能。(C++20 openjpeg)语言支持:压缩,压缩,解压,压缩,解压,解压,压缩,解压,解压,解压,解压,解压,解压,解压,解压,解压,解压,解压,解压,解压
2条答案
按热度按时间bkhjykvo1#
-mavx
/-mavx2
/-mavx512f
(以及-march=
选项,这些选项通过相关调优设置暗示了它们)允许GCC在编译代码时将AVX /AVX 2/ AVX-512指令用于它认为是好主意的任何事情,包括但不限于循环的自动矢量化,如果您也启用了该功能。SSE指令的其他用例(如果您告诉GCC启用了AVX,GCC将使用AVX编码)包括复制和零初始化结构体和数组,以及内联小的恒定大小
memset
和memcpy
的其他用例。还有标量FP数学,即使在-O0
的64位代码中,-mfpmath=sse
是默认值。使用
-mavx
构建的代码通常不能在没有AVX的CPU上运行,即使没有启用自动矢量化,并且没有使用任何AVX内部函数;它使GCC对每个SIMD指令都使用VEX编码,而不是传统的SSE。另一方面,AVX 2通常不使用,除非在实际自动矢量化循环时。它与复制数据或标量FP数学无关。不过,如果启用-mfma
,GCC将使用标量FMA指令。Godbolt示例
使用
gcc -O2 -fno-tree-vectorize -march=haswell
编译AVX指令,因为当启用AVX时,GCC完全避免了任何地方的传统SSE编码。**
-m
选项不 * 启用 * 自动矢量化;-ftree-vectorize
可以做到这一点。**它在-O3
和更高版本中启用。(或者在使用GCC 12和更高版本的-O2
中启用,如使用clang。)如果您确实 * 希望 * 启用扩展的自动矢量化,也可以使用
-O3
,最好是-march=native
或-march=znver2
等,而不仅仅是-mavx2
。-march
还设置了调优选项,并将启用您可能忘记的其他伊萨扩展,如-mfma
和-mbmi2
。-march=haswell
(或仅-mtune=haswell
)所暗示的调优选项在较旧的GCC上特别有用,当tune=generic
更关心没有AVX 2的旧CPU时,或者当在某些情况下将未对齐的256位加载作为两个单独的部分来执行时,-march=haswell
(或仅-mtune=haswell
)所暗示的调优选项是一种优势:Why doesn't gcc resolve _mm256_loadu_pd as single vmovupd?不幸的是,没有什么像
-mtune=generic-avx2
或-mtune=enabled-extension
仍然关心AMD和英特尔CPU,但不关心那些太旧的扩展您启用。当 * 手动 * 使用内部函数进行矢量化时,您只能为已启用的指令集使用内部函数。(或者默认启用的指令集,如SSE 2,它是x86-64的基线,并且即使在现代GCC配置中使用
-m32
也经常启用。例如,如果您使用
_mm256_add_epi32
,则除非您使用-mavx2
,否则您的代码将无法编译。(或者更好的做法是,使用-march=haswell
或-march=native
,它们支持AVX 2、FMA、BMI 2以及现代x86所具有的其他功能,* 并且 * 设置适当的调优选项。)在这种情况下,GCC错误消息为
error: inlining failed in call to 'always_inline' '_mm256_loadu_si256': target specific option mismatch
。在GCC术语中,“目标”是您正在编译的机器。例如,
-mavx2
告诉GCC目标支持AVX 2。因此GCC将生成一个可执行文件,它可以在任何地方使用AVX 2指令,例如复制一个结构体或零初始化一个本地数组,或者扩展一个小的恒定大小的memcpy或memset。它还将定义CPP宏
__AVX2__
,因此#ifdef __AVX2__
可以测试是否可以在编译时假定AVX 2。如果这不是你想要的整个程序,你需要确保不要使用
-mavx2
来编译任何在没有运行时检查CPU特性的情况下被调用的代码。例如,把你的AVX 2版本的函数放在一个单独的文件中,用-mavx2
来编译,或者使用__attribute__((target("avx2")))
。让你的程序在检查__builtin_cpu_supports("avx2")
后设置函数指针。或者使用GCC的ifunc
调度机制进行多版本化。-m
选项本身 * 不 * 启用自动矢量化(自动矢量化并不是GCC使用SIMD指令集的唯一方式。)
-ftree-vectorize
(作为-O3
的一部分启用,或者甚至在GCC 12和更高版本的-O2
中启用)是GCC自动矢量化所必需的。和/或-fopenmp
,如果代码中包含一些#pragma omp simd
。(如果您关心性能,您肯定总是希望至少使用-O2
或-Os
;-O3
* 应该 * 是最快的,但并不总是最快的。有时GCC会出现优化缺失错误,其中-O3会使情况变得更糟,或者在大型程序中,可能会发生代码越大,I缓存和I TLB缺失就越多的情况。)当自动矢量化和优化时,GCC将(可能)使用您告诉它可用的任何指令集(带有
-m
选项)。例如,-O3 -march=haswell
将使用AVX 2 + FMA自动矢量化。没有-m
选项的-O3
将仅使用SSE 2自动矢量化。例如,比较Godbolt GCC
-O3 -march=nehalem
(SSE4.2)与-march=znver2
(AVX 2)对整数数组的求和。(编译时常量大小使asm保持简单)。如果你使用
-O3 -mgeneral-regs-only
(后一个选项通常只在内核代码中使用),GCC仍然会自动矢量化,但只有在它认为执行SWAR是有利可图的情况下(例如,使用64位整数寄存器直接对数组进行异或,或者甚至使用SWAR位黑客来阻止/纠正字节之间的进位的字节和)例如
gcc -O1 -mavx
仍然仅仅使用标量代码。通常,如果您需要完全优化而不是自动矢量化,则应使用类似
-O3 -march=znver1 -fno-tree-vectorize
的代码其他编译器
以上所有这些对于clang也是正确的,只是它不理解
-mgeneral-regs-only
。(我认为您需要-mno-mmx -mno-sse
,也许还需要其他选项。)(The Effect of Architecture When Using SSE / AVX Intrinisics重复了其中的一些信息)
对于MSVC / ICC,您 * 可以 * 使用您没有告诉编译器它可以自己使用的伊萨扩展的内部函数。因此,例如,没有
-arch:AVX
的MSVC-O2
将允许它使用SSE 2自动矢量化(因为这是x86-64的基线),并使用movaps
复制大约16字节的结构体或其他内容。但是使用MSVC风格的目标选项,您仍然可以使用
_mm_cvtepi8_epi32
(pmovsxwd
)等SSE 4内部函数,甚至AVX内部函数,而无需告诉编译器允许自己使用这些指令。旧版MSVC在使用AVX /AVX 2内部函数而不使用
-arch:AVX
时会产生非常糟糕的asm,例如导致在同一个函数中混合VEX和传统SSE编码(例如对_mm_add_ps
这样的128位内部函数使用非VEX编码),以及在256位向量后无法使用vzeroupper,这两种情况对性能都是灾难性的。但是我认为现代的MSVC已经基本解决了这个问题,尽管它仍然没有对内部函数进行太多的优化,比如甚至没有通过它们进行常量传播。
不优化intrinsic可能与MSVC允许你编写
if(avx_supported) { __m256 v = _mm256_load_ps(p); ...
等代码的能力有关。如果它试图优化,它必须跟踪沿着执行路径已经看到的最小扩展级别,这些路径可以到达任何给定的intrinsic,这样它就知道什么替代方案是有效的。ICC也是这样。出于同样的原因,GCC不能将具有不同目标选项的函数内联到彼此中,因此不能使用
__attribute__((target("")))
来避免运行时调度的开销;您仍然希望避免循环内的函数调用开销,即确保AVX 2函数内有循环,否则可能不值得使用AVX 2版本,只需使用SSE 2版本。我对英特尔新推出的OneAPI编译器ICX不太了解,我觉得它是基于LLVM的,所以可能更像是clang。
mzillmmw2#
目前使用的是gcc 11.3.1或更高版本。我不是程序员,但能区分C和C++。我在github /doom 9论坛上制作最新的编解码器已经有三年了。在我的旧英特尔“核心”(R)(TM)i5- 2500 K CPU@3. 30 GHz我注意到了。在C语言中,你可以播放SIMD AVX 2 ex.汇编器编解码器的非SIMD处理器。我们可以使用论坛上发布的编解码器吗?谁知道呢。例如,libjpeg,dav 1d与SIMD没有mavx 2。
xeve,xevd,uvg 266,无人机战斗系统3e,无人机战斗系统3d,空中通讯,libavif
在C++ SIMD AVX 2中,你甚至不会打开帮助。第二件事是线程和Unix与Windows的兼容性。在C中,这比在C中工作得更快。在C中,你还必须添加一些未经测试的特殊附加项,如mingw-std-thread到g中,以使一切正常工作。关于C的另一个好奇心。MSYS 2 GCC 12.1.0。在AVX 2/AVX 3中制作的编解码器可以在旧的处理器上打开。它是如何制作的?我不知道,但不具备上述功能。
(C++20 openjpeg)语言支持:压缩,压缩,解压,压缩,解压,解压,压缩,解压,解压,解压,解压,解压,解压,解压,解压,解压,解压,解压,解压,解压