Parallel 2013 Conference - Workshop Parallel Programming

17 May 2013 - .NET, C#, Visual Studio

On May 17th I did a workshop on parallel programming at parallel 2013 conference in Karlsruhe. The attendees asked me to publish some of the live coding samples I did. In this blog you can find the samples.

Slides

The slides for the workshop were handed out to attendees on memory sticks. However, my slides were quite similar to the slides I have used in my parallel programming workshop at the last BASTA conference. You can find them on SlideShare:

Sample for Introduction for Tasks

Here is the sample code that I have developed step by step to describe what a Task is in .NET:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            //var tasks = new Task[10];
            //for (int i = 0; i < tasks.Length; i++)
            //{
            //  tasks[i] = DivideAsync(100, i);
            //}

            //Task.WaitAll(tasks);

            //Task.WaitAll(Enumerable.Range(0, 10)
            //  .Where(i => i % 2 == 0)
            //  .Select(i => DivideAsync(100, i))
            //  .ToArray());

            Task.Run(() => 42)
                .ContinueWith(t =>
                    {
                        Task.Factory.ContinueWhenAll(
                            Enumerable.Range(0, 3)
                                .Select(i => Task.Run(() => { Thread.Sleep(200); return 5; }))
                                .ToArray(),
                                tArray =>
                                {
                                    Thread.Sleep(200);
                                    Console.WriteLine(
                                        tArray.Aggregate<Task<int>, int>(
                                            0,
                                            (agg, current) => agg + current.Result)
                                        );
                                }, TaskContinuationOptions.AttachedToParent);
                    })
                .Wait();
        }

        static Task<int> NullTask = Task.FromResult(0);
        static Task<int> DivideAsync(int x, int y)
        {
            if (y == 0)
            {
                // return NullTask;
                var tcs = new TaskCompletionSource<int>();
                tcs.SetException(new DivideByZeroException());
                return tcs.Task;
            }

            return Task.Run(() =>
                {
                    Thread.Sleep(500);
                    return x / y;
                });
        }
    }
}

Monte Carlo Simulation to Calculate PI

This is the serial implementation of the monte carlo simulation example from which we started our discussion about parallel algorithms:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            MonteCarloSerial();
        }

        /// <summary>
        /// Serial implementation of our monte carlo algorithm for calculating PI
        /// </summary>
        /// <remarks>
        /// On my laptop this shows the following results:<br/>
        /// 3,14171336</br>
        /// 32,1411226701302</br>
        /// </remarks>
        public static void MonteCarloSerial()
        {
            var rand = new Random();
            var counterInside = 0;
            var iterations = 350000000;
            var stopwatch = new Stopwatch();
            stopwatch.Start();

            for(int i=0; i<iterations; i++)
            {
                var a = rand.NextDouble();
                var b = rand.NextDouble();
                var c = Math.Sqrt(a * a + b * b);
                if (c <= 1)
                {
                    counterInside++;
                }
            }

            stopwatch.Stop();
            Console.WriteLine(((double)counterInside) / ((double)iterations) * 4);
            Console.WriteLine(iterations / 1000000 / stopwatch.Elapsed.TotalSeconds);
        }
    }
}

After a few steps we parallelized the algorithm. Note that it was not our goal to come up with the most optimal algorithm for calculating PI using monte carlo simulation. We have just used the example to discuss various aspects of parallel programming with .NET.

private static void MonteCarloAdvancedParallel()
{
    var counterInside = 0;
    var iterations = 350000000;
    var stopwatch = new Stopwatch();
    stopwatch.Start();

    Parallel.For(0, 8, i =>
    {
        if (rand == null)
        {
            rand = new Random();
        }

        var localCounterInside = 0;
        for (var j = 0; j < iterations / 8; j++)
        {
            var a = rand.NextDouble();
            var b = rand.NextDouble();
            var c = a * a + b * b;
            if (c <= 1)
            {
                localCounterInside++;
            }
        }

        Interlocked.Add(ref counterInside, localCounterInside);
    });

    stopwatch.Stop();
    Console.WriteLine(((double)counterInside) / ((double)iterations) * 4);
    Console.WriteLine(iterations / 1000000 / stopwatch.Elapsed.TotalSeconds);
}

Producer/Consumer Pattern

In the workshop we discussed various possibilities of implementing producer/consumer problems. The attendees asked me to publish one possible algorithm which makes use of the BlockingCollection<T> class:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var queue = new BlockingCollection<int>(5);

            // Alternative: TPL Dataflow Library von .NET 4.5

            // Producer/Consumer oder Pipline-Algorithmen
            var producer = Task.Run(() =>
                {
                    for (int i = 0; i < 100; i++)
                    {
                        Thread.Sleep(100);
                        queue.Add(i);
                    }
                });

            var consumer = Task.Run(() =>
                {
                    foreach (var item in queue.GetConsumingEnumerable())
                    {
                        Console.WriteLine(item);
                    }
                });

            producer.Wait();
            queue.CompleteAdding();
            consumer.Wait();
        }
    }
}