我正在学习 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 i
和 float argg
。
发生的事情是你所有的 200 个线程都在覆盖 i
和 argg
,所以有时线程的 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/