OpenMP循环为同一个串行循环提供不同的结果

时间:2014-11-28 03:18:08

标签: c parallel-processing openmp multicore reduction

我有一些序列码:

double* a = malloc((1000000) * sizeof(double));
double* b = malloc((1000000) * sizeof(double));
double totalA = 0;

for (int i = 0; i < 1000000; i++) {

    if (i == 0) {
        a[i] = sin(i);
    }

    b[i] = sin(i+1);

    if (i < 1000000-1) {
        a[i+1] = b[i];
    }

    totalA += a[i];
}

此串行循环后totalA的输出为0.232883978073

然后我有一个OpenMP版本(注意:所有变量都重新初始化):

double* a = malloc((1000000) * sizeof(double));
double* b = malloc((1000000) * sizeof(double));
double totalA = 0;

#pragma omp parallel for reduction(+:totalA)
for (int i = 0; i < 1000000; i++) {

    if (i == 0) {
        a[i] = sin(i);
    }

    b[i] = sin(i+1);

    if (i < 1000000-1) {
        a[i+1] = b[i];
    }

    totalA += a[i];
}

但是,此代码中totalA的输出为-0.733714826779

我无法弄清楚为什么会有不同之处。

感谢。


更新

经过多次游戏后,似乎奇怪地忽略了循环中的if语句。 if块中的实际语句在循环的所有迭代上执行(就好像if子句不存在一样)。

例如,将if块更改为:

if (i < 555555) {
    a[i+1] = b[i];
}

似乎完全没有区别。

我仍然不知道这里发生了什么。

2 个答案:

答案 0 :(得分:5)

您的代码包含race condition。冲突的语句是写入数组a[i+1] = b[i];的赋值a和从totalA += a[i];读取的语句a

在您的代码中,无法保证在从该位置读取的迭代之前执行负责写入数组中特定位置的迭代。

为了进一步证明这个问题,对包含冲突语句的循环段进行排序可以解决问题(但最有可能破坏性能):

#pragma omp parallel for ordered reduction(+:totalA)    
for (int i = 0; i < 1000000; i++) {

   if (i == 0) {
     a[i] = sin(i);
   }

  b[i] = sin(i+1);

  #pragma omp ordered
  {
    if (i < 1000000-1) {
      a[i+1] = b[i];
    }

    totalA += a[i];
  }
}

最好完全避免这个问题并重写你的程序以摆脱循环携带的依赖:

#define N 1000000

double* a = malloc(N * sizeof(double));
double* b = malloc(N * sizeof(double));
double totalA = 0;

a[0] = sin(0);
totalA += a[0];

#pragma omp parallel for reduction(+:totalA)
for (int i = 0; i < N - 1; i++) {
  b[i] = sin(i + 1);
  a[i + 1] = b[i];
  totalA += a[i + 1];
}

b[N - 1] = sin(N);

最后,请注意,sin(0) ≈ 0.0为语句

a[0] = sin(0);
totalA += a[0];

可以简单地替换为

a[0] = 0.0;

答案 1 :(得分:3)

您可以将主循环简化为:

a[0] = sin(0);
for (int i = 0; i < N-1; i++) {
    b[i] = sin(i+1);
    a[i+1] = b[i];
    totalA += a[i];
}
totalA += a[N-1];
b[N-1] = sin(N);

让我们为thread1 i1和thread2 i2调用迭代器。然后当例如i2=i1+1a[i1+1]的写入和a[i2]的读取将是相同的地址,读取或写入的值将取决于第一个线程的顺序。这是竞争条件。

一个简单的解决方法是观察totalA = a[0] + b[0] + b[1] + ... b[N-2]

a[0] = sin(0);
totalA += a[0];
#pragma omp parallel for reduction(+:totalA)
for (int i = 0; i < N-1; i++) {
    b[i] = sin(i+1);
    a[i+1] = b[i];
    totalA += b[i];
}
b[N-1] = sin(N);

这会为0.232883978073生成N=1000000