目前,我正在尝试实现拉普拉斯方程的算法。我已经看了几个实现,并且我坚持认为放置OpenMP的pragma声明的最佳位置。
while( var > tol && iter <= maxIter ) {
++iter;
var = 0.0;
for (i=1; i<=n; ++i)
for (j=1; j<=n; ++j) {
Tnew[i*n2+j] = 0.25*( T[(i-1)*n2+j] + T[(i+1)*n2+j]
+ T[i*n2+(j-1)] + T[i*n2+(j+1)] );
var = fmax(var, fabs(Tnew[i*n2+j] - T[i*n2+j]));
}
}
就个人而言,我认为最好的情况是将它置于内循环之前,因为我认为它不会导致依赖性问题。但是,一位朋友告诉我,这样做太贵了。什么是最好的方法,为什么?
答案 0 :(得分:0)
首先,我认为您忘记在所有外循环迭代结束时交换数组Tnew
和T
。
假设这些是指针,这里是经过纠正的串行代码:
while(var > tol && iter <= maxIter) {
++iter;
var = 0.0;
for (i=1; i<=n; ++i)
for (j=1; j<=n; ++j) {
Tnew[i*n2+j] = 0.25*(T[(i-1)*n2+j] + T[(i+1)*n2+j]
+ T[i*n2+(j-1)] + T[i*n2+(j+1)]);
var = fmax(var, fabs(Tnew[i*n2+j] - T[i*n2+j]));
}
}
temp = TNew;
TNew = T;
T = temp;
}
现在,您可能希望尝试并行化i
- 循环。请注意,var
必须是共享变量,因此会在最里面的并行区域中对其更新进行竞争;我们将使用critical
构造来确保原子性。这是näive代码:
while(var > tol && iter <= maxIter) {
++iter;
var = 0.0;
#pragma omp parallel for
for (i=1; i<=n; ++i)
for (j=1; j<=n; ++j) {
Tnew[i*n2+j] = 0.25*(T[(i-1)*n2+j] + T[(i+1)*n2+j]
+ T[i*n2+(j-1)] + T[i*n2+(j+1)]);
#pragma omp critical
{
var = fmax(var, fabs(Tnew[i*n2+j] - T[i*n2+j]));
}
}
}
temp = TNew;
TNew = T;
T = temp;
}
正如你的朋友正确指出的那样,如果parallel
区域被创建并被大量销毁,则线程创建/终止的开销可能很高。因此,在master
或single
构造的帮助下,我们现在应该尝试将此并行构造移动到最外层循环之外。以下是示例翻译:
#pragma omp parallel private(iter) shared(var, maxIter)
{
while(var > tol && iter <= maxIter) {
++iter;
#pragma omp barrier
#pragma omp single
{
var = 0.0;
}
#pragma omp for
for (i=1; i<=n; ++i)
for (j=1; j<=n; ++j) {
Tnew[i*n2+j] = 0.25*(T[(i-1)*n2+j] + T[(i+1)*n2+j]
+ T[i*n2+(j-1)] + T[i*n2+(j+1)]);
#pragma omp critical
{
var = fmax(var, fabs(Tnew[i*n2+j] - T[i*n2+j]));
}
}
}
#pragma omp single
{
temp = TNew;
TNew = T;
T = temp;
}
}
}
请注意,私有化iter
非常重要。另请注意,显式和隐式障碍确保了对var
,TNew
和T
的不同访问之间没有竞争。
关于critical
构造:请注意,在最内层循环内多次调用关键区域可能成本很高。您应该使用私有变量来获取每个线程的最大差异,然后使用for循环的外部来获取var
的正确值。我会把实际的翻译留给你。
建议:测试一些大数据集的代码,即n
(约500-2000)的一些大值和tol
的小值(约0.01-0.001),与串行代码相比,可以看到任何可见的加速;否则,同步成本可能会中和任何并行性增益,使并行版本的效率低于串行版本。