OpenMP 中的缩减操作在幕后是如何工作的?
How does reduction operations in OpenMP work under the hood?
我正在尝试优化 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[= 来提高性能23=]变量带数组delta[number_of_threads],其中每个线程在计算的时候会写入数组的不同位置,然后对所有元素求和平行区域
每个线程在其堆栈帧上都有自己的 '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
}
以上请当pseudo-code。实际代码模式将取决于实现、内联和 OpenMP 实现可能执行的各种其他优化。如果您想了解一些有关工作原理的信息,请查看 [1] 和 [2]。
__omp_reduce()
的实现可以是 tree-based 或使用锁或原子指令的顺序。 OpenMP 实现通常相当聪明,可以为机器选择正确的算法 and/or 正在使用的线程数。
进行 delta[numthreads]
修改可能会使性能降低 100 倍以上,因为这是 false-sharing 的典型示例,如线程 0 的 delta[0]
和线程 0 的 delta[1]
线程一将在同一个缓存行中,这会导致缓存和内存上的大量流量。更好的方法是引入 patting 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/
我正在尝试优化 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[= 来提高性能23=]变量带数组delta[number_of_threads],其中每个线程在计算的时候会写入数组的不同位置,然后对所有元素求和平行区域
每个线程在其堆栈帧上都有自己的 '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
}
以上请当pseudo-code。实际代码模式将取决于实现、内联和 OpenMP 实现可能执行的各种其他优化。如果您想了解一些有关工作原理的信息,请查看 [1] 和 [2]。
__omp_reduce()
的实现可以是 tree-based 或使用锁或原子指令的顺序。 OpenMP 实现通常相当聪明,可以为机器选择正确的算法 and/or 正在使用的线程数。
进行 delta[numthreads]
修改可能会使性能降低 100 倍以上,因为这是 false-sharing 的典型示例,如线程 0 的 delta[0]
和线程 0 的 delta[1]
线程一将在同一个缓存行中,这会导致缓存和内存上的大量流量。更好的方法是引入 patting 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/