Channels ▼
RSS

.NET

PLINQ: Parallel Queries in .NET


Language-Integrated Query (LINQ) is a feature of Microsoft's .NET Framework that adds native data-query capabilities to .NET languages. PLINQ is a parallel version of LINQ. It was introduced with .NET Framework 4.0. The objective of parallel programming is to maximize processor utilization with increased throughput in a multicore architecture. For a multicore computer, your application should recognize and scale performance to the number of available processor cores. LINQ queries execute when you iterate over the results, and they execute sequentially. With PLINQ, the iterations are performed in parallel, as tasks are scheduled on threads running in the .NET Framework 4 thread pool.

More Insights

White Papers

More >>

Reports

More >>

Webcasts

More >>

One of the best features of PLINQ is that it's easy to convert LINQ queries to PLINQ. You can simply add the AsParallel clause. Here is a simple LINQ query that returns a selection of books. The results are generated sequentially:

from book in books
  where book.Publisher=="Lucerne Publishing"
  orderby book.Title
  select book;

Now, here's the same query updated for PLINQ. Note that the only addition to the code is the call to the AsParallel method of the books collection. This minor change, however, completely alters how the query is performed. When you iterate over the results, the query is performed with parallel tasks.

from book in books.AsParallel() 
  where book.Publisher=="Lucerne Publishing" 
  orderby book.Title 
  select book;

The following discussion will present an example that contrasts the productivity of standard LINQ and PLINQ. You can perform the example query either sequentially or in parallel. You'll display information to compare the performance of both approaches. The task and thread identifiers are also displayed to highlight the underlying differences between parallel and sequential execution. Because the sequential version of the query does not use the Task Parallel Library (TPL), the task IDs are blank. In addition, the sequential version will execute on a single thread.

Perform a sequential query and a parallel query on an integer array and imperatively invoke the Where clause.

1. Create a console application for C# in Visual Studio 2010. Add using statements for both the System.Threading and System.Diagnostics namespaces.

2. Above the Main method, create a static method called Normalize that returns a bool. You'll call this method in the Where clause. In the Normalize method, display the current task and thread identifiers. Use the Thread.SpinWait method to simulate a real-world normalization operation. Return true to select and add the current element to the result collection.

static bool Normalize()
{
  Console.WriteLine(
    "Normalizing [Task {0} : Thread {1}]",
    Task.CurrentId,
    Thread.CurrentThread.ManagedThreadId
  );
  Thread.SpinWait(int.MaxValue);
  return true;
}

3. In Main, create an instance of the Stopwatch class. The Stopwatch is used to calculate the duration of both the sequential and the parallel versions of the PLINQ query. Also, define an integer array that has four elements. Here is the array you will query:

Stopwatch sw = new Stopwatch();
var intArray = new [] { 1, 2, 3, 4 };

4. Perform a sequential query on the array by using LINQ to Objects. Call the Where method. Evaluate a lambda expression and call the Normalize method as a parameter. The Where method — and consequently Normalize — will be called for each element of the array. Because the lambda expression returns true for each element, all elements of the array are included in the results. On the next line, repeat the query but use PLINQ. Add the AsParallel method. For now, comment out the parallel version of the query. You will initially run only the sequential query.

var result = intArray.Where((index)=>Normalize());
//var result = intArray.AsParallel().Where((index) =>
//Normalize());

5. Start the Stopwatch, and then iterate the results of the query. Display the results of the operation. Because of deferred execution, this is when the query actually executes.

foreach (int item in result)
{
  Console.WriteLine("Item={0}", item);
}

6. Call the Stop method on the Stopwatch class and display the duration:

sw.Stop();
Console.WriteLine("Elapsed time: {0}: seconds",
        sw.ElapsedMilliseconds / 1000);

Here is the entire program:

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

namespace Performance
{
  class Program
  {
    static bool Normalize()
    {
      Console.WriteLine(
        "Normalizing [Task {0} : Thread {1}]",
        Task.CurrentId, 
        Thread.CurrentThread.ManagedThreadId
      );
      Thread.SpinWait(int.MaxValue);
      return true;
    }

    static void Main(string[] args)
    {
      Stopwatch sw = new Stopwatch();
      var intArray = new [] { 1, 2, 3, 4 };
      var result = intArray.Where((index)=>
                    Normalize());
      //var result = 
    //intArray.AsParallel().Where((index) => Normalize());

      sw.Start();
      foreach (int item in result)
      {
        Console.WriteLine("Item={0}", item);
      }
      sw.Stop();

      Console.WriteLine("Elapsed time: {0}: 
               seconds", sw.ElapsedMilliseconds / 1000);
      Console.WriteLine("Press Enter to Continue");
      Console.ReadLine();
    }
  }
}

Build and run the application. Because the statement containing the PLINQ query is commented, the code executes only the standard LINQ query. Each operation is therefore performed sequentially and on the same thread, which you can see from the output in the console window as shown in Figure 1.


Figure 1: Output of the serial version.

Because parallel tasks are not used, task IDs are not displayed. The duration is essentially the sum of running each of the operations in order. Now uncomment the statement containing the PLINQ command and comment out the LINQ query instead. Rerun the application. This time, the results are entirely different. The Where method runs in parallel and on different threads, as shown in the output window in Figure 2.


Figure 2: Output of the parallel version.

The PLINQ query leverages the multicore processor architecture; the results are specific to this example and the current hardware architecture. In this example, each iteration of a query operation is a different task. For this reason, the query runs faster.

The difference in performance of the two versions is depicted in the processor utilization of the Task Manager. Figure 3 is a screen shot of the processor utilization from the LINQ version of the application. In this example, the average processor utilization is about 12 percent. Most of the processor computing capability is unused!


Figure 3: Processor utilization of the serial version.

The PLINQ version of the application is much more efficient. In Figure 4 you can see a considerably higher utilization — more than 50 percent.


Figure 4: Processor utilization of the parallel version.


Related Reading






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video