本文调研了4篇与OpenMP优化相关的文献,对优化点分析如下:
Open64有着过程间分析优化部件,因此可以知道哪些函数使用了被调函数,从而可以通过在使用被调函数处放置合适的编译指导语句来完成并行区重构。
这样做的好处是:
以下给出例子:
program main
call sub_procedure
end
subroutine sub_procedure
!$omp parallel
P
!$omp end parallel
end
优化后:
program main
!$omp parallel
call sub_procedure
!$omp end parallel
end
subroutine sub_procedure
P
end
在OpenMP中可对并行循环指定调度方案,以将每个迭代分配给多个工作线程执行。其一般形式如下:
#pragma omp for schedule(schedule_name, chunk_size)
for(i = 0; i < N; i++)
论文中给出了一种使用启发式规则来估计各种额外开销和调度参数的关系,得到一个线性不等式组,可以通过求解该不等式组得到较优的调度参数。
在OpenMP语句中每一次对变量的声明都对应一次新的地址分配。给出以下例子:
#pragma omp parallel
{
#pragma omp for private(a)
{...}
#pragma omp for private(a)
{...}
}
在如上代码中,编译器会为每个循环分配一个单独的私有变量,而优化后的代码如下所示:
#pragma omp parallel private(a)
{
#pragma omp for
{...}
#pragma omp for
{...}
}
对于某些循环语句,存在依赖而导致无法使用OpenMP优化,但是这其中的某些依赖可以通过修改代码去除依赖而使用OpenMP运行代码。
下列循环存在反依赖:
for(int i = 0; i < n; i++) {
x = (b[i] + c[i]) / 2;
a[i] = a[i + 1] + x;
}
除去循环之间的依赖后:
#pragma omp parallel for shared(a, a_copy)
for(int i = 0; i < n; i++) {
a_copy[i] = a[i + 1];
}
#pragma omp parallel for shared(a, a_copy) private(x)
for(int i = 0; i < n; i++) {
x = (b[i] + c[i]) / 2;
a[i] = a_copy[i] + x;
}
下列循环存在流依赖:
for(int i = 1; i < n; i++) {
b[i] = b[i] + a[i - 1];
a[i] = a[i] + c[i];
}
在loop skewing之后:
b[1] = b[1] + a[0]
#pragma omp parallel for shared(a, b, c)
for(int i = 1; i < n - 1; i++) {
a[i] = a[i] + c[i];
b[i + 1] = b[i + 1] + a[i];
}
a[n - 1] = a[n - 1] + c[n - 1];
下段代码使用流水线形式处理,以块的形式读取数据,然后处理每个块并在下一个块之前将结果写入磁盘,造成极差的负载均衡。
for(i = 0; i < N; i++) {
readfromfile(i, ...);
for(int j = 0; j < processingnum; j++) {
processdata(); //lots of work
}
writetofile(i);
}
接下来这段代码使用动态调度来重叠I/O和处理数据,将上述流水线代码并行化。
#pragma omp parallel
{
/* preload data to be used in first iteration of the i-loop */
#pragma omp single
{ReadFromFile(O,...);}
for (i=0; i<N; i++) {
/* preload data for next iteration of the i-loop */
#pragma omp single nowait
{ReadFromFile(i+1...);}
#pragma omp for schedule(dynamic)
for (j=0; j<ProcessingNum; j++)
ProcessChunkOfData(); /* here is the work */
/* there is a barrier at the end of this loop */
#pragma omp single nowait
{writeResultsToFile(i);}
} /* threads immediately move on to next iteration of i-loop */
} /* one parallel region encloses all the work */
int a[Nthreads][cache_line_size];
#pragma omp parallel for shared(Nthreads, a) schedule(static,1)
for (int i = 0; i < Nthreads; i++)
a[i] += i;
一般情况下,int型变量占四个字节,A[0]和A[1]的地址只差四个字节,小于一个Cache行,它们有着极大的可能在同一Cache行内,从而导致同时更新不同处理器的相同Cache行中的单个元素会导致整个Cache行无效。
对于False sharing问题,一般可以通过填充数组来优化。
int a[Nthreads][cache_line_size];
#pragma omp parallel for shared(Nthreads, a) schedule(static,1)
for (int i = 0; i < Nthreads; i++)
a[i][0] += i;
我们还对文献中的部分优化使用LLVM Flang编译器和classic-flang编译器进行了测试,测试结果请参考https://gitee.com/src-openeuler/flang/pulls/22/files。
更多回帖