如何并行化openmp中的while循环-共轭梯度

时间:2019-01-21 17:38:39

标签: c algorithm parallel-processing openmp

我有一个while循环,想使用OpenMP在2个线程上并行化它。循环中的变量不依赖于先前迭代中的值,因此我认为必须有某种使其并行化的方法。我有2个线程,所以每次循环同时进行2次while循环,每个循环执行自己的计算。此循环的目标是找到alfa的值,这是共轭梯度法中用于寻找最佳点的步长。

我想我必须以某种方式利用alfaalfaIter变量和OpenMP语句来使此并行循环工作,但不知道如何工作。

    #pragma omp parallel num_threads(2)
    {
    while (alfaSet == false) {
        alfaIter++;
        alfa = pow(gamma, alfaIter - 1);

        b = 0;

        for (i = 0; i < dim; i++) {
            testX[i] = x[i] + alfa * d[i];
        }
        for (i = 0; i < dim; i++) {
            b += d[i] * g[i];
        }
        if (shanno(testX, dim) - shanno(x, dim) <= delta * alfa * b) {
            alfaIter = 0;
            alfaSet = true;
        }
    }
    }

编辑1:这种实现似乎还可以:

    #pragma omp parallel num_threads(alfaThreads)
    {
    int alfaIter = omp_get_num_threads();
    int step = omp_get_num_threads();
    double localAlfa = alfa;
    double *testX = (double *) malloc(dim * sizeof(double));
    while (!alfaSet) {
        #pragma omp barrier
        alfaIter += step;
        localAlfa = pow(gamma, alfaIter - 1);
        for (i = 0; i < dim; i++) {
            testX[i] = x[i] + localAlfa * d[i];
        }
        if (func(testX, dim) - delta * localAlfa * b <= oldFunc) {
            #pragma omp critical
            {
                if (!alfaSet) {
                    alfaSet = true;
                    alfaIter = 0;
                    alfa = localAlfa;
                }
            }
        } 
    }
    free(testX);
    }

因此,在使用这段代码一段时间后,我发现没有任何同步,因此线程不再彼此等待,并且它们以不可预测的方式到达了代码的各个部分。 OpenMP barrier现在可以同步它们,并且我总是得到相同的迭代次数以及性能增益。但是,有时程序现在会崩溃。僵局?如何检查导致崩溃的原因以及如何防止崩溃?

这是算法的全部实现:https://gist.github.com/mazury/394adc82ab51ce36acfae297ae0555ce

1 个答案:

答案 0 :(得分:0)

#pragma omp parallel 在多个线程上并行运行以下代码。因此,几个循环将同时运行。所有这些版本都将获取全局变量并同时或多或少地更新它们,而您不能简单地控制会发生什么。

例如,很可能以不受控制的方式修改了alfaIter,从而导致未定义的行为。

这是处理器的第一行代码如何执行

1 read alfaIter in local var (register)
2 var++
3 write register var in alfaIter
4 fetch alfaIter to call pow and put it in stack or register
5 call pow(...)

让我们说这些指令是线程A中的1a 2a 3a 4a 5a和线程B中的1b 2b 3b 4b 5b。

现在执行的实际顺序是什么?

假设是

1a 2a 3a 4a 5a 1b 2b 3b 4b 5b. 

行为将符合预期。 Pow在线程A中使用alfaIter = 1调用,在线程B中使用alfaIter = 2

调用

但是其他排序可能导致不同的行为

例如

1a 1b (both local regs in thrd A and B have initial value of 0)
2a 3a (alfaIter=1 written back to memory by thead A)
2b 3b (alfaIter=1 written back to memory by thead B)
4a 4b 5a 5c (pow called by both threads with the same value of alfaIter=1)

由于任何排序都是可能的,因此循环的行为是不可预测的。

使其可预测的解决方案是通过原子操作实现的。 在这种情况下,您可以确保对内存的访问是顺序的,并且while循环的行为将符合预期。

但这有一个主要缺点。原子操作非常长,在现代处理器上通常需要约100个周期。这将大大降低您的代码速度,并使它的速度比顺序版本慢很多。

通常,最好的方法是使用for循环,但您似乎无法使用。

我建议的是渲染大多数var局部变量,运行将alfaIter递增2(或按线程数增加)的并行线程,并仅将全局操作用于终止条件。

示例代码:

#pragma omp parallel num_threads(2)
{
  int alfaIter=omp_get_thread_num();
  int step=omp_get_num_threads();
  float alfa;
  float testX[dim],b; 
      // and maybe d[] and g[] but I do not understand what they do
  while (alfaSet == false) { // no problem to read a global var
    alfaIter+=step;
    alfa = pow(gamma, alfaIter - 1);
    b = 0;
    for (i = 0; i < dim; i++) {
        testX[i] = x[i] + alfa * d[i];
    }
    for (i = 0; i < dim; i++) {
        b += d[i] * g[i];
    }
    if (shanno(testX, dim) - shanno(x, dim) <= delta * alfa * b) {
     #pragma omp critical
      if (! alfaSet) { // you can do safe operations here
        alfaIter = 0;
        alfaSet = true;
      }
    }
  }
} 

未经测试,但可以作为起点。

相关问题