Pulled Apart - Part VII: PLINQ, not as easy as first assumed
[
]
Note: This is part of a series, you can find the rest of the parts in the series index.
PLINQ, which is Parallel LINQ—or the ability to run LINQ queries with parallel extensions in .NET 4—is supposed to magically parallelize queries by appending .AsParallel(). Here’s my infamous "insane solution" to Fizz Buzz:
var result = from i in Enumerable.Range(0, 1000).AsParallel()
where (i % 3 == 0 || i % 5 == 0)
select new { value = i, answer = i % 3 == 0 ? i % 5 == 0 ? "Fizz Buzz" : "Buzz" : "Fizz" };
foreach (var item in result)
{
Console.WriteLine("{0} gets a {1}", item.value, item.answer);
}
If you’ve attended one of my What’s New in .NET 4 talks, you might have seen me demo this—and for that, I am VERY VERY SORRY, because I was wrong. 🙁
In Pull, I used this exact approach to parallelize podcast updates. It wasn’t until I implemented a status view that I realized—two weeks and 46 check-ins later—it wasn’t actually parallel.
The issue? .AsParallel() alone does little more than setup. For true parallelism, you need .ForAll(), as shown below (note the processing change on line 5):
var result = from i in Enumerable.Range(0, 30).AsParallel()
where (i % 3 == 0 || i % 5 == 0)
select new { value = i, answer = i % 3 == 0 ? i % 5 == 0 ? "Fizz Buzz" : "Buzz" : "Fizz" };
result.ForAll(item =>
{
Console.WriteLine("{0} gets a {1}", item.value, item.answer);
});
Now Pull truly runs in parallel. Problem solved? Wrong again. Further research led me to a white paper by Pamela Vagata (Microsoft’s Parallel Computing Platform Group). It clearly explains when to use PLINQ vs. Parallel.ForEach:
| Action | PLINQ | Parallel.ForEach |
|---|---|---|
| Simple Data-Parallel Operation with Independent Actions | ✅ | |
| Ordered Data-Parallel Operation | ✅ | |
| Streaming Data-Parallel Operation | ✅ | |
| Operating over Two Collections | ✅ | |
| Thread-Local State | ✅ | |
| Exiting from Operations | ✅ |
My mistake? I used PLINQ for a Simple Data-Parallel Operation with Independent Actions—a perfect case for Parallel.ForEach. Why?
While PLINQ’s
ForAllexists, parallel loops are often more intuitive for independent actions. PLINQ’s overhead can be excessive for simple tasks. WithParallel.ForEach, you control thread count viaMaxDegreeOfParallelism, adapting to system resources. PLINQ, however, requires exact thread counts viaWithDegreeOfParallelism(), locking in resource usage.
Final Thoughts
.NET 4 made parallelism too easy—so easy that wrong choices still appear to work. Research matters. Don’t assume.