我做了一些旧的OpenMP练习来练习一下,但我很难找到解决方案。
目标是编写与依赖关系图对应的最简单的 OpenMP 代码。
图表在这里可见:https://i.stack.imgur.com/MAzu1.jpg
第一个很简单。
它对应于以下代码:
#pragma omp parallel
{
#pragma omp simple
{
#pragma omp task
{
A1();
A2();
}
#pragma omp task
{
B1();
B2();
}
#pragma omp task
{
C1();
C2();
}
}
}
第二个仍然很容易。
#pragma omp parallel
{
#pragma omp simple
{
#pragma omp task
{
A1();
}
#pragma omp task
{
B1();
}
#pragma omp task
{
C1();
}
#pragma omp barrier
A2();
B2();
C2();
}
}
现在是最后一个...这让我很烦恼,因为所有函数调用的依赖项数量都不相等。我以为有一个明确说明您应该等待的任务,但我在 OpenMP 文档中找不到我要查找的内容。
如果有人对这个问题有解释,我将不胜感激,因为我已经考虑了一个多月了。
首先,OpenMP 4.5 规范中没有#pragma omp simple
。 我想你的意思是#pragma omp single
.
如果是这样pragma omp barrier
在single
区域内是一个坏主意,因为只有一个线程将执行代码并等待不执行该区域的所有其他线程。
此外,在 A2 上的第二个中,B2 和 C2 不再作为任务并行执行。
对于您的尖锐问题: 您正在寻找的似乎是 OpenMP Secification 第 169 页中任务构造的depend
子句。
马西米利亚诺对依赖子句及其工作原理有一个很好的解释。
一旦你了解了那里发生了什么,最后一个例子就不是那么复杂了:每个任务Tn
都依赖于上一个迭代T-1_n
及其邻居(T-1_n-1
和T-1_n+1
(。这种模式被称为雅可比模板。这在偏微分方程求解器中很常见。
正如Henkersmann所说,最简单的选择是使用OpenMP Task的depend
子句:
int val_a[N], val_b[N];
#pragma omp parallel
#pragma omp single
{
int *a = val_a;
int *b = val_b;
for( int t = 0; t < T; ++t ) {
// Unroll the inner loop for the boundary cases
#pragma omp task depend(in:a[0], a[1]) depend(out:b[0])
stencil(b, a, i);
for( int i = 1; i < N-1; ++i ) {
#pragma omp task depend(in:a[i-1],a[i],a[i+1])
depend(out:b[i])
stencil(b, a, i);
}
#pragma omp task depend(in:a[N-2],a[N-1]) depend(out:b[N-1])
stencil(b, a, N-1);
// Swap the pointers for the next iteration
int *tmp = a;
a = b;
b = tmp;
}
#pragma omp taskwait
}
如您所见,OpenMP 任务依赖关系是点对点的,这意味着您无法用阵列区域来表示它们。
另一种选择,对于这种特定情况来说更干净一些,是使用屏障间接强制依赖关系:
int a[N], b[N];
#pragma omp parallel
for( int t = 0; t < T; ++t ) {
#pragma omp for
for( int i = 0; i < N-1; ++i ) {
stencil(b, a, i);
}
}
第二种情况在每次内部循环完成时执行同步屏障。同步粒度更粗,因为每个外部循环迭代只有 1 个同步点。但是,如果stencil
函数很长且不平衡,则可能值得使用任务。