5

我正在使用蒙特卡罗方法来计算 pi 并使用并行编程和 openmp 进行基本体验

问题是当我使用 1 个线程,x 次迭代时,总是比 n 个线程,x 次迭代运行得快。谁能告诉我为什么?

例如,代码运行如下“a.out 1 1000000”,其中 1 是线程,1000000 是迭代

include <omp.h>
include <stdio.h>
include <stdlib.h>
include <iostream>
include <iomanip>
include <math.h>

using namespace std;

int main (int argc, char *argv[]) {

double arrow_area_circle, pi;
float xp, yp;
int i, n;
double pitg= atan(1.0)*4.0; //for pi error
cout << "Number processors: " << omp_get_num_procs() << endl;

//Number of divisions
iterarions=atoi(argv[2]); 
arrow_area_circle = 0.0;

#pragma omp parallel num_threads(atoi(argv[1]))
{
srandom(omp_get_thread_num());

#pragma omp for private(xp, yp) reduction(+:arrow_area_circle) //*,/,-,+
for (i = 0; i < iterarions; i++) {
    xp=rand()/(float)RAND_MAX;
    yp=rand()/(float)RAND_MAX;

    if(pow(xp,2.0)+pow(yp,2.0)<=1) arrow_area_circle++;
}
}

pi = 4*arrow_area_circle / iterarions;
cout << setprecision(18) << "PI = " << pi << endl << endl;
cout << setprecision(18) << "Erro = " << pitg-pi << endl << endl;

return 0;
}
4

5 回答 5

10

如果您在比系统中的 CPU 更多的线程中执行工作,那么像这样的 CPU 密集型任务将会更慢。如果您在单 CPU 系统上运行它,您肯定会看到多个线程的速度下降。这是由于操作系统必须在各个线程之间切换——这纯粹是开销。理想情况下,对于这样的任务,您应该拥有与核心相同数量的线程。

另一个问题是 arrow_area_circle 在线程之间共享。如果您在每个内核上运行一个线程,递增 arrow_area_circle 将使其他内核缓存中的副本无效,导致它们必须重新获取。应该需要几个周期的arrow_area_circle++ 将需要几十或几百个周期。尝试为每个线程创建一个 arrow_area_circle 并在最后组合它们。

编辑:Joe Duffy 刚刚发布了一篇关于线程间共享数据成本的博客文章

于 2009-10-20T01:20:20.003 回答
7

看起来您正在使用某种自动并行化编译器。我将假设您的系统中有超过 1 个内核/CPU(因为这太明显了——而且 Pentium 4 上没有超线程不算有两个内核,不管英特尔的营销会让你相信什么.) 我看到了两个问题。第一个是微不足道的,可能不是你的问题:

  1. 如果变量 arrow_area_circle 在您的进程之间共享,则执行 arrow_area_circle++ 的行为将导致使用联锁指令以原子合理的方式同步值。您应该增加一个“私有”变量,然后在最后将该值添加到公共 arrow_area_circle 变量中,而不是在内部循环中增加 arrow_area_circle。

  2. rand() 函数要正常运行,必须在内部使用临界区执行。原因是它的内部状态/种子是静态共享变量;如果不是这样,两个不同的进程就有可能以异常高的概率从 rand() 获得相同的输出,因为它们几乎同时调用 rand()。这意味着 rand() 运行缓慢,尤其是当更多线程/进程同时调用它时。与 arrow_area_circle 变量(只需要原子增量)不同,真正的临界区必须由 rand() 调用,因为它的状态更新更复杂。要解决此问题,您应该获取自己的随机数生成器的源代码,并将其与私有的种子或状态。大多数编译器中的标准 rand() 实现的源代码是广泛可用的。

我还想指出,您正在使用 pow(,) 函数来执行与 x * x 相同的事情。后者比前者快约 300 倍。尽管这一点与您提出的问题无关。:)

于 2009-10-20T01:40:37.387 回答
2

rand() 是阻塞函数。这意味着它内部有临界区。

于 2009-11-16T21:01:07.790 回答
2

上下文切换。

于 2009-10-20T01:19:32.777 回答
1

只是要强调在并行设置中使用随机数时必须非常小心。事实上,你应该使用类似SPRNG 的东西

无论您做什么,请确保每个线程没有使用相同的随机数。

于 2009-11-05T14:20:24.227 回答