Skip to main content

A C# console application that estimates the value of PI using a variety of both serial and parallel implementations, the latter done with both PLINQ and the Parallel class.

class Program
{
    static int Main(string[] args)
    {
        int returnCode = 0;
        RunCalculatePi();
        return returnCode;
    }

    /// <summary>
    /// Main method to time various implementations of computing PI.
    /// </summary>
    public static void RunCalculatePi()
    {
        ComputePi.Time(() => ComputePi.SerialLinqPi());
        ComputePi.Time(() => ComputePi.ParallelLinqPi());
        ComputePi.Time(() => ComputePi.SerialPi());
        ComputePi.Time(() => ComputePi.ParallelPi());
        ComputePi.Time(() => ComputePi.ParallelPartitionerPi());

        System.Console.WriteLine("----");
        System.Console.ReadLine();
    }
}

public static class ComputePi
{
    const int num_steps = 100000000;

    /// <summary>
    /// Times the execution of a function and outputs both
    /// the elapsed time and the function's result.
    /// </summary>
    public static void Time<T>(Func<T> work)
    {
        var sw = Stopwatch.StartNew();
        var result = work();
        System.Console.WriteLine(sw.Elapsed + ": " + result);
    }

    /// <summary>
    /// Estimates the value of PI using a LINQ-based
    /// implementation.
    /// </summary>
    public static double SerialLinqPi()
    {
        double step = 1.0 / (double)num_steps;

        return (from i in Enumerable.Range(0, num_steps)
                let x = (i + 0.5) * step
                select 4.0 / (1.0 + x * x)).Sum() * step;
    }

    /// <summary>
    /// Estimates the value of PI using a PLINQ-based implementation.
    /// </summary>
    public static double ParallelLinqPi()
    {
        double step = 1.0 / (double)num_steps;

        return (from i in ParallelEnumerable.Range(0, num_steps)
                let x = (i + 0.5) * step
                select 4.0 / (1.0 + x * x)).Sum() * step;
    }

    /// <summary>
    /// Estimates the value of PI using a for loop.
    /// </summary>
    public static double SerialPi()
    {
        double sum = 0.0;
        double step = 1.0 / (double)num_steps;

        for (int i = 0; i < num_steps; i++)
        {
            double x = (i + 0.5) * step;
            sum = sum + 4.0 / (1.0 + x * x);
        }

        return step * sum;
    }

    /// <summary>
    /// Estimates the value of PI using a Parallel.For.
    /// </summary>
    public static double ParallelPi()
    {
        double sum = 0.0;
        double step = 1.0 / (double)num_steps;
        object monitor = new object();

        Parallel.For(0, num_steps, () => 0.0, (i, state, local) =>
        {
            double x = (i + 0.5) * step;
            return local + 4.0 / (1.0 + x * x);
        }, local =>
        {
            lock (monitor)
            {
                sum += local;
            }
        });

        return step * sum;
    }

    /// <summary>
    /// Estimates the value of PI using a Parallel.ForEach and a
    /// range partitioner.
    /// </summary>
    public static double ParallelPartitionerPi()
    {
        double sum = 0.0;
        double step = 1.0 / (double)num_steps;
        object monitor = new object();

        Parallel.ForEach(Partitioner.Create(0, num_steps), () => 0.0, (range, state, local) =>
        {
            for (int i = range.Item1; i < range.Item2; i++)
            {
                double x = (i + 0.5) * step;
                local += 4.0 / (1.0 + x * x);
            }
            return local;
        }, local =>
        {
            lock (monitor)
            {
                sum += local;
            }
        });

        return step * sum;
    }
}