用于 simd 的 pragma omp 不会在 GCC 中生成矢量指令

pragma omp for simd does not generate vector instructions in GCC

pragma omp for simd OpenMP 指令是否生成使用 SIMD 寄存器的代码?

更长: 如 OpenMP documentationworksharing-loop SIMD 构造指定一个或多个关联循环的迭代将分布在已经存在的线程中 [..] 使用 SIMD 指令”。从这个声明中,我希望以下代码 (simd.c) 在编译 运行 gcc simd.c -o simd -fopenmp 时使用 XMMYMMZMM 寄存器,但是它没有。

#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 标志。

但是,结合使用标志 -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[]) 循环融合。