C++ 并行化嵌套for循环:打包数据
我有两个带整数参数的函数;叫他们f和g。我还有另一个函数h,它有两个整数参数。给定一个大小为D的平方U(意思是:{m0,m0+1,…,m0+D-1}x{n0,n0+1,…,n0+D-1}),我有一个程序来计算f(n)g(m)h(n,m)在时间上的和,在D上大致线性,给定数组C++ 并行化嵌套for循环:打包数据,c++,c,openmp,nested-loops,C++,C,Openmp,Nested Loops,我有两个带整数参数的函数;叫他们f和g。我还有另一个函数h,它有两个整数参数。给定一个大小为D的平方U(意思是:{m0,m0+1,…,m0+D-1}x{n0,n0+1,…,n0+D-1}),我有一个程序来计算f(n)g(m)h(n,m)在时间上的和,在D上大致线性,给定数组farr,garr包含f(m0),f(m0+1),…,f(m0+D-1)和g(n0),g(n0+1),…);让我们将该过程视为一个黑箱,我们通过调用和(farr,garr,m0,n0,D)调用它。我们可以通过调用Fillf(f
farr
,garr
包含f(m0),f(m0+1),…,f(m0+D-1)和g(n0),g(n0+1),…);让我们将该过程视为一个黑箱,我们通过调用和(farr,garr,m0,n0,D)调用它。我们可以通过调用Fillf(f,m0,D)和Fillg(g,m0,D),在时间上大致线性地计算farr[0]=f(m0),…farr[D-1]=f(m0+D-1)或garr[0]=garr(n0),garr[1]=g(n0+1),…,garr[n0+D-1]
问题是如何有效地并行计算{0,1,…,rD-1}x{0,1,…,rD-1}(假设)中所有(n,m)的f(n)g(m)h(n,m)之和。这在抽象上很简单——我想知道的是如何在OpenMP中实现这一点
最简单的方法可能是这样的:
S=0;
#pragma omp parallel for collapse(2) schedule(dynamic) private(m0,n0,farr,garr) reduction(+:S)
for(m0=0; m0<r*D; m0+=D)
for(n0=0; n0<r*D; n0+=D)
farr = (short *) calloc(D,sizeof(int));
Fillf(farr,m0,D);
garr = (short *) calloc(D,sizeof(int));
Fillg(garr,n0,D);
S+=Sum(farr,garr,m0,n0,D)
free(garr);
free(farr);
这也是一个有效的解决方案,但是:(a)每个garr
段仍然得到r次而不是一次计算,(b)如果可用线程的数量远远大于r
(但小于r^2
),并行化将是低效的。这里我们不能使用collapse(2)
,因为在这两个循环之间发生了一些事情
显然应该可以做得更好。使用OpenMP编写或多或少显而易见的过程的简单方法是什么?(一个人是否应该预先计算大小约为sqrt(s)D的
farr
和garr
段,其中s是可用线程的数量,然后对长度约为D sqrt(s)的段执行嵌套循环,并对m0
和n0
进行折叠(2))如果要避免重复计算farr
和garr
,则至少有两个选项:
garr
,并采用与第二种方法相同的方法。也可以选择提前计算并存储所有的farr
farr
的分配和解除分配从循环中提出来:
S = 0;
farr = malloc(D, sizeof(int));
for (int m0 = 0; m0 < r * D; m0 += D) {
int S2 = 0;
Fillf(farr, m0, D);
#pragma omp parallel for private(farr, m0) reduction(+:S2)
for (int n0 = 0; n0 < r * D; n0 += D) {
int *garr = calloc(D, sizeof(int));
Fillg(garr, n0, D);
S2 += Sum(farr, garr, m0, n0, D)
free(garr);
}
S += S2;
}
free(farr);
S=0;
farr=malloc(D,sizeof(int));
对于(int m0=0;m0
- 如果
假设它的数组参数最初是零填充的(正如Fillf()
所确保的那样),那么将内存分配从循环中提升出来需要在每次调用之前手动进行零填充,但这仍然可能比释放和重新分配便宜calloc()
- 我删除了scheduling子句,因为动态调度似乎对这种计算没有任何好处
- 声明
private只会使指针私有,而不是它所指向的数据。此外,由于在并行循环中既不修改farr
也不修改farr
,因此将它们声明为私有实际上没有什么用处。我在示例代码中留下了m0
子句,主要是作为这些评论的焦点private
- 重新计算
的各种值可以节省细粒度并行带来的额外开销。如果内部循环的每个迭代的工作量仍然相对较大,那么这更有可能是一个胜利garr
collapse(2)
),并使用多个线程来执行它,那么我认为没有其他方法可以避免重复计算garr
。毕竟,这就是问题所在。然而,这种复制产生更快的代码并非不可能。
S = 0;
farr = malloc(D, sizeof(int));
for (int m0 = 0; m0 < r * D; m0 += D) {
int S2 = 0;
Fillf(farr, m0, D);
#pragma omp parallel for private(farr, m0) reduction(+:S2)
for (int n0 = 0; n0 < r * D; n0 += D) {
int *garr = calloc(D, sizeof(int));
Fillg(garr, n0, D);
S2 += Sum(farr, garr, m0, n0, D)
free(garr);
}
S += S2;
}
free(farr);