29.7 PLINQ
PLINQ就是并行化了的LINQ,是LINQ to Objects的并行实现。使用PLINQ,可以让查询在多个处理器中同时执行,以期更高的执行效率。PLINQ将LINQ语法的简洁和可靠性与并行编程的强大功能结合在一起。就像面向任务并行库的代码一样,PLINQ查询会根据主计算机的能力按比例调整并发程度。在许多情况下,PLINQ可通过更有效地使用主计算机上的所有可用内核来显著提高LINQ to Objects查询的速度。这一性能提升将使桌面具备高性能计算能力。
并行执行,PLINQ通常只需向数据源添加AsParallel查询操作,举例如下,如代码清单29-30所示。
代码清单29-30 PLINQ和普通LINQ查询的效率对比
1 using System;
2 using System.Linq;
3
4 namespace ProgrammingCSharp4
5{
6 class ParallelSample
7{
8 static void Main(string[]args)
9{
10 for(int i=0;i<5;i++)
11{
12 Console.WriteLine(“第{0}次”,i+1);
13 Test();
14 Console.WriteLine("-".PadLeft(35,'-'));
15}
16}
17
18//分别进行并行LINQ和普通LINQ查询,并记录各自查询时间
19 private static void Test()
20{
21 StopWatch sw=new StopWatch();
22 sw.Begin();
23 var source=Enumerable.Range(1,50000);
24
25//并行LINQ查询
26 var nums=from num in source.AsParallel()
27 where ComplexCompute(num)>0
28 select num;
29
30 foreach(var num in nums){}
31
32 long time1=sw.StopAndShowTime();
33 Console.WriteLine(“并行LINQ查询所需时间:{0}毫秒”,time1);
34
35 sw.Reset();
36 sw.Begin();
37//普通LINQ查询
38 var evenNums2=from num in source
39 where ComplexCompute(num)>0
40 select num;
41
42 foreach(var num in evenNums2){}
43
44 long time2=sw.StopAndShowTime();
45 Console.WriteLine(“普通LINQ查询所需时间:{0}毫秒”,time2);
46 Console.WriteLine(“较普通LINQ效率提升:{0:P2}”,
Convert.ToDouble(time2-time1)/time2);
47}
48
49//本操作模拟比较耗时的操作
50 private static int ComplexCompute(int num)
51{
52 int temp=0;
53 for(int i=0;i<num;i++)
54{
55 temp+=i;
56}
57 return temp;
58}
59}
60}
在上述代码的Test方法中,分别使用PLINQ和LINQ进行了查询,其中ComplexCompute方法是模拟一个比较费时的操作,稍后会讲到。该方法的“简单”与“复杂”会产生比较大的影响。在Main方法中,将Test方法执行五次,观察执行的平均效果,结果如下:
第1次
并行LINQ查询所需时间:3235毫秒
普通LINQ查询所需时间:6796毫秒
较普通LINQ效率提升:52.40%
第2次
并行LINQ查询所需时间:3079毫秒
普通LINQ查询所需时间:6703毫秒
较普通LINQ效率提升:54.07%
第3次
并行LINQ查询所需时间:3125毫秒
普通LINQ查询所需时间:6687毫秒
较普通LINQ效率提升:53.27%
第4次
并行LINQ查询所需时间:3094毫秒
普通LINQ查询所需时间:6594毫秒
较普通LINQ效率提升:53.08%
第5次
并行LINQ查询所需时间:3093毫秒
普通LINQ查询所需时间:6750毫秒
较普通LINQ效率提升:54.18%
请按任意键继续……
可见,这里PLINQ较普通LINQ的效率提升平均达50%以上,但是设置并行查询的开销可能获得的性能收益大。如果查询不执行太多计算,或者数据源很小,则PLINQ查询可能比顺序LINQ to Objects查询慢。
接下来分析一下数据源的AsParallel方法,如图29-8所示。
图 29-8 集合类型的AsParallel方法
如上图所示,source是一个集合,也是一个IEnumerable类型。这里的AsParallel是一个扩展方法,定义于System.Linq.ParallelEnumerable类。该方法共有3个重载版本,如下所示:
❑public static ParallelQuery<TSource>AsParallel<TSource>(this Partitioner<TSource>source);
❑public static ParallelQuery<TSource>AsParallel<TSource>(this IEnumerable<TSource>source);
❑public static ParallelQuery AsParallel(this IEnumerable source);
我们这里使用的是第3个重载,因为source是一个非泛型集合。AsParallel方法可以启用查询的并行化。ParallelEnumerable类中除了AsParallel方法外,还有其他一些重要的方法,如AsSequential()、AsOrdered()以及ForAll()等。