openMP输出的可再现性问题

时间:2019-04-09 15:59:14

标签: c unix openmp

我正在阅读openMP教程,随着我的进步,我编写了openMP版本的代码,该代码使用积分来计算PI。

我写了一个串行版本,所以我知道串行版本还可以。一旦openMP版本完成,我注意到每次运行它都会给我一个不同的答案。如果我进行了几次运行,我可以看到输出大致在正确的数字附近,但是我仍然没想到几次openMP运行会给出不同的答案。

#include<stdio.h>
#include<stdlib.h>
#include<omp.h>

void main()

{ int nb=200,i,blob;



 float summ=0,dx,argg;
 dx=1./nb;

 printf("\n dx------------: %f \n",dx);


 omp_set_num_threads(nb);
 #pragma omp parallel
 {

 blob=omp_get_num_threads();

 printf("\n we have now %d number of threads...\n",blob);

 int ID=omp_get_thread_num();
 i=ID;
 printf("\n i is now: %d \n",i);

 argg=(4./(1.+i*dx*i*dx))*dx;
 summ=summ+argg;
 printf("\t\t and summ is %f \n",summ);
 }


 printf("\ntotal summ after loop: %f\n",summ);

 }

我使用gcc -f mycode.c -fopenmp在RedHat上编译此代码,当我运行它时,说3次,我得到:

3.117

3.113

3.051

有人可以帮助我理解为什么我得到不同的结果吗?难道我做错了什么?并行性只是拼接积分间隔,​​但是当计算矩形时,它们在最后求和时应该相同,不是吗?

串行版本给我3.13

(我没有得到3.14的事实是正常的,因为我对积分进行了非常粗略的采样,在0和1之间只有200个除法)

我也尝试添加一个障碍,但是我仍然得到不同的答案,尽管更接近于串行版本,但其值仍然分散且不相同...

2 个答案:

答案 0 :(得分:2)

我认为问题在于在并行循环之外声明int ifloat argg

正在发生的事情是,您的所有200个线程都覆盖了iargg,因此有时某个线程的argg被另一个线程的argg覆盖,导致您观察到的无法预测的错误。

这是一个工作代码,始终显示相同的值(最多6位小数):

void main()
{
    int nb = 200, blob;
    float summ = 0, dx;// , argg;
    dx = 1. / nb;

    printf("\n dx------------: %f \n", dx);

    omp_set_num_threads(nb);
#pragma omp parallel
    {

        blob = omp_get_num_threads();

        printf("\n we have now %d number of threads...\n", blob);

        int i = omp_get_thread_num();
        printf("\n i is now: %d \n", i);

        float argg = (4. / (1. + i * dx*i*dx))*dx;
        summ = summ + argg;
        printf("\t\t and summ is %f \n", summ);
    }

    printf("\ntotal summ after loop: %f\n", summ);
}

但是,将最后一行更改为%.9f则表明实际上并不是完全相同的浮点数。这是由于浮点数加法中的数值错误。 a + b + c不保证与a + c + b相同的结果。您可以在下面的示例中尝试此操作:

在定义float* arr = new float[nb];之后,首先在并行循环之前先添加arr[i] = argg;,然后在并行循环之前添加argg,然后在并行循环内添加float testSum = 0; for (int i = 0; i < nb; i++) testSum += arr[i]; printf("random sum: %.9f\n", testSum); std::sort(arr, arr + nb); testSum = 0; for (int i = 0; i < nb; i++) testSum += arr[i]; printf("sorted sum: %.9f\n", testSum); testSum = 0; for (int i = nb-1; i >= 0; i--) testSum += arr[i]; printf("reversed sum: %.9f\n", testSum); ,在内部 。然后在并行循环后的之后添加以下内容:

omp_set_num_threads(nb);

最有可能的是,尽管排序和和反向和是由完全相同的200个数字相加构成的,但它们还是有细微差别的。

您可能要注意的另一件事是,您不太可能找到实际上可以并行运行200个线程的处理器。售价$ 15k的Xeon Platinum 9282,最普通的处理器可以处理4到32个线程,而专用服务器处理器可以达到112个线程。

因此,我们通常执行以下操作:

我们删除了int i = omp_get_thread_num();以使用建议的线程数

我们从循环中删除了int i,以使用#pragma omp parallel for for (int i = 0; i < nb; i++) {...}

我们将循环重写为for循环:

document.onmousedown = function(e) {
  if (e.shiftKey && e.which == 1) {
  alert("Mouse and Shift was pressed");
  }
};

结果应该是相同的,但是您现在仅使用实际硬件上可用的尽可能多的线程。这样可以减少线程之间的上下文切换,并应提高代码的时间性能。

答案 1 :(得分:1)

问题来自变量summarggi。它们属于全局顺序范围,如果没有预防措施,则不能对其进行修改。您将在线程之间发生争用,这可能导致这些var中出现意外值。种族是完全不确定的,这可以解释您获得的不同结果。根据对这些var的读写时间变化,您也可能会获得正确的结果或任何不正确的结果。

处理此问题的正确方法:

  • 用于变量arggi:它们是在全局范围内声明的,但是它们用于在线程中执行速度计算。您应该:在并行域中声明它们以使它们成为私有线程,或者在omp指令中添加private(argg,i)。请注意,blob还有一个潜在的问题,但是它的值在所有线程中都是相同的,因此不应修改程序的行为。

  • 对于变量summ,情况有所不同。这确实是一个全局变量,它从线程中累积一些值。它必须保持全局,但是在修改它时必须添加atomic openmp指令。对该变量执行完整的读取-修改-写入操作将变得牢不可破,这将确保无种族歧视的修改。

这是代码的修改后的版本,可提供一致的结果(但浮点数不具有关联性,最后一位十进制可能会更改)。

#include<stdio.h>
#include<stdlib.h>
#include<omp.h>

void main()

{
  int nb=200,i,blob;
  float summ=0,dx,argg;
  dx=1./nb;

  printf("\n dx------------: %f \n",dx);

  omp_set_num_threads(nb);
# pragma omp parallel private(argg,i)
  {
    blob=omp_get_num_threads();

    printf("\n we have now %d number of threads...\n",blob);

    int ID=omp_get_thread_num();
    i=ID;
    printf("\n i is now: %d \n",i);

    argg=(4./(1.+i*dx*i*dx))*dx;
    #pragma omp atomic
    summ=summ+argg;

    printf("\t\t and summ is %f \n",summ);
  }

  printf("\ntotal summ after loop: %f\n",summ);

}

如前所述,这不是使用线程的最佳方法。创建和同步线程的成本很高,很少需要拥有比核心数量更多的线程。