Doing Parallel Tasks using Parallel Class – Part 01(Parallel.For Parallel.ForEach)

Introduction

Recently I had a requirement for performance tuning the application which my team is working on. We thought of running using normal .NET Threading.

But I thought about utilizing the Parallel programming extensions(PFX) in .NET Framework 4.0.

Since we have lots of For Loops and for each loops consisting of web service calls, makes our application to wait long (minutes) to complete the tasks. To make effective utilization of Threading and starting calls parallely and effective utilization of Multi-Core efficiently, which improves the performance of our applications.

This article is just some findings on my experiments with Parallel Computing.

.NET Framework 4.0 & Parallel Computing

Many personal computers and workstations have two or four cores (that is, CPUs) that enable multiple threads to be executed simultaneously. Computers in the near future are expected to have significantly more cores. To take advantage of the hardware of today and tomorrow, you can parallelize your code to distribute work across multiple processors. In the past, parallelization required low-level manipulation of threads and locks. Visual Studio 2010 and the .NET Framework 4 enhance support for parallel programming by providing a new runtime, new class library types, and new diagnostic tools. These features simplify parallel development so that you can write efficient, fine-grained, and scalable parallel code in a natural idiom without having to work directly with threads or the thread pool. The following illustration provides a high-level overview of the parallel programming architecture in the .NET Framework 4. The framework is called PFX ( Parallel Framework)

.NET Parallel Programming Architecture

(Ref: from MSDN)

Why PFX?

In recent times, CPU clock speeds have stagnated and manufacturers have shifted their focus to increasing core counts. This is problematic for us as programmers because our standard single-threaded code will not automatically run faster as a result of those extra cores.

Leveraging multiple cores is easy for most server applications, where each thread can independently handle a separate client request, but is harder on the desktop — because it typically requires that you take your computationally intensive code and do the following:

  1. Partition it into small chunks.
  2. Execute those chunks in parallel via multithreading.
  3. Collate the results as they become available, in a thread-safe and performant manner.

Although you can do all of this with the classic multithreading constructs, it’s awkward — particularly the steps of partitioning and collating. A further problem is that the usual strategy of locking for thread safety causes a lot of contention when many threads work on the same data at once.

The PFX libraries have been designed specifically to help in these scenarios.

Programming to leverage multicores or multiple processors is called parallel programming. This is a subset of the broader concept of multithreading.

PFX Concepts

There are two strategies for partitioning work among threads: data parallelism and task parallelism.

When a set of tasks must be performed on many data values, we can parallelize by having each thread perform the (same) set of tasks on a subset of values. This is called data parallelism because we are partitioning the data between threads. In contrast, with task parallelism we partition the tasks; in other words, we have each thread perform a different task.

In general, data parallelism is easier and scales better to highly parallel hardware, because it reduces or eliminates shared data (thereby reducing contention and thread-safety issues). Also, data parallelism leverages the fact that there are often more data values than discrete tasks, increasing the parallelism potential.

Data parallelism is also conducive to structured parallelism, which means that parallel work units start and finish in the same place in your program. In contrast, task parallelism tends to be unstructured, meaning that parallel work units may start and finish in places scattered across your program. Structured parallelism is simpler and less error-prone and allows you to farm the difficult job of partitioning and thread coordination (and even result collation) out to libraries.

PFX Components

PFX comprises two layers of functionality. The higher layer consists of two structured data parallelism APIs: PLINQ and the Parallel class. The lower layer contains the task parallelism classes — plus a set of additional constructs to help with parallel programming activities.

Parallel Programming Components

PLINQ offers the richest functionality: it automates all the steps of parallelization — including partitioning the work into tasks, executing those tasks on threads, and collating the results into a single output sequence. It’s called declarative — because you simply declare that you want to parallelize your work (which you structure as a LINQ query), and let the Framework take care of the implementation details. In contrast, the other approaches are imperative, in that you need to explicitly write code to partition or collate. In the case of the Parallel class, you must collate results yourself; with the task parallelism constructs, you must partition the work yourself, too:

  Partitions work Collates results
PLINQ Yes Yes
The Parallel class Yes No
PFX’s task parallelism No No

