我正在阅读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个除法)
我也尝试添加一个障碍,但是我仍然得到不同的答案,尽管更接近于串行版本,但其值仍然分散且不相同...
答案 0 :(得分:2)
我认为问题在于在并行循环之外声明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;
,然后在并行循环之前添加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)
问题来自变量summ
,argg
和i
。它们属于全局顺序范围,如果没有预防措施,则不能对其进行修改。您将在线程之间发生争用,这可能导致这些var中出现意外值。种族是完全不确定的,这可以解释您获得的不同结果。根据对这些var的读写时间变化,您也可能会获得正确的结果或任何不正确的结果。
处理此问题的正确方法:
用于变量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);
}
如前所述,这不是使用线程的最佳方法。创建和同步线程的成本很高,很少需要拥有比核心数量更多的线程。