.NET Core  

PLINQ vs LINQ: Use Cases, and Performance Insights in C#

As computationally intensive tasks and large datasets become more common in software applications, developers need efficient tools to process data. Two popular options in C# are LINQ (Language Integrated Query) and PLINQ (Parallel LINQ). While they share similar syntax and functionality, they differ significantly in how they execute queries. This article explores the key differences, use cases, and performance considerations of LINQ and PLINQ, including practical examples and benchmarking their performance.

What is LINQ?

LINQ (Language Integrated Query) is a feature of C# that enables developers to perform data querying in a syntax integrated into the language. Introduced in .NET Framework 3.5, LINQ provides a consistent method to work with different data sources like collections, databases, XML, and more. It executes queries sequentially, processing each item in turn.

LINQ Example

var numbers = new List<int> { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();

foreach (var number in evenNumbers)
{
    Console.WriteLine(number); // Output: 2, 4
}

LINQ is straightforward to use and works well for small-to-medium-sized datasets or queries that are not computationally intensive.

What is PLINQ?

PLINQ (Parallel LINQ) was introduced with .NET Framework 4.0 and extends LINQ by enabling parallel query execution. Built on the Task Parallel Library (TPL), PLINQ uses multiple CPU cores to process large datasets or computationally expensive operations more efficiently. It partitions data into chunks and executes them concurrently using threads.

PLINQ Example

var numbers = Enumerable.Range(1, 10_000);
var evenNumbers = numbers.AsParallel()
                         .Where(n => n % 2 == 0)
                         .ToList();

Console.WriteLine(evenNumbers.Count); // Output: 5000

The AsParallel() method enables parallel execution of the query, leveraging all available processor cores.

Performance Comparison Between LINQ and PLINQ

To better understand how LINQ and PLINQ differ in performance, let’s process a large dataset and measure the time taken for each.

Example: LINQ vs PLINQ Performance

The following code processes a dataset of numbers from 1 to 5,000,000 and filters prime numbers using both LINQ and PLINQ. We also measure execution time.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

class Program
{
    static void Main()
    {
        // Prepare a large dataset
        var largeDataSet = Enumerable.Range(1, 5_000_000).ToList();

        // LINQ benchmark
        var stopwatch = Stopwatch.StartNew();
        var linqPrimes = largeDataSet.Where(IsPrime).ToList();
        stopwatch.Stop();
        Console.WriteLine($"LINQ Time: {stopwatch.ElapsedMilliseconds} ms");
        Console.WriteLine($"LINQ Prime Count: {linqPrimes.Count}");

        // PLINQ benchmark
        stopwatch.Restart();
        var plinqPrimes = largeDataSet.AsParallel().Where(IsPrime).ToList();
        stopwatch.Stop();
        Console.WriteLine($"PLINQ Time: {stopwatch.ElapsedMilliseconds} ms");
        Console.WriteLine($"PLINQ Prime Count: {plinqPrimes.Count}");
    }

    static bool IsPrime(int number)
    {
        if (number <= 1) return false;
        for (int i = 2; i <= Math.Sqrt(number); i++)
        {
            if (number % i == 0) return false;
        }
        return true;
    }
}

Explanation of Benchmark

  1. Dataset: A large range of numbers (1 to 5,000,000) serves as the input.
  2. LINQ: The query is processed sequentially, examining each number to determine if it is prime.
  3. PLINQ: The query runs in parallel, dividing the dataset into chunks for multiple threads to process concurrently.

Expected Output

On a multi-core machine, you might see performance results like:

Output

Ordered vs Unordered Processing in PLINQ

By default, PLINQ processes data in unordered mode to maximize performance. However, if your application requires results to be in the same order as the input dataset, you can enforce order using .AsOrdered().

Example. Using .AsOrdered() in PLINQ

var numbers = Enumerable.Range(1, 10);
var orderedResult = numbers.AsParallel()
                           .AsOrdered()
                           .Where(n => n % 2 == 0)
                           .ToList();
Console.WriteLine(string.Join(", ", orderedResult)); // Output: 2, 4, 6, 8, 10

If maintaining the order doesn’t matter, you can use .AsUnordered() to further optimize performance.

Benchmark. Ordered vs Unordered PLINQ

var numbers = Enumerable.Range(1, 1_000_000).ToList();

var stopwatch = Stopwatch.StartNew();

// Ordered PLINQ
var orderedPrimes = numbers.AsParallel()
                           .AsOrdered()
                           .Where(IsPrime)
                           .ToList();
stopwatch.Stop();
Console.WriteLine($"AsOrdered Time: {stopwatch.ElapsedMilliseconds} ms");

stopwatch.Restart();

// Unordered PLINQ
var unorderedPrimes = numbers.AsParallel()
                             .AsUnordered()
                             .Where(IsPrime)
                             .ToList();
stopwatch.Stop();
Console.WriteLine($"AsUnordered Time: {stopwatch.ElapsedMilliseconds} ms");

Expected Output

AsOrdered Time: 210 ms
AsUnordered Time: 140 ms

Use Cases for LINQ and PLINQ

When to Use LINQ?

  • Small datasets where sequential processing is efficient.
  • Tasks requiring strict order preservation.
  • Easy debugging and simple queries.
  • Real-time systems where lower latency matters more than raw throughput.

When to Use PLINQ?

  • Large datasets where parallel execution can reduce runtime.
  • Computationally intensive tasks, such as processing images or mathematical operations.
  • Bulk operations where order doesn’t matter, e.g., statistical analysis of logs.
  • Applications running on multi-core machines utilize available CPU resources.

Summary Table of Insights

Key Differences Between LINQ and PLINQ

Feature LINQ PLINQ
Execution Sequential Parallel
Performance Best suited for small datasets Designed for large datasets
Utilization Uses a single CPU core Utilizes multiple CPU cores and threads
Order Preservation Preserves element order by default Unordered by default (order can be enforced)
Error Handling Simple error propagation Requires handling of thread-specific exceptions
Control Limited control over execution Offers options like cancellation and partitioning
Overhead No additional overhead Thread management and partitioning may add overhead

Conclusion

Both LINQ and PLINQ are excellent tools for querying data in C#. LINQ is suitable for smaller, simpler datasets, while PLINQ excels in scenarios requiring heavy data processing or operations on large datasets where parallelism can be leveraged.

The choice between ordered and unordered processing in PLINQ depends on whether you need result ordering or prioritize performance. Benchmarking your query in real-world scenarios helps determine the best approach for your use case.

Maximize LINQ and PLINQ’s potential by balancing performance, order sensitivity, and the complexity of your application, ensuring efficient and maintainable code.