The concurrent collections and spinning primitives help you with lower-level parallel programming activities. These are important because PFX has been designed to work not only with today’s hardware, but also with future generations of processors with far more cores. If you want to move a pile of chopped wood and you have 32 workers to do the job, the biggest challenge is moving the wood without the workers getting in each other’s way. It’s the same with dividing an algorithm among 32 cores: if ordinary locks are used to protect common resources, the resultant blocking may mean that only a fraction of those cores are ever actually busy at once. The concurrent collections are tuned specifically for highly concurrent access, with the focus on minimizing or eliminating blocking. PLINQ and the Parallel class themselves rely on the concurrent collections and on spinning primitives for efficient management of work.

PFX and Traditional Multithreading

A traditional multithreading scenario is one where multithreading can be of benefit even on a single-core machine — with no true parallelization taking place. They include such tasks as maintaining a responsive user interface and downloading two web pages at once.

Some of the constructs  in the parallel programming sections are also sometimes useful in traditional multithreading. In particular:

Taken from Threading in C# – Part 5 – By Joseph Albahari ( Visit here for more understanding on Threads & PFX )

As I mentioned earlier I would be trying two methods mainly to discussing my experiences with two methods(Parallel.For, Parallel.ForEach) as part of Class:System.Threading.Tasks.Parallel” within  Namespace: System.Threading.Tasks.

My Experiements with Parallel.For, Parallel.ForEach

I create a Web Service to mock the delay in Service request and responses and set a 1 second delay in each service response from the TestMethod1() in the TestService.asmx web service.

Test Environment :

My personal pc consist of the following configuration. High end 6-core desktop processor by AMD.

AMD Phenom-II 1090T X6 ( 6 Physical cores, 1 threads per core) / 8GB RAM

Since i have 6-cores my sample application task will be distributed among all 6-cores efficiently automatically by .NET Framework 4.0 parallel tasks. So lets evaluate my application sample code.

1.) Source Code : TestService.asmx

<span class="lnum">   1:  </span><span class="kwrd">using</span> System;
<span class="lnum">   2:  </span><span class="kwrd">using</span> System.Collections.Generic;
<span class="lnum">   3:  </span><span class="kwrd">using</span> System.Linq;
<span class="lnum">   4:  </span><span class="kwrd">using</span> System.Web;
<span class="lnum">   5:  </span><span class="kwrd">using</span> System.Web.Services;
<span class="lnum">   6:  </span><span class="kwrd">using</span> System.Threading;
<span class="lnum">   7:  </span>
<span class="lnum">   8:  </span><span class="kwrd">namespace</span> WebSvcApp
<span class="lnum">   9:  </span>{
<span class="lnum">  10:  </span>    <span class="rem">/// &lt;summary&gt;</span>
<span class="lnum">  11:  </span>    <span class="rem">/// Summary description for TestService</span>
<span class="lnum">  12:  </span>    <span class="rem">/// &lt;/summary&gt;</span>
<span class="lnum">  13:  </span>    [WebService(Namespace = <span class="str">"http://tempuri.org/"</span>)]
<span class="lnum">  14:  </span>    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
<span class="lnum">  15:  </span>    [System.ComponentModel.ToolboxItem(<span class="kwrd">false</span>)]
<span class="lnum">  16:  </span>    <span class="rem">// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. </span>
<span class="lnum">  17:  </span>    <span class="rem">// [System.Web.Script.Services.ScriptService]</span>
<span class="lnum">  18:  </span>    <span class="kwrd">public</span> <span class="kwrd">class</span> TestService : System.Web.Services.WebService
<span class="lnum">  19:  </span>    {
<span class="lnum">  20:  </span>&#160;
<span class="lnum">  21:  </span>        [WebMethod]
<span class="lnum">  22:  </span>        <span class="kwrd">public</span> <span class="kwrd">string</span> HelloWorld()
<span class="lnum">  23:  </span>        {
<span class="lnum">  24:  </span>            <span class="kwrd">return</span> <span class="str">"Hello World"</span>;
<span class="lnum">  25:  </span>        }
<span class="lnum">  26:  </span>&#160;
<span class="lnum">  27:  </span>         [WebMethod]
<span class="lnum">  28:  </span>        <span class="kwrd">public</span> <span class="kwrd">string</span> TestMethod1(<span class="kwrd">int</span> uniqId)
<span class="lnum">  29:  </span>        {
<span class="lnum">  30:  </span>            Thread.Sleep(1000);
<span class="lnum">  31:  </span>
<span class="lnum">  32:  </span>            <span class="kwrd">return</span> uniqId.ToString();
<span class="lnum">  33:  </span>        }
<span class="lnum">  34:  </span>    }
<span class="lnum">  35:  </span>}

