我正在学习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个除法)
我也试图增加一个障碍,但我仍然得到不同的答案,虽然更接近串行版本,仍然有一个利差值和不相同的.
发布于 2019-04-09 09:33:35
我认为问题在于在并行循环之外声明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显示,它实际上不是完全相同的浮点数。这是由于浮点加法的数值误差造成的。a+b+c不能保证结果与a+c+b相同。可以在下面的示例中尝试这样做:
首先,在定义了并行循环( float* arr = new float[nb];
)之后,在(并行循环)和arr[i] = argg;
(并行循环)之前添加arr[i] = 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个线程,而专用服务器处理器可以使用$15k Xeon白金9282来处理112个线程。
因此,我们通常做以下工作:
我们删除omp_set_num_threads(nb);
以使用推荐的线程数
我们从for循环中删除int i = omp_get_thread_num();
以使用int i
。
我们将循环重写为for循环:
#pragma omp parallel for
for (int i = 0; i < nb; i++)
{...}
结果应该是相同的,但是现在只使用实际硬件上可用的线程。这样可以减少线程间的上下文切换,并提高代码的时间性能。
发布于 2019-04-09 09:46:33
问题来自变量summ
、argg
和i
。它们属于全局顺序范围,如果不采取预防措施,就不能修改。您将在线程之间进行竞争,这可能会导致这些var中出现一个意外的值。种族是完全不确定的,这就解释了你得到的不同结果。您还可以获得正确的结果或任何不正确的结果,这取决于读取和写入这些vars的时间。
处理这个问题的正确方法是:
argg
和i
:它们在全局范围内声明,但它们用于在线程中执行临时计算。您应该:要么在并行域中声明它们,使它们成为线程私有,要么在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);
}
如前所述,这不是使用线程的最佳方式。创建和同步线程是昂贵的,很少需要有更多的线程数量的核心。
https://stackoverflow.com/questions/55596868
复制