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 上大致线性的总和,给定数组farr
,garr
包含 f(m0),f(m0+1),...,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 +1),...,garr[n0+D-1] 通过调用 Fillf(f,m0,D) 和 Fillg(g,m0,D) 在时间上大致线性地分布在 D 上。
问题是如何有效地计算 {0,1,...,rD-1}x{0,1 中所有 (n,m) 的 f(n) g(m) h(n,m) 之和,...,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,n0,D);
S+=Sum(farr,garr,m0,n0,D)
free(garr);
free(farr);
这工作得很好,但它的缺点是每个段farr
和garr
被计算 r 次而不是一次。考虑到整体计算复杂度没有改变(它不会比 O(r^2 D) 更好),这几乎不是悲剧,但仍然是不可取的。
另一种方法是写
S=0;
#pragma omp parallel for schedule(dynamic) private(m0,n0,farr,garr) reduction(+:S)
for(m0=0; m0<r*D; m0+=D) {
farr = (short *) calloc(D,sizeof(int));
Fillf(farr,m0,D);
for(n0=0; n0<r*D; n0+=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 是可用线程的数量,然后使用collapse(2)
for执行嵌套循环m0
并n0
遍历长度约为 D sqrt(s) 的段?)
解决方案
如果您想避免重复计算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);
另请注意,
- 如果
Fillf()
假设它的数组参数最初是零填充的(calloc()
确保),那么将内存分配提升到循环之外将需要在每次调用之前手动填充零,但这仍然可能比释放和重新分配便宜。 - 我删除了调度子句,因为动态调度似乎对这种计算没有任何好处
- 声明
farr
私有只使指针私有,而不是它指向的数据。此外,由于在并行循环中既没有farr
也没有m0
修改,因此将它们声明为私有实际上并没有用处。我private
将示例代码中的子句主要作为这些评论的焦点。 - 重新计算各种值的节省与
garr
细粒度并行带来的额外开销相抵触。如果内循环每次迭代的工作量还是比较大的话,这更有可能是胜利。
推荐阅读
- android - 将文本视图放在文本中心
- node.js - 尝试使用 node.js 中的 jira-connector 库从 JIRA 获取问题的 json 列表
- php - 收件箱系统的 Laravel 查询
- python - 如何在 tensorflow 中绘制训练误差、验证误差和预测精度?
- python - selenium 可以在 Outlook Online 中找到_element_by_xpath 吗?
- c# - 扩展 AuditedAttribute 以替换或屏蔽审计值
- javascript - 完整日历描述中的新行
- c# - Mailkit MimeKit.MimeMessage 错误:未知的初始化参数:System.Byte []
- php - 匹配正则表达式
- javascript - 在可重用的 Razor 类库中包含静态文件