Parallel.004

2.) Source Code : ParallelUtils.cs ( Implementation class consisting of methods using Parallel.For extension for doing parallel tasks within a for loop or doing operations parallely within for loop)

<span class="lnum">   1:  </span><span class="kwrd">using</span> System;
<span class="lnum">   2:  </span><span class="kwrd">using</span> System.Collections.Generic;
<span class="lnum">   3:  </span><span class="kwrd">using</span> System.Data;
<span class="lnum">   4:  </span><span class="kwrd">using</span> System.IO;
<span class="lnum">   5:  </span><span class="kwrd">using</span> System.Linq;
<span class="lnum">   6:  </span><span class="kwrd">using</span> System.Text;
<span class="lnum">   7:  </span><span class="kwrd">using</span> System.Threading;
<span class="lnum">   8:  </span><span class="kwrd">using</span> System.Threading.Tasks;
<span class="lnum">   9:  </span><span class="kwrd">using</span> System.Diagnostics;
<span class="lnum">  10:  </span><span class="kwrd">using</span> ConsoleApp.WebSvcClient;
<span class="lnum">  11:  </span>
<span class="lnum">  12:  </span><span class="kwrd">namespace</span> ConsoleApp
<span class="lnum">  13:  </span>{
<span class="lnum">  14:  </span>    <span class="rem">/// &lt;summary&gt;</span>
<span class="lnum">  15:  </span>    <span class="rem">/// Using System.Theading.Tasks(parallel programming extensions) for efficient utilization of parallel tasks</span>
<span class="lnum">  16:  </span>    <span class="rem">/// &lt;/summary&gt;</span>
<span class="lnum">  17:  </span>    <span class="kwrd">public</span> <span class="kwrd">class</span> ParallelUtils
<span class="lnum">  18:  </span>    {
<span class="lnum">  19:  </span>        WebSvcClient.TestService clientSvc = <span class="kwrd">new</span> TestService();
<span class="lnum">  20:  </span>&#160;
<span class="lnum">  21:  </span>        <span class="rem">/// &lt;summary&gt;</span>
<span class="lnum">  22:  </span>        <span class="rem">/// Does the parallel task.</span>
<span class="lnum">  23:  </span>        <span class="rem">/// &lt;/summary&gt;</span>
<span class="lnum">  24:  </span>        <span class="rem">/// &lt;returns&gt;&lt;/returns&gt;</span>
<span class="lnum">  25:  </span>        <span class="kwrd">public</span> List&lt;ResultInfo&gt; DoParallelTask()
<span class="lnum">  26:  </span>        {
<span class="lnum">  27:  </span>
<span class="lnum">  28:  </span>            List&lt;ResultInfo&gt; resultInfos = <span class="kwrd">new</span> List&lt;ResultInfo&gt;();
<span class="lnum">  29:  </span>            Stopwatch watch = Stopwatch.StartNew();
<span class="lnum">  30:  </span>&#160;
<span class="lnum">  31:  </span>            <span class="rem">//Executes a for loop in which iterations may run in parallel.</span>
<span class="lnum">  32:  </span>            <span class="rem">//Ref: http://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel.aspx</span>
<span class="lnum">  33:  </span>            Parallel.For(0,100, (item) =&gt;
<span class="lnum">  34:  </span>                                    {
<span class="lnum">  35:  </span>                                       resultInfos.Add( DoWork(item));
<span class="lnum">  36:  </span>                                       Console.WriteLine(<span class="str">"Item Index :: "</span> + item);
<span class="lnum">  37:  </span>                                    })
<span class="lnum">  38:  </span>            ;
<span class="lnum">  39:  </span>
<span class="lnum">  40:  </span>            Console.WriteLine(<span class="str">"Parallel Task Completed with : "</span> + watch.ElapsedMilliseconds + <span class="str">" milli seconds"</span>);
<span class="lnum">  41:  </span>
<span class="lnum">  42:  </span>            <span class="kwrd">return</span> resultInfos;
<span class="lnum">  43:  </span>        }
<span class="lnum">  44:  </span>&#160;
<span class="lnum">  45:  </span>
<span class="lnum">  46:  </span>        <span class="rem">/// &lt;summary&gt;</span>
<span class="lnum">  47:  </span>        <span class="rem">/// Does the normal task.</span>
<span class="lnum">  48:  </span>        <span class="rem">/// &lt;/summary&gt;</span>
<span class="lnum">  49:  </span>        <span class="rem">/// &lt;returns&gt;&lt;/returns&gt;</span>
<span class="lnum">  50:  </span>        <span class="kwrd">public</span> List&lt;ResultInfo&gt; DoNormallTask()
<span class="lnum">  51:  </span>        {
<span class="lnum">  52:  </span>&#160;
<span class="lnum">  53:  </span>            Stopwatch watch = Stopwatch.StartNew();
<span class="lnum">  54:  </span>&#160;
<span class="lnum">  55:  </span>            List&lt;ResultInfo&gt; resultInfos = <span class="kwrd">new</span> List&lt;ResultInfo&gt;();
<span class="lnum">  56:  </span>
<span class="lnum">  57:  </span>            <span class="kwrd">for</span> (<span class="kwrd">int</span> item = 0; item &lt; 100; item++)
<span class="lnum">  58:  </span>            {
<span class="lnum">  59:  </span>
<span class="lnum">  60:  </span>                resultInfos.Add(DoWork(item));
<span class="lnum">  61:  </span>                Console.WriteLine(<span class="str">"Item Index :: "</span> + item);
<span class="lnum">  62:  </span>            }
<span class="lnum">  63:  </span>
<span class="lnum">  64:  </span>            Console.WriteLine(<span class="str">"Normal Task Completed with : "</span> + watch.ElapsedMilliseconds + <span class="str">" milli seconds"</span> );
<span class="lnum">  65:  </span>
<span class="lnum">  66:  </span>            <span class="kwrd">return</span> resultInfos;
<span class="lnum">  67:  </span>        }
<span class="lnum">  68:  </span>&#160;
<span class="lnum">  69:  </span>
<span class="lnum">  70:  </span>&#160;
<span class="lnum">  71:  </span>        <span class="rem">/// &lt;summary&gt;</span>
<span class="lnum">  72:  </span>        <span class="rem">/// Does the work.</span>
<span class="lnum">  73:  </span>        <span class="rem">/// &lt;/summary&gt;</span>
<span class="lnum">  74:  </span>        <span class="rem">/// &lt;param name="querId"&gt;The quer id.&lt;/param&gt;</span>
<span class="lnum">  75:  </span>        <span class="kwrd">private</span> ResultInfo DoWork(<span class="kwrd">int</span> querId)
<span class="lnum">  76:  </span>        {
<span class="lnum">  77:  </span>            clientSvc.TestMethod1(querId);
<span class="lnum">  78:  </span>&#160;
<span class="lnum">  79:  </span>            <span class="kwrd">return</span> <span class="kwrd">new</span> ResultInfo()
<span class="lnum">  80:  </span>                       {
<span class="lnum">  81:  </span>                           Id = querId,
<span class="lnum">  82:  </span>                           Guid = System.Guid.NewGuid().ToString(),
<span class="lnum">  83:  </span>                           Name = <span class="str">"Name::"</span> + querId,
<span class="lnum">  84:  </span>                           Description =  <span class="str">"Desc:: "</span> + querId
<span class="lnum">  85:  </span>
<span class="lnum">  86:  </span>                       };
<span class="lnum">  87:  </span>
<span class="lnum">  88:  </span>        }
<span class="lnum">  89:  </span>
<span class="lnum">  90:  </span>    }
<span class="lnum">  91:  </span>
<span class="lnum">  92:  </span>&#160;
<span class="lnum">  93:  </span>    <span class="kwrd">public</span> <span class="kwrd">class</span> ResultInfo
<span class="lnum">  94:  </span>    {
<span class="lnum">  95:  </span>
<span class="lnum">  96:  </span>        <span class="kwrd">public</span> <span class="kwrd">int</span> Id { get; set;
<span class="lnum">  97:  </span>        }
<span class="lnum">  98:  </span>&#160;
<span class="lnum">  99:  </span>        <span class="kwrd">public</span> <span class="kwrd">string</span> Guid
<span class="lnum"> 100:  </span>        {
<span class="lnum"> 101:  </span>            get;
<span class="lnum"> 102:  </span>            set;
<span class="lnum"> 103:  </span>        }
<span class="lnum"> 104:  </span>&#160;
<span class="lnum"> 105:  </span>        <span class="kwrd">public</span> <span class="kwrd">string</span> Name { get; set;
<span class="lnum"> 106:  </span>        }
<span class="lnum"> 107:  </span>
<span class="lnum"> 108:  </span>         <span class="kwrd">public</span> <span class="kwrd">string</span> Description
<span class="lnum"> 109:  </span>         {
<span class="lnum"> 110:  </span>             get; set;
<span class="lnum"> 111:  </span>         }
<span class="lnum"> 112:  </span>    }
<span class="lnum"> 113:  </span>}

