41. Parallel Monte Carlo π

This example solution uses the same approach used by the preceding solution. It uses form-level variables to pass information to the parallel instances of a method. Those instances perform calculations and then use locking to save their results to the form-level variables.

The following code shows the form-level variables:

private int NumHits = 0;
private object LockObject = new object();
private int MonteCarloWidth = 0, MonteCarloHeight = 0;
private Bitmap MonteCarloBitmap = null;
private const int PointsPerCall = 10000;

The following method starts the parallel execution:

// Use Monte Carlo simulation to estimate pi.
private double MonteCarloPi(int numPoints)
{
// Make a bitmap to show points.
MonteCarloWidth = pointsPictureBox.ClientSize.Width;
MonteCarloHeight = pointsPictureBox.ClientSize.Height;
MonteCarloBitmap = new Bitmap(MonteCarloWidth, MonteCarloWidth);
using (Graphics gr = Graphics.FromImage(MonteCarloBitmap))
{
gr.Clear(Color.White);
gr.DrawEllipse(Pens.Black, 0, 0,
MonteCarloWidth - 1, MonteCarloWidth - 1);
}

// Make the random points.
NumHits = 0;
int numMethods = numPoints / PointsPerCall;
Parallel.For(0, numMethods, TestPoint);

// Display the plotted points.
pointsPictureBox.Image = MonteCarloBitmap;

// Get the hit fraction.
double fraction = NumHits / (double)(numMethods * PointsPerCall);

// Estimate pi.
return 4.0 * fraction;
}

This method creates a bitmap to show some of the test points and saves it in a form-level variable. It then uses Parallel.For to launch instances of the TestPoint method. After those parallel methods finish, the code calculates the fraction of test points that fell within the circle of radius 1, uses that to calculate an estimate for π, and returns the result.

The following code shows the TestPoint method:

private void TestPoint(int i)
{
Random rand = new Random(i * DateTime.Now.Millisecond);
int myHits = 0;
for (int pointNum = 0; pointNum < PointsPerCall; pointNum++)
{
// Make a random point 0 <= x < 1.
double x = rand.NextDouble();
double y = rand.NextDouble();

// See how far the point is from (0.5, 0.5).
double dx = x - 0.5;
double dy = y - 0.5;
if (dx * dx + dy * dy < 0.25) myHits++;

if (i == 0)
{
int ix = (int)(MonteCarloWidth * x);
int iy = (int)(MonteCarloHeight * y);
if (dx * dx + dy * dy < 0.25)
MonteCarloBitmap.SetPixel(ix, iy, Color.Gray);
else
MonteCarloBitmap.SetPixel(ix, iy, Color.Black);
}
}

// Slightly slower.
//Interlocked.Add(ref NumHits, myHits);

// Slightly faster.
lock (LockObject)
{
NumHits += myHits;
}
}

This method has a problem that didn't occur in earlier examples. The Random class does not work well when it runs on multiple threads simultaneously. This means the instances of the method cannot share a form-level Random object. Instead, each instance must create its own Random object.

However, the Random class's default constructor uses the system's time to initialize new objects. Because the parallel instances of the method execute very quickly, many of them may use the same system time to initialize their Random objects. When that happens, those instances of the method will generate the same random values, so they will use the same test points. Instead of executing using many different points, the methods use the same points several times.

For example, suppose the program runs 10 instances of the method with 10,000 points each. Instead of using 100,000 points to estimate π, the program basically uses 10,000 points to estimate π 10 times. You still get an estimate, but without the precision that you would get with 100,000 different points.

To solve this dilemma, the program needs to initialize each instance's Random object differently. The example solution does that by multiplying the current time's number of milliseconds by the method instance number and passing that to the Random constructor as a seed value.

Having created a suitable Random object, the method performs its trials and counts the number of generated points that lie within the target circle. Notice that the code stores that count in a local variable. After it has finished counting hits, the method updates the form-level variable NumHits.

The code shows two ways to update NumHits. The first method, which is commented out, uses the System.Threading.Interlocked class's Add method to safely add the myHits value to NumHits. The second method, which seems to be slightly faster, explicitly locks the lock object and updates NumHits.

The following screenshot shows the iterative and parallel versions of this program after using 100 million test points to estimate π. The two programs provide roughly the same accuracy, but the parallel version only takes about 27% as long as the iterative version:

Download the ParallelMonteCarloPi example solution to see additional details.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.166.255