task and data parallelism



Simone Grignola

Salvatore Capuano


Board International SA


Based on Sasha Goldshtein session @ DevReach 2012

Why this talk?


  • Multicore machines are a cheap commodity
  • Adoption of concurrent programming is still slow
  • Pattern and best practices are scarce

Task parallel library


  • 2008: Incubated for 3 years
  • 2010: Released with .NET 4.0
  • 2012: Data Flow in .NET 4.5 (smart chain of Tasks), await - async, ...

tasks

  • A task is a unit of work
  • May be executed in parallel
  • More than a thread but cheaper



try //The C# 5.0 version
{
    var task = Task.Run(DnaSimulation);
    DisplayProgress();
    Show(await task);
}
catch(Exception ex)
{
    Show(ex);
}

Parallel loops

  • Ideal for parallelizing work over a collection of data
  • Easy porting of for and foreach
  • Available also for Linq



Parallel.For(0, 100, i => {
    ...
});
                                
Parallel.ForEach(urls, url => {
    webClient.Post(url, options, data);
});


measuring concurrency

  • Parallelizing code is worthless if you can't measure the results
  • New Visual Studio Concurrency Visualizer:

Syncronization => Aggregation


  • Excessive synchronization brings parallel code to its knees
  • Try to avoid shared state, or minimize access to it
  • Aggregate thread- or task-local state and merge later

Parallel.ForEach(
    Partitioner.Create(Start, End, ChunkSize),
    () => List(), //initial local state
    (range, pls, localPrimes) => { //aggregator
        for (int i = range.Item1; i < range.Item2; ++i)
            if (IsPrime(i)) localPrimes.Add(i);
        return localPrimes;
    },
    localPrimes => { //combiner
        lock (primes) 
            primes.AddRange(localPrimes);
    });

Creative Synchronization

  • Stock prices: initialized with 105 name/price pairs
  •  Access rate: 107 reads/s, 106 “update” writes/s, 103 “add” writes/day
  • Many reader threads, many writer threads
  • Store the data in two dictionaries: safe and unsafe

GET(key):
    if safe contains key then
        return safe[key]
    lock
    { 
        return unsafe[key]
    }

PUT(key, value):
    if safe contains key then safe[key] = value
    lock
    {
        unsafe[key] = value
    }

Lock-Free Patterns

  • Try to void Windows synchronization and use hardware synchronization
  • Primitive operations such as Interlocked.Increment, Interlocked.Add, Interlocked.CompareExchange
  • Retry pattern with Interlocked.CompareExchange enables arbitrary lock-free algorithms, ConcurrentQueue and ConcurrentStack
                        
                            
                                int
                             InterlockedMultiply(
                                ref
                             
                                int
                             
                                x
                            , 
                                int
                             
                                y
                            ) {
                            
                                int
                             t, r;
                            
                                do
                             {
                            t = 
                                x
                            ;
                            r = t * 
                                y
                            ;
                            }
                            
                                while
                             (Interlocked.CompareExchange(
                                ref
                             
                                x
                            , r, t) != t);
                            
                                return
                             r;
                            }