蒙特卡罗pi近似的并行化

时间:2016-10-27 05:39:10

标签: c multithreading openmp montecarlo

我正在编写一个c脚本来与OpenMp并行化pi近似。我认为我的代码在令人信服的输出下运行良好。我现在用4个线程运行它。我不确定的是,如果此代码容易受到竞争条件的影响?如果是,如何协调此代码中的线程操作?

代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <math.h>
#include <omp.h>

double sample_interval(double a, double b) {

  double x = ((double) rand())/((double) RAND_MAX);
  return (b-a)*x + a;

}

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


  int N = atoi( argv[1] ); // convert command-line input to N = number of points
  int i;
  int NumThreads = 4;
  const double pi = 3.141592653589793;
  double x, y, z;
  double counter = 0;



  #pragma omp parallel firstprivate(x, y, z, i) reduction(+:counter) num_threads(NumThreads)
 {
  srand(time(NULL));
  for (int i=0; i < N; ++i) 
  {
    x = sample_interval(-1.,1.);
    y = sample_interval(-1.,1.);
    z = ((x*x)+(y*y));

    if (z<= 1) 
   {
      counter++;
    }
  }

 } 
  double approx_pi = 4.0 * counter/ (double)N;

  printf("%i %1.6e %1.6e\n ", N, 4.0 * counter/ (double)N, fabs(4.0 * counter/ (double)N - pi) / pi);


  return 0;

}

此外,我想知道随机数的种子是否应该在并行化内部或外部声明。我的输出看起来像这样:

10 3.600000e+00 1.459156e-01
100 3.160000e+00 5.859240e-03
1000 3.108000e+00 1.069287e-02
10000 3.142400e+00 2.569863e-04
100000 3.144120e+00 8.044793e-04
1000000 3.142628e+00 3.295610e-04
10000000 3.141379e+00 6.794439e-05
100000000 3.141467e+00 3.994585e-05
1000000000 3.141686e+00 2.971945e-05

现在看起来不错。你对种族状况和种子安置的建议是最受欢迎的。

1 个答案:

答案 0 :(得分:4)

我可以看到您的代码中存在一些问题。从我的观点来看,主要的是它没有并行化。或者更确切地说,在编译时,您没有启用OpenMP引入的并行性。以下是人们可以看到的方式:

代码并行化的方式,主for循环应该由所有线程完全执行(这里没有工作共享,没有#pragma omp parallel for,只有#pragma omp parallel)。因此,考虑到将线程数设置为4,全局迭代次数应为4*N。因此,您的输出应该慢慢收敛到4 * Pi,而不是Pi。

确实,我在我的笔记本电脑上尝试了你的代码,用OpenMP支持编译它,这就是我得到的。但是,当我不启用OpenMP时,我得到的输出类似于你的。总而言之,您需要:

  1. 在编译时启用OpenMP以获取代码的并行版本。
  2. 将结果除以NumThreads以得到Pi的“有效”近似值(或者将N上的循环分布为#pragma omp for
  3. 但是,如果你的代码在其他地方是正确的,那么它还没有。 正如BitTickler已经暗示的那样,rand()不是线程安全的。所以你必须使用另一个随机数生成器,这将允许你私有化它的状态。例如,这可能是rand_r()。也就是说,这仍有很多问题:

    1. rand() / rand_r()在随机性和周期性方面是可怕的 RNG。在增加尝试次数的同时,您将快速浏览RNG的周期并重复相同的序列。你需要更强大的东西来做任何远程严肃的事情。
    2. 即使使用“良好”的RNG,并行性方面也可能是一个问题,因为您希望并行序列在彼此之间不相关。并且每个线程使用不同的种子值并不能保证(尽管有足够宽的RNG,你有一点空间)
    3. 无论如何,底线是:

      • 使用更好的线程安全RNG(我发现drand48_r()random_r()对于Linux上的玩具代码没问题)
      • 例如,基于线程id初始化其每个线程的状态,同时请记住,在某些情况下,这将无法确保随机序列的正确去相关(并且调用函数的次数越多,你越有可能最终重叠系列。)

      完成此操作(以及一些小修复),您的代码将变为如下:

      #include <stdlib.h>
      #include <stdio.h>
      #include <time.h>
      #include <math.h>
      #include <omp.h>
      
      typedef struct drand48_data RNGstate;
      
      double sample_interval(double a, double b, RNGstate *state) {
          double x;
          drand48_r(state, &x);
          return (b-a)*x + a;
      }
      
      int main (int argc, char **argv) {
      
          int N = atoi( argv[1] ); // convert command-line input to N = number of points
          int NumThreads = 4;
          const double pi = 3.141592653589793;
          double x, y, z;
          double counter = 0;
          time_t ctime = time(NULL);
      
          #pragma omp parallel private(x, y, z) reduction(+:counter) num_threads(NumThreads)
          {
              RNGstate state;
              srand48_r(ctime+omp_get_thread_num(), &state);
              for (int i=0; i < N; ++i) {
                  x = sample_interval(-1, 1, &state);
                  y = sample_interval(-1, 1, &state);
                  z = ((x*x)+(y*y));
      
                  if (z<= 1) {
                      counter++;
                  }
              }
      
          } 
          double approx_pi = 4.0 * counter / (NumThreads * N);
      
          printf("%i %1.6e %1.6e\n ", N, approx_pi, fabs(approx_pi - pi) / pi);
      
          return 0;
      }
      

      我编译的是这样的:

      gcc -std=gnu99 -fopenmp -O3 -Wall pi.c -o pi_omp