使用非线程安全随机数生成器在C中对pi monte carlo更正OpenMP pragma

时间:2013-12-08 10:38:36

标签: thread-safety montecarlo prng openmp

我需要一些帮助来将pi计算与monmp carlo方法并行化,使用给定的随机数生成器进行openmp,这不是线程安全的。

首先:This SO线程没有帮助我。

我自己尝试的是以下#pragma omp语句。我认为i,x和y变量应该由每个线程初始化,而不应该是私有的。 z是圆圈中所有命中的总和,因此它应该在for循环后隐含的barriere之后求和。

认为主要问题是随机数发生器的静态状态变量。我创建了一个调用函数的关键部分,因此每次只有一个线程可以执行它。但Pi解决方案不能随着更高的值而扩展。

注意:我不应该使用其他RNG,但可以对其进行一些修改。

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

    int i, z = 0, threads = 8, iters = 100000;
    double x,y, pi;

    #pragma omp parallel firstprivate(i,x,y) reduction(+:z) num_threads(threads)
        for (i=0; i<iters; ++i) {
            #pragma omp critical
            {
                x = rng_doub(1.0);
                y = rng_doub(1.0);
            }
            if ((x*x+y*y) <= 1.0)
                z++;
        }

    pi = ((double) z / (double) (iters*threads))*4.0;
    printf("Pi: %lf\n", pi);;
    return 0;
}

这个RNG实际上是一个包含文件,但由于我不确定我是否正确创建了头文件,我将其集成到另一个程序文件中,所以我只有一个.c文件。

#define RNG_MOD 741025

int rng_int(void) {
    static int state = 0;

    return (state = (1366 * state + 150889) % RNG_MOD);
}

double rng_doub(double range) {
    return ((double) rng_int()) / (double) ((RNG_MOD - 1)/range);
}

我也尝试将静态int状态设为全局,但它不会改变我的结果,也许我做错了。那么请你能帮我做出正确的改变吗?非常感谢你!

2 个答案:

答案 0 :(得分:8)

原始线性全等PRNG的周期长度为49400,因此您只获得29700个独特测试点。这是一个可怕的发生器,可用于任何类型的蒙特卡罗模拟。即使您进行了100000000次试验,也不会接近Pi的真实值,因为您只是一遍又一遍地重复相同的点,因此z的最终值和{{1简单地乘以相同的常数,在分割期间最终取消。

Z boson引入的每线程种子通过随着OpenMP线程总数增加的唯一点数增加了一点点情况。增加不是线性的,因为如果一个PRNG的种子落入另一个PRNG的序列中,则两个PRNG产生相同的序列,其移位不超过49400个元素。给定周期长度,每个PRNG覆盖49400 / RNG_MOD =总输出范围的6.7%,这是两个PRNG同步的概率。总共有RNG_MOD / 49400 = 15个唯一序列。它基本上意味着在最好的播种情况下你将无法获得超过30个线程,因为任何其他线程只会重复其他一些线程的结果。乘数2来自这样一个事实,即每个点使用序列中的两个元素,因此如果将序列移动一个元素,则可以获得不同的点集。

最终解决方案是完全放弃你的PRNG并坚持Mersenne twister MT19937之类的东西,其周期长度为2 19937 - 1,并且是一种非常强大的播种算法。如果您在问题中未能使用其他PRNG,请至少修改LCG的常量以匹配iters中使用的常量:

rand()

请注意int rng_int(void) { static int state = 1; // & 0x7fffffff is equivalent to modulo with RNG_MOD = 2^31 return (state = (state * 1103515245 + 12345) & 0x7fffffff); } 不是一个好的PRNG - 它仍然很糟糕。它比你的代码中使用的好一点。

答案 1 :(得分:0)

尝试以下代码。它为每个线程创建一个私有状态。我使用at rand_r函数Why does calculation with OpenMP take 100x more time than with a single thread?

做了类似的事情

编辑:我使用Hristo的一些建议更新了我的代码。我使用了threadprivate(第一次)。我还使用了更好的rand函数,可以更好地估计pi,但它还不够好。

一个奇怪的事情是我必须在threadprivate之后定义函数rng_int否则我得到一个错误“错误:'状态'在第一次使用后声明'threadprivate'”。我应该问一个关于这个的问题。

//gcc -O3 -Wall -pedantic -fopenmp main.c
#include <omp.h>
#include <stdio.h>

#define RNG_MOD 0x80000000
int state;

int rng_int(void);
double rng_doub(double range);

int main() {
    int i, numIn, n;
    double x, y, pi;

    n = 1<<30;
    numIn = 0;

    #pragma omp threadprivate(state)
    #pragma omp parallel private(x, y) reduction(+:numIn) 
    {

        state = 25234 + 17 * omp_get_thread_num();
        #pragma omp for
        for (i = 0; i <= n; i++) {
            x = (double)rng_doub(1.0);
            y = (double)rng_doub(1.0);
            if (x*x + y*y <= 1) numIn++;
        }
    }
    pi = 4.*numIn / n;
    printf("asdf pi %f\n", pi);
    return 0;
}

int rng_int(void) {
   // & 0x7fffffff is equivalent to modulo with RNG_MOD = 2^31
   return (state = (state * 1103515245 + 12345) & 0x7fffffff);
}

double rng_doub(double range) {
    return ((double)rng_int()) / (((double)RNG_MOD)/range);
}

您可以在http://coliru.stacked-crooked.com/a/23c1753a1b7d1b0d

查看结果(并编辑并运行代码)