Tutorial for DPRNG
OpenCilk 2.0 is a programming framework that supports both pedigrees and deterministic pseudorandom number generation (DPRNG). To enable these features in a Cilk program, simply link the program with the -lopencilk-pedigrees
library. No recompiling is necessary.
There are three options for using DPRNG with OpenCilk 2.0:
- Use the OpenCilk runtime's built-in DPRNG. To generate a pseudorandom number, call the
__cilkrts_get_dprand()
function. This function returns a 64-bit unsigned integer, although the return value will be one of 2^64-59 possible results. The pseudorandom number generator can be reseeded at the beginning of the program by calling__cilkrts_dprand_set_seed(seed)
, whereseed
is a 64-bit unsigned integer. The__cilkrts_get_dprand()
function is easy to use and fast, but it offers only limited control over the DPRNG. - Use an existing DPRNG library that uses pedigrees. The only known such library is the DotMix implementation from CilkPub. CilkPub is implemented entirely in C++, so it may not be useful for programs written in other languages.
- Write your own DPRNG library that uses pedigrees. The library can read the pedigree of the current strand by calling
__cilkrts_get_pedigree()
, which returns the pedigree as a singly-linked list. This option allows for flexible implementation of a new DPRNG in C or C++, but it may be more complicated than necessary.
Here is a usage example for implementing DPRNG with OpenCilk 2.0 using the built-in pseudorandom number generator:
#include <stdio.h>
#include <stdint.h>
#include <cilk/cilk.h>
#include <cilk/cilk_api.h>
#include <math.h>
// Generate a pseudorandom number using the built-in DPRNG in OpenCilk 2.0
uint64_t generate_random_number() {
return __cilkrts_get_dprand();
}
// Reseed the built-in DPRNG in OpenCilk 2.0
void reseed_dprng(uint64_t seed) {
__cilkrts_dprand_set_seed(seed);
}
// Identity function for the reducer
void zero(void *v) {
*(int *)v = 0;
}
// Reduce function for the reducer
void plus(void *l, void *r) {
*(int *)l += *(int *)r;
}
int main() {
// Reseed the DPRNG with a value of 12345
reseed_dprng(12345);
// Set the number of iterations to use for the Monte Carlo simulation
int num_iterations = 1000000;
// Set the radius of the circle
double radius = 1.0;
// Create reducer objects for the counters for the number of points inside and outside the circle
int cilk_reducer(zero, plus) inside_circle = 0;
int cilk_reducer(zero, plus) outside_circle = 0;
// Run the Monte Carlo simulation in parallel
cilk_for(int i = 0; i < num_iterations; i++) {
// Generate two pseudorandom numbers for the x and y coordinates
double x = (double)generate_random_number() / UINT64_MAX;
double y = (double)generate_random_number() / UINT64_MAX;
// Compute the distance of the point from the origin
double dist = sqrt(x * x + y * y);
// Increment the counter for points inside or outside the circle based on the distance
if (dist <= radius) {
inside_circle++;
} else {
outside_circle++;
}
}
// Compute the ratio of points inside the circle to
// the total number of points
double ratio = (double)inside_circle / (double)(inside_circle + outside_circle);
// Use this ratio to compute an estimate of pi
double pi_estimate = 4.0 * ratio;
// Print the result
printf("Estimate of pi: %.10f\n", pi_estimate);
return 0;
}
The example above runs a Monte Carlo simulation to generate points in a two-dimensional plane. The points are generated using the generate_random_number()
function, which uses the __cilkrts_get_dprand()
function to generate pseudorandom numbers. The coordinates of the points are computed by scaling the pseudorandom numbers to the range
This example uses OpenCilk 2.0's reducer syntax to avoid race conditions. The inside_circle
and outside_circle
counters are declared as reducer objects using the cilk_reducer
syntax, and the zero
and plus
functions are specified as the identity and reduce functions, respectively. This ensures that the counters are updated atomically and that their values are consistent across all strands.