[英]Trouble with nested loops and openmp
我在将openmp应用于这样的嵌套循环时遇到了麻烦:
#pragma omp parallel shared(S2,nthreads,chunk) private(a,b,tid)
{
tid = omp_get_thread_num();
if (tid == 0)
{
nthreads = omp_get_num_threads();
printf("\nNumber of threads = %d\n", nthreads);
}
#pragma omp for schedule(dynamic,chunk)
for(a=0;a<NREC;a++){
for(b=0;b<NLIG;b++){
S2=S2+cos(1+sin(atan(sin(sqrt(a*2+b*5)+cos(a)+sqrt(b)))));
}
} // end for a
} /* end of parallel section */
当我将序列号与openmp版本进行比较时,最后一个给出了奇怪的结果。 即使我删除了#pragma omp,openmp的结果也不正确,您知道为什么还是可以指向一个关于双循环和openmp的好的教程吗?
这是种族状况的经典例子。 每个openmp线程都在同时访问和更新共享值,并且不保证某些更新不会丢失(充其量),或者所得到的答案不会变得乱七八糟(最坏的情况)。
比赛条件的问题在于,他们敏感地取决于时间。 在较小的情况下(例如,使用较小的NREC和NLIG),您有时可能会错过这一点,但是在较大的情况下,它将最终总是出现。
没有#pragma omp for
会得到错误答案的原因是,一旦您进入并行区域,所有的openmp线程就会启动; 除非你使用类似的omp for
(所谓工作分享结构)分裂的工作,每个线程都做在并行部分的一切 -所以所有的线程会做同样的全部资金,全部更新S2
simultatneously。
您必须小心OpenMP线程更新共享变量。 OpenMP具有atomic
操作,可让您安全地修改共享变量。 下面是一个示例(不幸的是,您的示例对求和顺序非常敏感,很难看到发生了什么,因此我对您的求和进行了一些更改:)。 在mysumallatomic
,每个线程都像以前一样更新S2
,但是这次安全地完成了它:
#include <omp.h>
#include <math.h>
#include <stdio.h>
double mysumorig() {
double S2 = 0;
int a, b;
for(a=0;a<128;a++){
for(b=0;b<128;b++){
S2=S2+a*b;
}
}
return S2;
}
double mysumallatomic() {
double S2 = 0.;
#pragma omp parallel for shared(S2)
for(int a=0; a<128; a++){
for(int b=0; b<128;b++){
double myterm = (double)a*b;
#pragma omp atomic
S2 += myterm;
}
}
return S2;
}
double mysumonceatomic() {
double S2 = 0.;
#pragma omp parallel shared(S2)
{
double mysum = 0.;
#pragma omp for
for(int a=0; a<128; a++){
for(int b=0; b<128;b++){
mysum += (double)a*b;
}
}
#pragma omp atomic
S2 += mysum;
}
return S2;
}
int main() {
printf("(Serial) S2 = %f\n", mysumorig());
printf("(All Atomic) S2 = %f\n", mysumallatomic());
printf("(Atomic Once) S2 = %f\n", mysumonceatomic());
return 0;
}
但是,原子操作确实会损害并行性能(毕竟,要点是防止在变量S2
周围进行并行操作!),因此更好的方法是进行求和,并且仅在两次求和之后才执行原子操作,而不是执行128。 * 128次; 那是mysumonceatomic()
例程,该例程仅产生每个线程一次同步开销,而不是每个线程16,000次。
但这是一种常见的操作,无需您自己实施。 可以使用OpenMP内置功能进行约简操作(约简是一种操作,如计算列表总和,查找列表的最小值或最大值等,仅通过查看即可一次完成一个元素到目前为止的结果以及下一个元素),如@ejd所建议。 OpenMP将运行并且速度更快(它的优化实现比其他OpenMP操作自己可以完成的速度要快得多)。
如您所见,这两种方法都有效:
$ ./foo
(Serial) S2 = 66064384.000000
(All Atomic) S2 = 66064384.000000
(Atomic Once) S2 = 66064384.00000
问题不在于双循环,而在于变量S2。 尝试在您的for指令上放置一个减少条款:
#pragma omp用于排定(动态,块)减少(+:S2)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.