我可以看到您的代码中存在一些问题。从我的角度来看,主要的一点是它不是并行的。或者更准确地说,您在编译 OpenMP 时没有启用通过 OpenMP 引入的并行性。人们可以通过以下方式看到这一点:
代码并行化的方式,主要for
循环应该由所有线程完整执行(这里没有工作共享,没有#pragma omp parallel for
,只有一个#pragma omp parallel
)。因此,考虑到您将线程数设置为 4,则全局迭代次数应为4*N
。因此,你的输出应该慢慢向 4*Pi 收敛,而不是向 Pi 收敛。
事实上,我在我的笔记本电脑上尝试了您的代码,并使用 OpenMP 支持对其进行了编译,这几乎就是我得到的结果。但是,当我不启用 OpenMP 时,我会得到与您类似的输出。所以总而言之,你需要:
- 在编译时启用 OpenMP 以获得代码的并行版本。
- 将你的结果除以
NumThreads
获得 Pi 的“有效”近似值(或将循环分布在N
with a #pragma omp for
例如)
但这是指您的代码在其他地方是否正确,但目前还不是。
正如 BitTickler 已经暗示的那样,rand()
不是线程安全的。所以你必须寻找另一个随机数生成器,这将允许你将其状态私有化。那可能是rand_r()
例如。也就是说,这仍然存在很多问题:
-
rand()
/ rand_r()
is a terribleRNG 的随机性和周期性。在增加尝试次数的同时,您将快速经历 RNG 的周期并一遍又一遍地重复相同的序列。你需要更强大的东西来完成任何严肃的事情。
- 即使使用“好的”RNG,并行性方面也可能是一个问题,因为您希望并行的序列彼此之间不相关。仅在每个线程使用不同的种子值并不能保证这一点(尽管使用足够宽的 RNG,您有一些空间)
无论如何,底线是:
- 使用更好的线程安全 RNG(我发现
drand48_r()
or 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