3.) Source Code : Program.cs (Entry method invokes parallel and normal methods to verify the effectiveness of the parallel calls)

<span class="lnum">   1:  </span><span class="kwrd">using</span> System;
<span class="lnum">   2:  </span><span class="kwrd">using</span> System.Collections.Generic;
<span class="lnum">   3:  </span><span class="kwrd">using</span> System.Linq;
<span class="lnum">   4:  </span><span class="kwrd">using</span> System.Text;
<span class="lnum">   5:  </span><span class="kwrd">using</span> System.Configuration;
<span class="lnum">   6:  </span><span class="kwrd">using</span> System.Data;
<span class="lnum">   7:  </span>
<span class="lnum">   8:  </span><span class="kwrd">namespace</span> ConsoleApp
<span class="lnum">   9:  </span>{
<span class="lnum">  10:  </span>    <span class="kwrd">class</span> Program
<span class="lnum">  11:  </span>    {
<span class="lnum">  12:  </span>        <span class="rem">/// &lt;summary&gt;</span>
<span class="lnum">  13:  </span>        <span class="rem">/// Mains the specified args.</span>
<span class="lnum">  14:  </span>        <span class="rem">/// &lt;/summary&gt;</span>
<span class="lnum">  15:  </span>        <span class="rem">/// &lt;param name="args"&gt;The args.&lt;/param&gt;</span>
<span class="lnum">  16:  </span>        <span class="kwrd">static</span> <span class="kwrd">void</span> Main(<span class="kwrd">string</span>[] args)
<span class="lnum">  17:  </span>        {
<span class="lnum">  18:  </span>&#160;
<span class="lnum">  19:  </span>            ParallelUtils parallelUtils = <span class="kwrd">new</span> ParallelUtils();
<span class="lnum">  20:  </span>&#160;
<span class="lnum">  21:  </span>            var result = parallelUtils.DoParallelTask();
<span class="lnum">  22:  </span>            var result1 = parallelUtils.DoNormallTask();
<span class="lnum">  23:  </span>
<span class="lnum">  24:  </span>            Console.Read();
<span class="lnum">  25:  </span>        }
<span class="lnum">  26:  </span>&#160;
<span class="lnum">  27:  </span>
<span class="lnum">  28:  </span>&#160;
<span class="lnum">  29:  </span>
<span class="lnum">  30:  </span>    }
<span class="lnum">  31:  </span>}

 

