Skip to content

It is dedicated to demonstrate the parallel programming capabilities in dotnet

Notifications You must be signed in to change notification settings

peter-csala/parallel-programming-dotnet

Repository files navigation

Welcome to the world of Parallel Programming in .NET

This repository's main purpose is to demonstrate the various tools that the .NET ecosystem provides us to write code that can run in parallel. Feel free to contribute. :)

Table of Contents

  1. Required environment
  2. Demo Application
    2.1) Static File server
    2.2) Throttled Downloader library
    2.3) Benchmark tool
  3. Instructions for running
    3.1) Benchmark
    3.2) Debug
  4. Demonstrated tools
    4.1) Baselines
    4.2) Low level abstractions
    4.3) Mid level abstractions
    4.4) High level abstractions
  5. Sample benchmark result
  6. .NET Profiling
  7. Known missing sample codes

Required environment

  • .NET Core 3.0
  • Visual Studio 2019

Demo Application

In order to demonstrate the different capabilities, we need a demo application.
In our case this app will be a throttled parallel downloader. The solution contains three projects:

I. - Static File server

It is an ASP.NET Core 3.0 web-application which can serve static files for http clients.
It exposes the files under the Resources folder through the /resources route.
Related project: ThrottledParallelism

II. - Throttled Downloader library

It is a .NET Core 3.0 library, which is exposing a simple API and several implementations of it.

public interface IGovernedParallelDownloader
{
    Task DownloadThemAllAsync(IEnumerable<Uri> uris, ProcessResult processResult, byte maxThreads);
}

Related project: LogFileServer

III. - Benchmark tool

It is a .NET Core 3.0 console application, which is used to perform micro-benchmarking. It measures execution time, GC cycles, etc.
Related project: RunEverythingInParallel


NOTE

Please note that this demo is I/O bound.
Which means that using techniques like Task.Run or Parallel.XYZ, which are CPU-bound, does not make too much sense, because they are limited to the number of cores in the machine.
So, please scrutinize the provided examples with this in mind.


Instructions for running

Benchmark

  1. Make sure that Program.cs of the RunEverythingInParallel project look like this:
using System;
using System.Threading;
using BenchmarkDotNet.Running; //BenchmarkRunner

namespace RunEverythingInParallel
{
    class Program
    {
        //Use Release 
        static void Main(string[] args)
        {
            Thread.Sleep(1000); //Wait for the WebApp to start
            BenchmarkRunner.Run<ThrottledDownloader>(); //Add as many benchmarks as you want to run
            Console.ReadLine();
        }
    }
}
  1. Build the solution in Release mode (Set the Optimize node in the csproj to true if it is needed)
  2. Hit Ctrl+F5 in Visual Studio
  3. If you want to run it without VS (by using the dotnet cli) then run the LogFileServer project prior the RunEverythingInParallel

Debug

  1. Make sure that Program.cs of the RunEverythingInParallel project look like this:
using System;
using System.Threading;
using ThrottledParallelism.Strategies;

namespace RunEverythingInParallel
{
    class Program
    {
        //Use Debug
        static void Main(string[] args)
        {
            Thread.Sleep(1000); //Wait for the WebApp to start
            var downloader = new ThrottledDownloader();
            downloader.Setup();
            downloader.RunExperiment<HighLevel_Foreach_AsParallel>(); 
        }
    }
}
  1. Build the solution in Debug mode (Set the Optimize node in the csproj to false if it is needed)
  2. Hit F5 in Visual Studio
  3. Analyze the choosen code by using the Parallel Watch, Parallel Stack and Tasks windows

Demonstrated tools

Baselines

No. Channel Synchronizer Workers via Throttled by File
1 IEnumerable - main thread - Link
2 IEnumerable Task.WhenAll Task - Link

Low level abstractions

