2016-10-27 37 views
1

我正在写一个c脚本来平行pi近似与OpenMp。我认为我的代码工作正常,有一个令人信服的输出。我现在用4个线程运行它。我不确定的是,如果这段代码容易受到竞争状况的影响?如果是这样,我如何协调此代码中的线程操作?蒙特卡罗pi逼近的并行化

的代码如下:

#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 

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

+2

什么的'文档'rand()''和''srand()''说?如果他们说,数字发生器的状态保存在线程本地存储中,至少这不会是一个问题。但他们是这么说的吗?这里:http://www.cplusplus.com/reference/cstdlib/rand/他们说那些比赛可能会发生。 – BitTickler

回答

3

您可以看到代码中存在一些问题。主要的是从我的观点来看,它并不是并行的。或者更确切地说,在编译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例如分发您的循环)

但是,这是如果/当你的代码是正确的其他地方,它还没有。由于BitTickler已经暗示,rand()不是线程安全的。所以你必须去另一个随机数发生器,这将允许你私有化它的状态。例如,这可能是rand_r()。尽管如此,这仍然有不少问题:

  1. rand()/rand_r()是随机性和周期性的任期可怕 RNG。在增加尝试次数的同时,您将快速超过RNG的周期,并一遍又一遍地重复相同的顺序。你需要更强大的东西来远程执行任何严肃的事情。
  2. 即使使用“良好”的RNG,并行性方面也可能是一个问题,因为您希望并行的序列在彼此之间不相关。而只是用每个线程不同的种子值并不保证该给你(虽然宽足以RNG,你有一点余量为的)

无论如何,底线是:

  • 使用更好的线程安全的RNG(我发现drand48_r()random_r()是在Linux上的玩具代码OK)
  • 初始化其状态每个线程例如基于线程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 
+0

非常感谢,现在这一切都有意义 – bhjghjh