用于 simd 的 pragma omp 不会在 GCC 中生成矢量指令
pragma omp for simd does not generate vector instructions in GCC
短:pragma omp for simd
OpenMP 指令是否生成使用 SIMD 寄存器的代码?
更长:
如 OpenMP documentation“worksharing-loop SIMD 构造指定一个或多个关联循环的迭代将分布在已经存在的线程中 [..] 使用 SIMD 指令”。从这个声明中,我希望以下代码 (simd.c) 在编译 运行 gcc simd.c -o simd -fopenmp
时使用 XMM
、YMM
或 ZMM
寄存器,但是它没有。
#include <stdio.h>
#define N 100
int main() {
int x[N];
int y[N];
int z[N];
int i;
int sum;
for(i=0; i < N; i++) {
x[i] = i;
y[i] = i;
}
#pragma omp parallel
{
#pragma omp for simd
for(i=0; i < N; i++) {
z[i] = x[i] + y[i];
}
#pragma omp for simd reduction(+:sum)
for(i=0; i < N; i++) {
sum += x[i];
}
}
printf("%d %d\n",z[N/2], sum);
return 0;
}
检查汇编程序时生成 运行 gcc simd.c -S -fopenmp
没有使用 SIMD 寄存器。
我可以使用选项 -O3
在没有 OpenMP 的情况下使用 SIMD 寄存器,因为根据 GCC documentation
它包括 -ftree-vectorize
标志。
XMM
注册:gcc simd.c -o simd -O3
YMM
注册:gcc simd.c -o simd -O3 -march=skylake-avx512
ZMM
注册:gcc simd.c -o simd -O3 -march=skylake-avx512 -mprefer-vector-width=512
但是,结合使用标志 -march=skylake-avx512 -mprefer-vector-width=512
和 -fopenmp
不会生成 SIMD 指令。
因此,我可以轻松地使用 -O3
对我的代码进行向量化而不使用 pragma omp for simd
,但反之则不行。
此时,我的目的不是生成 SIMD 指令,而是了解 OpenMP SIMD 指令在 GCC 中的工作原理以及如何仅使用 OpenMP(没有 -O3
)生成 SIMD 指令。
至少启用 -O2
以使 -fopenmp
正常工作,并获得一般性能
gcc simd.c -S -fopenmp
GCC 的默认值是 -O0
,针对一致的调试进行了反优化。它永远不会使用 -O0
进行自动矢量化,因为当来自 C 源代码的每个 i
值都必须存在于内存中时,它毫无意义,依此类推。 Why does clang produce inefficient asm with -O0 (for this simple floating point sum)?
当您必须能够一次单步执行源代码行,甚至在 运行 时使用调试器修改 i
或内存内容,并且拥有程序时,这也是不可能的保持 运行ning 就像您期望的 C 抽象机那样。
没有任何优化的构建对于性能来说完全是垃圾;甚至考虑您是否足够关心使用 OpenMP 的性能。(当然实际调试除外。)通常,从反优化到优化标量的加速比您从矢量化中获得的更多标量代码,但两者都可能是很大的因素,因此您肯定希望在自动矢量化之外进行优化。
I can use SIMD registers without OpenMP using the option -O3
because according to GCC documentation it includes the -ftree-vectorize
flag.
对,就这样吧。 -O3 -march=native -flto
通常是编译主机上 运行 代码的最佳选择。此外,-fno-trapping-math -fno-math-errno
应该对所有内容都是安全的,并启用一些更好的 FP 函数内联,即使你不想要 -ffast-math
。也最好是 -fprofile-generate
/ -fprofile-use
profile-guided optimization (PGO),展开热循环并适当地选择分支与无分支等。
#pragma omp parallel
在 -O3 -fopenmp
时仍然有效 - GCC 默认不启用自动并行化。
此外,#pragma omp simd
有时会使用不同的矢量化风格。在您的情况下,它似乎让 GCC 忘记了它知道数组是 16 字节对齐的,并使用 movdqu
加载(当 AVX 不可用于 paddd xmm0, [rax]
的未对齐内存源操作数时)。比较 https://godbolt.org/z/8q8Dqm - main
调用的 main._omp_fn.0:
辅助函数不假定对齐。 (尽管如果 GCC 不费心做矢量大小的块,也许它不能在除以线程数后将数组拆分成范围?)
使用 -O2 -fopenmp
得到您期望的结果
OpenMP 将让 gcc 更容易或更有效地对您没有使用 restrict
函数指针参数的循环进行矢量化,以让它知道数组不重叠,或让浮点数让它知道即使您不使用 -ffast-math
.
,也假装 FP 数学是关联的
或者如果您启用了一些优化而不是完全优化(例如 -O2
不包括 -ftree-vectorize
),则#pragma omp
将按您预期的方式工作。
请注意,x[i] = y[i] = i;
init 循环不会在 -O2
处自动矢量化,但 #pragma
循环会。并且没有 -fopenmp
,纯标量。 Godbolt compiler explorer
串行 -O3
代码将 运行 对于这个小 N
更快,因为线程启动开销远不值得。但是对于大 N,如果单个内核不能使内存带宽饱和(例如,在 Xeon 上,但大多数 dual/quad-core 桌面 CPU 几乎可以用一个内核使内存带宽饱和),则并行化可能会有所帮助。或者,如果您的阵列在不同内核的缓存中很热。
不幸的是(?)甚至 GCC -O3 也无法通过您的整个代码进行持续传播并仅打印结果。或者将 z[i] = x[i]+y[i]
循环与 sum(x[])
循环融合。
短:pragma omp for simd
OpenMP 指令是否生成使用 SIMD 寄存器的代码?
更长:
如 OpenMP documentation“worksharing-loop SIMD 构造指定一个或多个关联循环的迭代将分布在已经存在的线程中 [..] 使用 SIMD 指令”。从这个声明中,我希望以下代码 (simd.c) 在编译 运行 gcc simd.c -o simd -fopenmp
时使用 XMM
、YMM
或 ZMM
寄存器,但是它没有。
#include <stdio.h>
#define N 100
int main() {
int x[N];
int y[N];
int z[N];
int i;
int sum;
for(i=0; i < N; i++) {
x[i] = i;
y[i] = i;
}
#pragma omp parallel
{
#pragma omp for simd
for(i=0; i < N; i++) {
z[i] = x[i] + y[i];
}
#pragma omp for simd reduction(+:sum)
for(i=0; i < N; i++) {
sum += x[i];
}
}
printf("%d %d\n",z[N/2], sum);
return 0;
}
检查汇编程序时生成 运行 gcc simd.c -S -fopenmp
没有使用 SIMD 寄存器。
我可以使用选项 -O3
在没有 OpenMP 的情况下使用 SIMD 寄存器,因为根据 GCC documentation
它包括 -ftree-vectorize
标志。
XMM
注册:gcc simd.c -o simd -O3
YMM
注册:gcc simd.c -o simd -O3 -march=skylake-avx512
ZMM
注册:gcc simd.c -o simd -O3 -march=skylake-avx512 -mprefer-vector-width=512
但是,结合使用标志 -march=skylake-avx512 -mprefer-vector-width=512
和 -fopenmp
不会生成 SIMD 指令。
因此,我可以轻松地使用 -O3
对我的代码进行向量化而不使用 pragma omp for simd
,但反之则不行。
此时,我的目的不是生成 SIMD 指令,而是了解 OpenMP SIMD 指令在 GCC 中的工作原理以及如何仅使用 OpenMP(没有 -O3
)生成 SIMD 指令。
至少启用 -O2
以使 -fopenmp
正常工作,并获得一般性能
gcc simd.c -S -fopenmp
GCC 的默认值是 -O0
,针对一致的调试进行了反优化。它永远不会使用 -O0
进行自动矢量化,因为当来自 C 源代码的每个 i
值都必须存在于内存中时,它毫无意义,依此类推。 Why does clang produce inefficient asm with -O0 (for this simple floating point sum)?
当您必须能够一次单步执行源代码行,甚至在 运行 时使用调试器修改 i
或内存内容,并且拥有程序时,这也是不可能的保持 运行ning 就像您期望的 C 抽象机那样。
没有任何优化的构建对于性能来说完全是垃圾;甚至考虑您是否足够关心使用 OpenMP 的性能。(当然实际调试除外。)通常,从反优化到优化标量的加速比您从矢量化中获得的更多标量代码,但两者都可能是很大的因素,因此您肯定希望在自动矢量化之外进行优化。
I can use SIMD registers without OpenMP using the option
-O3
because according to GCC documentation it includes the-ftree-vectorize
flag.
对,就这样吧。 -O3 -march=native -flto
通常是编译主机上 运行 代码的最佳选择。此外,-fno-trapping-math -fno-math-errno
应该对所有内容都是安全的,并启用一些更好的 FP 函数内联,即使你不想要 -ffast-math
。也最好是 -fprofile-generate
/ -fprofile-use
profile-guided optimization (PGO),展开热循环并适当地选择分支与无分支等。
#pragma omp parallel
在 -O3 -fopenmp
时仍然有效 - GCC 默认不启用自动并行化。
此外,#pragma omp simd
有时会使用不同的矢量化风格。在您的情况下,它似乎让 GCC 忘记了它知道数组是 16 字节对齐的,并使用 movdqu
加载(当 AVX 不可用于 paddd xmm0, [rax]
的未对齐内存源操作数时)。比较 https://godbolt.org/z/8q8Dqm - main
调用的 main._omp_fn.0:
辅助函数不假定对齐。 (尽管如果 GCC 不费心做矢量大小的块,也许它不能在除以线程数后将数组拆分成范围?)
使用 -O2 -fopenmp
得到您期望的结果
OpenMP 将让 gcc 更容易或更有效地对您没有使用 restrict
函数指针参数的循环进行矢量化,以让它知道数组不重叠,或让浮点数让它知道即使您不使用 -ffast-math
.
或者如果您启用了一些优化而不是完全优化(例如 -O2
不包括 -ftree-vectorize
),则#pragma omp
将按您预期的方式工作。
请注意,x[i] = y[i] = i;
init 循环不会在 -O2
处自动矢量化,但 #pragma
循环会。并且没有 -fopenmp
,纯标量。 Godbolt compiler explorer
串行 -O3
代码将 运行 对于这个小 N
更快,因为线程启动开销远不值得。但是对于大 N,如果单个内核不能使内存带宽饱和(例如,在 Xeon 上,但大多数 dual/quad-core 桌面 CPU 几乎可以用一个内核使内存带宽饱和),则并行化可能会有所帮助。或者,如果您的阵列在不同内核的缓存中很热。
不幸的是(?)甚至 GCC -O3 也无法通过您的整个代码进行持续传播并仅打印结果。或者将 z[i] = x[i]+y[i]
循环与 sum(x[])
循环融合。