OpenMP Monte Carlo \(\pi\)
Let’s parallelize our Monte Carlo example of computing pi.
Important
We want each thread to have independent random numbers. We do this by creating a random number generator separately on each thread with different seeds.
There is a worry still—if you are sampling a large fraction of the period of the pseudo-random number generator, than it is possible that the sequences will overlap. This is discussed in the SC08 presentation by Tim Mattson & Larry Meadows (look around slide 130).
Tip
The Merseene twister has a period of \(2^{19937}-1\), so it is unlikely for overlap to be an issue.
Note
True parallel random number generators exist, where a single thread controls the generator and gives the numbers to each thread as needed.
See also: Improved Long-Period Generators Based on Linear Recurrences Modulo 2 by Panneton and L’Ecuyer.
Then this calculation can proceed in a trivially parallel fashion. The only thing we need to do is mark the sum for a reduction at the end of the parallel region.
#include <iostream>
#include <iomanip>
#include <random>
#include <cmath>
#ifdef _OPENMP
#include <omp.h>
#endif
const int N = 1000000;
int main() {
#ifdef _OPENMP
auto start = omp_get_wtime();
#endif
int N_circle{0};
#pragma omp parallel reduction(+:N_circle)
{
// each thread will have its own random number
// engine
std::random_device rd;
#ifdef _OPENMP
std::seed_seq seed{static_cast<int>(rd()), omp_get_thread_num()};
#else
std::seed_seq seed{static_cast<int>(rd())};
#endif
std::mt19937 generator(seed);
std::uniform_real_distribution<double> uniform(0.0, 1.0);
#pragma omp for
for (int n = 0; n < N; ++n) {
double x = uniform(generator);
double y = uniform(generator);
double r2 = std::pow(x - 0.5, 2) + std::pow(y - 0.5, 2);
if (r2 <= 0.25) {
N_circle++;
}
}
}
std::cout << "pi = " << std::setprecision(10)
<< 4 * static_cast<double>(N_circle) / N << std::endl;
#ifdef _OPENMP
std::cout << "time = " << omp_get_wtime() - start << std::endl;
#endif
}
Tip
There is one further trick here—all of the OpenMP stuff is
wrapped in #ifdef _OPENMP
—the _OPENMP
preprocessor
variable is defined by the standard if we are building with OpenMP.
By wrapping those features, this allows the code to be compiled
without OpenMP if we desire.
Tip
Other types of random number generators exist, beyond those in the C++ SL. Here’s an example of a header-only library that works on CPUs and GPUs: random123.