如何解决并行化嵌套的for循环:分解数据
我有两个带有整数参数的函数;称为 f 和 g 。我还有另一个函数 h ,它带有两个整数参数。给定一个大小为D的正方形U(意思是:{m0,m0 + 1,..,m0 + D-1} x {n0,n0 + 1,...,n0 + D-1}),我有一个过程给定包含数组f(m0),f(m0 + 1)的数组farr
,garr
,用于计算D上的f(n)g(m)h(n,m)的时间大致线性),...,f(m0 + D-1)和g(n0),g(n0 + 1),...,g(n0 + D-1);让我们将该程序视为一个黑盒,通过调用Sum(farr,garr,m0,n0,D)来调用它。我们可以计算出farr [0] = f(m0),... farr [D-1] = f(m0 + D-1)或garr [0] = garr(n0),garr [1] = g(n0通过调用Fillf(f,m0,D)和Fillg(g,m0,D)+1),...,garr [n0 + D-1]在时间上大致线性于D。
问题是如何有效计算{0,1,...,rD-1} x {中的所有(n,m)的f(n)g(m)h(n,m)的总和0,1,...,rD-1}(说)并行。摘要很简单-我想知道的是如何在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,D);
S+=Sum(farr,garr,D)
free(garr);
free(farr);
这已经足够好了,但是它的缺点是farr
和garr
的每个段都被计算了r次而不是一次。考虑到整体的计算复杂度没有改变(不会比O(r ^ 2 D)好),这几乎不是一个悲剧,但是仍然是不可取的。
另一种方法是写
S=0;
#pragma omp parallel for schedule(dynamic) private(m0,garr) reduction(+:S)
for(m0=0; m0<r*D; m0+=D) {
farr = (short *) calloc(D,sizeof(int));
Fillf(farr,D);
for(n0=0; n0<r*D; n0+=D) {
garr = (short *) calloc(D,D)
free(garr);
}
free(farr);
}
这也是一个可行的解决方案,但是:(a)garr
的每个段仍获得r次而不是一次的计算,(b)如果可用线程数比{ {1}}(但小于r
)。在这里我们不能使用r^2
,因为两个循环之间正在发生某些事情。
显然应该可以做得更好。使用OpenMP编写或多或少显而易见的过程的直接方法是什么? (应该将collapse(2)
和farr
的一个预先计算的分段的大小约为sqrt(s)D,其中s是可用线程的数量,然后对{{ 1}}和garr
遍历大约D个sqrt的长度?)
解决方法
如果要避免对ComboBox
和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,D) free(garr); } S += S2; } free(farr);
假设其数组参数最初是零填充的(如Fillf()
确保),那么从循环中提升内存分配将需要在每次调用之前手动进行零填充,但这仍然比重新分配和重新分配便宜。 - 我删除了调度子句,因为看来动态调度不会对这种计算有任何好处
- 将
calloc()
声明为私有,只会使指针成为私有,而不是指针指向的数据。而且,由于farr
和farr
都没有在并行循环中被修改,因此将它们声明为私有实际上并没有用。我将示例代码中的m0
子句留在了这里,主要是作为这些说明的重点。 - 重新计算
private
的各种值所节省的费用与更细粒度的并行性带来的额外开销相抵触。如果内部循环的每次迭代的工作仍然相对较大,那么这更可能是一个胜利。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。