When I run the application( running 100 web service calls within the loop and retrieving result).

1. Parallel tasks completed with 15 seconds( around 15,000 milli-seconds)

2. Normal tasks(sequential tasks) completed with 110 seconds (around 110,000 milli-seconds)

Parallel.001Parallel.002Parallel.003

Conclusion

Considering the above results consisting of running 100 web service calls within the loop parallel and sequentially, we found that Parallel extensions effectively distributed the tasks among the 6-cores and efficiently finished the works in 15-16 seconds.But the sequential tasks ran in sequentially waiting to complete each task in the loop to continue to the next task. This makes the task slower.

Parallel extensions helpful in distributing tasks among multiple cores and threads efficiently with out needing to worry about threading and locks in older frameworks.

We could implement our own way of threading and locks, but we have to write lots of lines of code to handle deadlocks, handle the thread results and handling locks.

Parallel computing extension in .NET Framework 4.0 makes our parallel computing tasks easier to run the time consuming parallel computing tasks efficiently.

This is a cool option every one should try.

I will leave more articles related to parallel extensions later. Hope this article was helpful.

If you would like to follow me on twitter: @nithinmohantk and facebook: @nithinmohantk  or mail me on nithinmohantk (at) nitrix-reloaded.com in regards to suggestions and feedbacks.