c - openMP 输出的再现性问题

标签 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);

 }

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

3.117

3.113

3.051

谁能帮助理解为什么我会得到不同的结果?难道我做错了什么?平行度只是拼接了积分区间,但是随着矩形的计算,最后加起来应该是一样的吧?

串行版给我3.13

(我没有得到 3.14 是正常的,因为我使用了非常粗略的积分采样,在 0 和 1 之间只有 200 个分度)

我也尝试过添加一个障碍,但我仍然得到不同的答案,虽然更接近串行版本,但值仍然存在差异且不相同......

最佳答案

我认为问题在于在并行循环外声明 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 表明它实际上不是完全相同的 float 。这是由于浮点加法中的数值错误。 a+b+c 不保证与 a+c+b 相同的结果。你可以在下面的例子中试试这个:

首先添加float* arr = new float[nb];并行循环之前AND 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);

很可能,排序和和反向和略有不同,即使它们是由完全相同的 200 个数字相加组成的。

您可能需要注意的另一件事是,您不太可能找到实际上可以并行运行 200 个线程的处理器。最常见的处理器可以处理 4 到 32 个线程,而专用服务器处理器可以使用值(value) 15,000 美元的 Xeon Platinum 9282 处理多达 112 个线程。

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

我们删除 omp_set_num_threads(nb); 以使用推荐的线程数

我们删除 int i = omp_get_thread_num(); 以在 for 循环中使用 int i

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

#pragma omp parallel for
for (int i = 0; i < nb; i++)
    {...}

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

关于c - openMP 输出的再现性问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55596868/

相关文章:

c - 如何在open mp的for循环中解析 "return 0 or return 1"?

c++ - 警告 #2901 : [omp] OpenMP is not active; all OpenMP directives will be ignored

c - 如何在 Bash C 源代码中实现 Tab 补全

c - 尝试按地址将数组传递给函数来对数组进行排序时出现段错误 11

linux - 我如何在使用“ls -la”时隐藏终端中链接文件夹的原始目录

c - C 中的后台进程(守护进程)不是 execvp() -ing

c - 如何将 "make room"表示为字符串?

c - 将数组添加到节点 n 叉树时出现段错误

java - 非交互式 bash shell 的环境变量

c++ - 查找数组中的最小元素及其索引