【问题标题】:Why does "#pragma omp simd" only take big performance improvement in "-O2" under gcc compiler?为什么“#pragma omp simd”只在 gcc 编译器下的“-O2”中有很大的性能提升?
【发布时间】:2018-06-07 23:08:18
【问题描述】:

检查以下代码:

#include <stdio.h>
#include <omp.h>

#define ARRAY_SIZE  (1024)
float A[ARRAY_SIZE];
float B[ARRAY_SIZE];
float C[ARRAY_SIZE];

int main(void)
{   
    for (int i = 0; i < ARRAY_SIZE; i++)
    {
        A[i] = i * 2.3;
        B[i] = i + 4.6;
    }

    double start = omp_get_wtime();
    for (int loop = 0; loop < 1000000; loop++)
    {
        #pragma omp simd
        for (int i = 0; i < ARRAY_SIZE; i++)
        {
            C[i] = A[i] * B[i];
        }
    }
    double end = omp_get_wtime();
    printf("Work consumed %f seconds\n", end - start);
    return 0;
}

在我的机器上构建并运行它,它输出:

$ gcc -fopenmp parallel.c
$ ./a.out
Work consumed 2.084107 seconds

如果我注释掉“#pragma omp simd”,再次构建并运行它:

$ gcc -fopenmp parallel.c
$ ./a.out
Work consumed 2.112724 seconds

我们可以看到“#pragma omp simd”并没有获得很大的性能提升。但是如果我添加-O2 选项,则没有“#pragma omp simd”:

$ gcc -O2 -fopenmp parallel.c
$ ./a.out
Work consumed 0.446662 seconds

带“#pragma omp simd”:

$ gcc -O2 -fopenmp parallel.c
$ ./a.out
Work consumed 0.126799 seconds

我们可以看到很大的改进。但是如果使用-O3,则没有“#pragma omp simd”:

$ gcc -O3 -fopenmp parallel.c
$ ./a.out
Work consumed 0.127563 seconds

使用“#pragma omp simd”:

$ gcc -O3 -fopenmp parallel.c
$ ./a.out
Work consumed 0.126727 seconds

我们可以再次看到结果相似。

为什么“#pragma omp simd”在gcc编译器下只对-O2有很大的性能提升?

【问题讨论】:

  • 看起来编译器在使用 O3 时会进一步优化您的代码,并且可能会利用 simd 指令。您是否比较了生成的程序集?

标签: performance gcc openmp simd auto-vectorization


【解决方案1】:

忘记-O0it's a total waste of time 的时间安排。

gcc -O3 尝试自动矢量化所有循环,因此使用 OpenMP 编译指示只能帮助您处理循环,否则这些循环只能使用 -ffast-mathrestrict 限定符或其他所有可能的情况下的正确性障碍进行自动矢量化编译器必须满足纯 C 的自动向量化。(显然这里没有障碍:这里不是简化,而且您有纯垂直操作。并且您正在对静态数组进行操作,因此编译器可以看到它们不重叠)

gcc -O2 不启用-ftree-vectorize,因此只有在使用 OpenMP 编译指示在特定循环上请求它时才能获得自动矢量化。


请注意,clang-O2 启用自动矢量化。


GCC 自动矢量化策略在 OpenMP 和 vanilla 之间可能有所不同。 IIRC,对于 OpenMP 循环,gcc 可能只使用未对齐的加载/存储,而不是使用标量直到达到对齐边界。如果数据在运行时对齐,则这对 AVX 没有性能缺点,即使在编译时不知道这一事实。与 gcc 的大量完全展开的启动/清理代码相比,它节省了大量的代码膨胀。

如果您要求使用 OpenMP 进行 SIMD 矢量化,那么您可能已经对齐数据以避免缓存行拆分,这是有道理的。但是 C 并不能很方便地传递指向float 的指针比float 的宽度具有更多对齐的事实。 (特别是它通常具有该属性,即使您需要该功能在极少数情况下仍然可以正常工作,而实际上它没有)。

【讨论】:

  • gcc 赋予 pragma simd 比其他编译器更小的角色。在您省略其他需要的限制限定符的情况下,它仍然会有所不同(在 -O2/3 处)。
  • @tim18:是的,好点,这也是适用于整数代码的“或某事”的一部分。
  • 我希望 GCC 和 Clang 至少像 ICC 一样默认为 -O2。我什至看不到-O0 or -O1 的意义。几次我使用调试器时,问题只出现在优化的代码中。我一直想问这个问题,但-O1 有什么意义。
  • 我发现了一个使用 OpenMP 导致 GCC 不能很好地矢量化代码的情况。代码是here。使用 Clang 没有问题,但要使用 GCC 获得最佳结果,我必须将 kernel 函数移动到单独的目标文件(在没有 -fopenmp 的情况下编译)。所以有时 OpenMP 实际上会使优化变得更糟。
  • 当对齐比数据类型更严格时,pragma omp simd 至少有助于避免使用依赖于编译器的对齐提示。
猜你喜欢
  • 2016-05-03
  • 2016-10-04
  • 2020-07-24
  • 2013-09-20
  • 2011-04-16
  • 2017-11-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多