OpenMP:无法更改缩减变量

时间:2015-10-06 09:19:13

标签: parallel-processing openmp reduction

在我读到减少变量的初始值是根据用于减少的运算符设置之后,我决定不是记住这些默认值而是明确地初始化它。所以我修改了question by Totonga中的代码,如下所示

const int num_steps = 100000;
double x, sum, dx = 1./num_steps;

#pragma omp parallel private(x) reduction(+:sum)
{
  sum = 0.;
  #pragma omp for schedule(static)
  for (int i=0; i<num_steps; ++i)
  {
    x = (i+0.5)*dx;
    sum += 4./(1.+x*x);
  }
}

但事实证明,无论我是写sum = 0.还是sum = 123.456,代码都会产生相同的结果(使用gcc-4.5.2编译器)。有人可以解释一下为什么吗? (如果可能的话,参考openmp标准)提前感谢所有人。

P.S。由于有些人反对初始化缩减变量,我认为稍微扩展一个问题是有意义的。下面的代码按预期工作:我初始化简化变量并获得结果,这取决于我的初始值

int sum;
#pragma omp parallel reduction(+:sum)
{
  sum = 1;
}
printf("Reduction sum = %d\n",sum);

打印结果将是核心数,而不是0。

P.P.S我必须再次更新我的问题。用户吉尔斯给出了一个富有洞察力的评论:并且在退出并行区域时,这些本地值将使用+运算符和变量的初始值在进入该部分之前减少。

嗯,以下代码为我提供了结果3.142592653598146,其结果很糟糕pi而不是预期的103.141592653598146(初始代码给了我极好的pi=3.141592653598146价值)

const int num_steps = 100000;
double x, sum, dx = 1./num_steps;

sum = 100.;
#pragma omp parallel private(x) reduction(+:sum)
{
  #pragma omp for schedule(static)
  for (int i=0; i<num_steps; ++i)
  {
    x = (i+0.5)*dx;
    sum += 4./(1.+x*x);
  }
}

1 个答案:

答案 0 :(得分:4)

你为什么要那样做?这只是乞求你所有的灵魂来解决麻烦。 reduction子句以及初始化局部变量的方式是出于某种原因而定义的,并且您的想法是您不需要仅仅因为它们已经是正确的而记住这些初始化值。

但是,在您的代码中,行为未定义。让我们看看为什么......

我们假设您的初始代码是:

const int num_steps = 100000;
double x, sum, dx = 1./num_steps;

sum = 0.;
for (int i=0; i<num_steps; ++i) {
    x = (i+0.5)*dx;
    sum += 4./(1.+x*x);
}

嗯,与OpenMP并行化的“正常”方式是:

const int num_steps = 100000;
double x, sum, dx = 1./num_steps;

sum = 0.;
#pragma omp parallel for reduction(+:sum) private(x)
for (int i=0; i<num_steps; ++i) {
    x = (i+0.5)*dx;
    sum += 4./(1.+x*x);
}

非常简单,不是吗?

现在,代替那个,你做:

const int num_steps = 100000;
double x, sum, dx = 1./num_steps;

#pragma omp parallel private(x) reduction(+:sum)
{
  sum = 0.;
  #pragma omp for schedule(static)
  for (int i=0; i<num_steps; ++i)
  {
    x = (i+0.5)*dx;
    sum += 4./(1.+x*x);
  }
}

您遇到问题......原因是在进入parallel区域后,sum尚未初始化。因此,当您声明omp parallel reduction(+:sum)时,您创建了sum的每线程私有版本,初始化为与您reduction子句的运算符对应的“逻辑”初始值,即0你要求减少+。退出parallel区域后,在进入该区域之前,将使用+运算符以及变量的初始值来减少这些局部值。见this for reference

  

reduction子句指定reduce-identifier和一个或多个   列出项目。对于每个列表项,将在每个列表项中创建一个私有副本   隐式任务或SIMD通道,并使用初始化程序初始化   减少标识符的值。在该地区结束后,   原始列表项使用私有副本的值进行更新   使用与缩减标识符相关联的组合器

总而言之,退出后您将拥有相当于sum += sum_local_0 + sum_local_1 + ... sum_local_nbthreadsMinusOne

的内容

因此,由于在您的代码中,sum没有任何初始值,因此退出parallel区域时的值也未定义,并且可以是任何... < / p>

现在让我们假设你确实初始化了它...然后,如果不是在parallel区域内使用正确的初始化器(就像上面代码中的sum=0.;那样),那么无论出于何种原因你都会使用它sum=1.;相反,最后的总和不会仅增加1,而是增加parallel区域内线程数的1倍,因为额外的值将会被计算的次数与线程数一样多。

总而言之,只需使用reduction子句和变量“预期”/“天真”的方式,这将使您和为维护您的代码而烦恼的人们免除。

编辑:看起来我的观点不够明确,所以我会尝试更好地解释一下:

此代码:

int sum;
#pragma omp parallel reduction(+:sum)
{
  sum = 1;
}
printf("Reduction sum = %d\n",sum);

具有未定义的行为,因为它等同于:

int sum, numthreads;
#pragma omp parallel
#pragma omp single
numthreads = omp_get_num_threads();

sum += numthreads; // value of sum is undefined since it never was initialised
printf("Reduction sum = %d\n",sum);

现在,此代码有效:

int sum = 0; //here, sum has been initialised
#pragma omp parallel reduction(+:sum)
{
  sum = 1;
}
printf("Reduction sum = %d\n",sum);

为了说服自己,请阅读我给出的标准片段:

  

该地区结束后,   原始列表项已更新,其中包含私有副本的值   使用与缩减标识符相关联的组合器

因此,减少使用私有减少变量和原始值的组合来在退出时执行最终减少。因此,如果未设置原始值,则最终值也是未定义的。而且这不是因为出于某种原因你的编译器给你一个似乎正确的值,代码是正确的。

现在更清楚了吗?