为什么“#pragma omp simd”在 gcc 编译器下只对“-O2”进行大的性能改进?
Why does "#pragma omp simd" only take big performance improvement in "-O2" under gcc compiler?
检查以下代码:
#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
中有很大的性能提升?
忘记 -O0
、 的时间安排。
gcc -O3
尝试自动矢量化所有循环,因此使用 OpenMP pragmas 只会帮助您进行循环,否则只能使用 -ffast-math
、restrict
限定符或其他来自动矢量化在编译器为纯 C 的自动矢量化必须满足的所有可能情况下的正确性障碍。(显然这里没有障碍:这里不是减少,你有纯粹的垂直操作。你在静态数组上操作所以编译器可以看到它们不重叠)
gcc -O2
不启用 -ftree-vectorize
,因此如果您使用 OpenMP pragmas 在特定循环上请求它,您只能获得自动矢量化。
请注意 clang
在 -O2
启用自动矢量化。
GCC 自动矢量化策略在 OpenMP 和 vanilla 之间可能有所不同。 IIRC,对于 OpenMP 循环,gcc 可能只使用未对齐的加载/存储,而不是在到达对齐边界之前使用标量。如果数据在运行时对齐,即使在编译时不知道这一事实,这对 AVX 来说没有性能缺点。与 gcc 的大量完全展开的启动/清理代码相比,它节省了大量代码膨胀。
如果您要求使用 OpenMP 进行 SIMD 向量化,您可能已经对齐数据以避免缓存行拆分,这是有道理的。但是 C 并不能很方便地传递指向 float
的指针比 float
的宽度更对齐的事实。 (特别是它 通常 具有 属性,即使您需要该功能在极少数情况下仍能正常工作)。
检查以下代码:
#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
中有很大的性能提升?
忘记 -O0
、
gcc -O3
尝试自动矢量化所有循环,因此使用 OpenMP pragmas 只会帮助您进行循环,否则只能使用 -ffast-math
、restrict
限定符或其他来自动矢量化在编译器为纯 C 的自动矢量化必须满足的所有可能情况下的正确性障碍。(显然这里没有障碍:这里不是减少,你有纯粹的垂直操作。你在静态数组上操作所以编译器可以看到它们不重叠)
gcc -O2
不启用 -ftree-vectorize
,因此如果您使用 OpenMP pragmas 在特定循环上请求它,您只能获得自动矢量化。
请注意 clang
在 -O2
启用自动矢量化。
GCC 自动矢量化策略在 OpenMP 和 vanilla 之间可能有所不同。 IIRC,对于 OpenMP 循环,gcc 可能只使用未对齐的加载/存储,而不是在到达对齐边界之前使用标量。如果数据在运行时对齐,即使在编译时不知道这一事实,这对 AVX 来说没有性能缺点。与 gcc 的大量完全展开的启动/清理代码相比,它节省了大量代码膨胀。
如果您要求使用 OpenMP 进行 SIMD 向量化,您可能已经对齐数据以避免缓存行拆分,这是有道理的。但是 C 并不能很方便地传递指向 float
的指针比 float
的宽度更对齐的事实。 (特别是它 通常 具有 属性,即使您需要该功能在极少数情况下仍能正常工作)。