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.7 PLINQ - 图1

图 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()等。