我正在尝试在 parallel-for 循环中优化性能,在该循环中我有一个归约变量(称为 delta ),想知道OpenMP库如何在后台对其进行处理。
让我们以下面的代码为例,在循环的开头,我只是将变量声明为归约变量:
#pragma omp parallel shared(delta, A, B, rows, colms) private(i, j)
.
.
.
#pragma omp for reduction(+:delta)
for (i=1; i<=rows; i++){
for (j=1; j<=colms; j++){
delta += fabs(A[i][j]- B[i][j]);
}
}
.
.
.
//end of parallel region
我想知道在计算期间每个线程在访问 delta 变量时是否设置了锁定,并且是否可以通过将 delta 变量替换为数组< em> delta [number_of_threads] ,其中每个线程在计算过程中将在数组的不同位置写入内容,然后将并行区域之后的所有元素求和。
答案 0 :(得分:3)
每个线程在其堆栈帧上都有自己的“ delta”副本:
#pragma omp parallel shared(delta, A, B, rows, colms) private(i, j)
{
double local_delta; // one copy per thread
__omp_init_schedule(1, rows, &lb, &ub);
for (i=lb; i<=ub; i++) {
for (j=1; j<=colms; j++) {
local_delta += fabs(A[i][j]- B[i][j]);
}
}
__omp_reduce(&delta, local_delta); // accumulate thread's delta with shared var
__omp_barrier(); // do the barrier of the for construct
}
请使用以上代码作为伪代码。实际的代码模式将取决于OpenMP实现可能会执行的实现,内联以及各种其他优化。如果您想阅读一些有关工作原理的信息,请查看[1]和[2]。
__omp_reduce()
的实现可以是基于树的,也可以是使用锁或原子指令的顺序实现。 OpenMP实现通常非常聪明,可以为计算机和/或所使用的线程数选择正确的算法。
进行delta[numthreads]
修改可能会使性能降低100倍以上,因为这是错误共享的典型示例,因为线程0的delta[0]
和线程1的delta[1]
在同一缓存行中,这会导致缓存和内存上的大量流量。更好的方法是引入拍拍delta[numthreads * 8]
(假设delta
是8个字节),以便每个线程都有自己的缓存行。但是,您仍然需要执行最后的聚合,并且OpenMP实现可能仍然做得更好。
[2] https://www.dontknow.de/openmp-stuff/thunk-you-very-much-or-how-do-openmp-compilers-work-part-2/