为什么"#pragma omp simd"在gcc编译器下只能在"-O2"中获得很大的性能提升?

Nan*_*iao 3 performance gcc simd openmp auto-vectorization

检查以下代码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

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

$ gcc -fopenmp parallel.c
$ ./a.out
Work consumed 2.084107 seconds
Run Code Online (Sandbox Code Playgroud)

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

$ gcc -fopenmp parallel.c
$ ./a.out
Work consumed 2.112724 seconds
Run Code Online (Sandbox Code Playgroud)

我们可以看到" #pragma omp simd"并没有获得巨大的性能提升.但如果我添加-O2选项,没有" #pragma omp simd":

$ gcc -O2 -fopenmp parallel.c
$ ./a.out
Work consumed 0.446662 seconds
Run Code Online (Sandbox Code Playgroud)

用" #pragma omp simd":

$ gcc -O2 -fopenmp parallel.c
$ ./a.out
Work consumed 0.126799 seconds
Run Code Online (Sandbox Code Playgroud)

我们可以看到一个很大的进步.但如果使用-O3,没有" #pragma omp simd":

$ gcc -O3 -fopenmp parallel.c
$ ./a.out
Work consumed 0.127563 seconds
Run Code Online (Sandbox Code Playgroud)

用" #pragma omp simd":

$ gcc -O3 -fopenmp parallel.c
$ ./a.out
Work consumed 0.126727 seconds
Run Code Online (Sandbox Code Playgroud)

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

为什么" #pragma omp simd"只会-O2gcc编译器中带来很大的性能提升?

Pet*_*des 7

忘记计时-O0,这是浪费时间.

gcc -O3尝试自动向量化所有循环,因此使用OpenMP pragma只能帮助您获得循环,否则只能在编译器必须满足纯C的自动向量化的所有可能情况下自动向量化-ffast-math,restrict限定符或其他障碍才能正确(显然没有障碍:这里它不是减少,你有纯粹的垂直操作.而且你在静态数组上运行,所以编译器可以看到它们没有重叠)

gcc -O2-ftree-vectorize如果您使用OpenMP pragma在特定循环上请求它,则不启用,因此您只能获得自动向量化.


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


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

有意义的是,如果您要求使用OpenMP进行SIMD矢量化,则可能需要对齐数据以避免缓存行拆分.但是C传递的方法并不是很方便,因为指针的float对齐比a的宽度更多float.(特别是它通常具有该属性,即使你需要该功能在极少数情况下仍然可以工作).

  • gcc给pragma simd一个比其他编译器更小的角色.在省略其他要求的限制限定符的情况下,它仍会产生差异(在-O2/3). (2认同)
  • 我希望GCC和Clang默认至少像ICC一样"-O2".我甚至没有看到`-O0或-O1`的意思.我使用调试器几次,问题只出现在优化代码中.我一直想问这个问题,但是`-O1`的重点是什么. (2认同)