No. Channel Synchronizer Workers via Throttled by File
1 BlockingCollection AsyncCountdownEvent ThreadPool.QueueUserWorkItem Manually (for (i = 0; i < maxThreads; i++)) Link
2 BlockingCollection CountdownEvent ThreadPool.QueueUserWorkItem Manually (for (i = 0; i < maxThreads; i++)) Link
3 BlockingCollection Parent Task Children Tasks Manually (for (i = 0; i < maxThreads; i++)) Link
4 BlockingCollection Task.WhenAll Task Manually (for (i = 0; i < maxThreads; i++)) Link
5 IEnumerable<KeyValuePair<Uri, Func<Uri, Task>> Task.WhenAll Task Load balancing by MoreLinq's Segment Link
6 IGrouping<int, Job> Task.WhenAll Task Load balancing by MoreLinq's GroupAdjacent Link

Mid level abstractions

No. Channel Synchronizer Workers via Throttled by File
1 ActionBlock CancellationTokenSource + Interlocked Task ExecutionDataflowBlockOptions Link
2 ActionBlock + BatchBlock PropagateCompletion + Completion Task ExecutionDataflowBlockOptions Link
3 BufferBlock Task.WhenAll + ImmutableList Task Manually (for (i = 0; i < maxThreads; i++)) Link
4 Channel Task.WhenAll Task Manually (Enumerable.Range(0, maxThreads -1)) Link

High level abstractions

No. Channel Synchronizer Workers via Throttled by File
1 Partitioner Parallel.Foreach Task + AsyncHelper.RunSync!!! ParallelOptions Link
2 IGrouping<int, Uri> Parallel.Invoke Task + AsyncHelper.RunSync!!! GroupBy (round robin) Link
3 IGrouping<int, Uri> Parallel.For +TLS Task + AsyncHelper.RunSync!!! GroupBy + Parallel.For Link
4 ConcurrentQueue Task.WhenAll Task Manually (Enumerable.Range(0, maxThreads -1)) Link
5 IEnumerable ParallelForEachAsync Task ParalellelForEachAsync Link
6 HashSet Task.WhenAny Task Manually only during initialization Link
7 IAsyncEnumerable await last Task Task SemaphoreSlim Link
8 ParallelQuery Task.WhenAll Task WithDegreeOfParallelism Link
9 ParallelQuery Custom Awaiter Task WithDegreeOfParallelism Link
10 IEnumerable Task.WhenAll Task SemaphoreSlim Link
11 IEnumerable Task.WhenAll Task BulkheadAsync Link

Sample benchmark result

BenchmarkDotNet=v0.11.5
OS=Windows 10.0.17134.1069 (1803/April2018Update/Redstone4)
Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores Frequency=2062501 Hz, Resolution=484.8482 ns .NET Core SDK=3.0.100
Host : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT
IterationCount=3 RunStrategy=ColdStart

Method Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
BaseLine_Sequentially 3.151 s 1.3267 s 0.0727 s 1.00 0.00 377000.0000 47000.0000 38000.0000 1600419.34 KB
BaseLine_EmbarrassinglyParallel 1.055 s 1.8874 s 0.1035 s 0.33 0.03 270000.0000 1000.0000 1000.0000 58.29 KB
CSharp3 1.426 s 0.7124 s 0.0390 s 0.45 0.02 320000.0000 8000.0000 7000.0000 1.98 KB
CSharp5 1.943 s 1.6989 s 0.0931 s 0.62 0.02 383000.0000 4000.0000 4000.0000 4.32 KB
CSharp6 1.318 s 0.6582 s 0.0361 s 0.42 0.00 367000.0000 1000.0000 1000.0000 22.23 KB
CSharp8 1.340 s 1.8518 s 0.1015 s 0.42 0.02 300000.0000 4000.0000 4000.0000 13.41 KB
Bonus 1.999 s 2.2747 s 0.1247 s 0.63 0.04 405000.0000 3000.0000 3000.0000 30.4 KB

.NET Profiling

If you want to deep dive into the execution details, I highly recommend you to use some profiling.
If sampling is enough for you, then I encourage you to use CodeTrack

If tracing is needed, then you can play with the Concurrency Visualizer step-by-step

Known missing sample codes

  • Reactive eXtensions
  • ideas are more than welcome

About

It is dedicated to demonstrate the parallel programming capabilities in dotnet

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages