并行块中允许哪些功能和操作?

时间:2012-11-19 16:53:27

标签: c parallel-processing openmp operations

代码:

double x(){return (double)rand()/(double)RAND_MAX;}
double y(){return (double)rand()/(double)RAND_MAX;}
double z(){return (double)rand()/(double)RAND_MAX;}

int d(double x, double y, double z){
        if ( ( (pow(x,2)+pow(y,2)) <1 ) && ( z<=1 && z>=0 )) return 1;
        return 0;
    }

double f(double x, double y, double z){
        return 1;
    }




#pragma omp parallel default(none) private(id,numt,j,local_sum,local_good_dots,local_coi,x_,y_,z_) shared(total_sum,good_dots,count_of_iterations)
    {
        local_coi = count_of_iterations;
        id = omp_get_thread_num() + 1;
        numt = omp_get_num_threads();
        #pragma omp for
        for (j = 1; j <= local_coi;  j++){
            x_=x();
            y_=y();
            z_=z();
            if (d(x_,y_,z_) == 1){
                local_sum += f(x_,y_,z_);
                local_good_dots += 1;

            }
        }

        #pragma omp critical
        {
            total_sum = total_sum + local_sum;
            good_dots = good_dots + local_good_dots;
        }
    }

评论:此代码是用于计算区域f()中函数d()的三维积分的蒙特卡罗方法的实现。

我希望,这个代码在多线程模式(openmp)中运行得更快。

但出了点问题。

经过几个小时的修改(reduction在openmp pragma中,if条件的简化(如f(x_,y_,z_) * d(x_,y_,z_)))我不明白,为什么这个简单的循环在更大的线程数上变得更慢。

但是在我为循环之前的每个坐标生成一个三维数组并将其放在shared之后,我的程序变得更快。

所以,问:

如何修改此代码以及并行块中允许哪些函数(操作)?

P.S:如我所见,rand函数不被允许(或者我错了?)

感谢您的帮助!

修改(@ HristoIliev的帮助)

double x(){return (double)rand()/(double)RAND_MAX;}
double y(){return (double)rand()/(double)RAND_MAX;}
double z(){return (double)rand()/(double)RAND_MAX;}

int d(double x, double y, double z){
        if ( ( (pow(x,2)+pow(y,2)) <1 ) && ( z<=1 && z>=0 )) return 1;
        return 0;
    }

double f(double x, double y, double z){
        return 1;
    }


#pragma omp parallel default(none) private(j,local_coi,x_,y_,z_) shared(count_of_iterations) reduction(+:total_sum,good_dots)
    {
        local_coi = count_of_iterations;
        #pragma omp for(prng)
        for (j = 1; j <= local_coi;  j++){                    
        #pragma omp critical(prng)
        {
                x_=x();
                y_=y();
                z_=z();
        }   
            if (d(x_,y_,z_) == 1){
                total_sum += f(x_,y_,z_);
                good_dots += 1;

            }
        }
    }

1 个答案:

答案 0 :(得分:2)

随机数生成器rand()使用全局静态分配状态,由所有线程共享,因此不是线程安全的。从多个线程中使用它会遇到一个非常糟糕的情况,即对共享变量进行不受保护的访问,这会破坏缓存并降低程序速度。您应该使用rand_r()erand48() - 它们使用您必须提供的单独的状态存储。你必须为每个线程声明一个状态(例如让它private),基本上为每个线程创建不同的PRNG。然后你必须相应地播种它们,否则你会得到统计上不好的结果。原则上,您可以使用一个rand48()生成器的输出来播种其他生成器 - 它应该足以获得中等长度的不相关序列。

以下是使用rand_r()的示例实现(不是这是用于蒙特卡罗模拟的非常糟糕的生成器,erand48更好,最好的是使用GNU科学图书馆的“Mersenne Twister”型发生器(如果有的话):

unsigned int prng_state;
#pragma omp threadprivate(prng_state)

double x(){return (double)rand_r(&prng_state)/(double)RAND_MAX;}
double y(){return (double)rand_r(&prng_state)/(double)RAND_MAX;}
double z(){return (double)rand_r(&prng_state)/(double)RAND_MAX;}

int d(double x, double y, double z){
    if ( ( (pow(x,2)+pow(y,2)) <1 ) && ( z<=1 && z>=0 )) return 1;
    return 0;
}

double f(double x, double y, double z){
    return 1;
}

...

#pragma omp parallel default(none) \
            private(id,numt,x_,y_,z_) \
            shared(count_of_iterations) \
            reduction(+:total_sum,good_dots)
{
    id = omp_get_thread_num() + 1;
    numt = omp_get_num_threads();

    // Sample PRNG seeding code - DO NOT USE IN PRODUCTION CODE!
    prng_state = 67894 + 1337*id;

    #pragma omp for
    for (j = 1; j <= count_of_iterations;  j++){
        x_=x();
        y_=y();
        z_=z();
        if (d(x_,y_,z_) == 1){
            total_sum += f(x_,y_,z_);
            good_dots += 1;
        }
    }
}

这只是一个非常糟糕的(从质量的角度来看)实现,但它应该让你了解事情是如何工作的。它还可以通过对原始代码进行最少的更改来实现线程安全。基本点是:

  • 通过OpenMP prng_state指令使PRNG状态threadprivate对每个线程都是私有的;
  • {li> rand_r()使用特定于线程的状态变量而不是rand()x()y()中的z();
  • PRNG状态以线程相关的方式初始化,例如, prng_state = 67894 + 1337*id;,以便不同的线程(希望)获得不相关的伪随机数流。

请注意,rand()rand_r()的质量非常差,这只是一个学术范例。使用更长的PRNG序列,您将获得不同线程中的相关流,这将破坏统计数据。我建议您使用erand48()重写代码。

要回答您的初始问题 - parallel块内允许所有线程安全函数调用。您也可以调用非线程安全函数,但必须保护(命名)critical构造内部的调用,例如:

#pragma omp for
for (j = 1; j <= local_coi; j++) {
    #pragma omp critical(prng)
    {
        x_=x();
        y_=y();
        z_=z();
    }
    if (d(x_,y_,z_) == 1) {
        local_sum += f(x_,y_,z_);
        local_good_dots += 1;
    }
}

这将确保不会并行调用rand()。但是你仍然可以对共享状态进行读 - 修改 - 写入访问,因此与缓存相关的减速。

此外,尝试重新实现OpenMP reduction或类似的结构。编译器供应商已经在努力确保以最佳(尽可能快的方式)实现它们。