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