天天看点

C#并行编程-PLINQ:声明式数据并行

背景

通过LINQ可以方便的查询并处理不同的数据源,使用Parallel LINQ (PLINQ)来充分获得并行化所带来的优势。

PLINQ不仅实现了完整的LINQ操作符,而且还添加了一些用于执行并行的操作符,与对应的LINQ相比,通过PLINQ可以获得明显的加速,但是具体的加速效果还要取决于具体的场景,不过在并行化的情况下一段会加速。

如果一个查询涉及到大量的计算和内存密集型操作,而且顺序并不重要,那么加速会非常明显,然而,如果顺序很重要,那么加速就会受到影响。

AsParallel() 启用查询的并行化

下面贴代码,看下效果,详情见注释:

C#并行编程-PLINQ:声明式数据并行
C#并行编程-PLINQ:声明式数据并行

View Code

C#并行编程-PLINQ:声明式数据并行

当前模拟的数据量比较少,数据量越多,采用并行化查询的效果越明显

AsOrdered()与orderby

AsOrdered:保留查询的结果按源序列排序,在并行查询中,多条数据会被分在多个区域中进行查询,查询后再将多个区的数据结果合并到一个结果集中并按源序列顺序返回。

orderby:将返回的结果集按指定顺序进行排序

C#并行编程-PLINQ:声明式数据并行

下面贴代码方便大家理解:

C#并行编程-PLINQ:声明式数据并行
C#并行编程-PLINQ:声明式数据并行
C#并行编程-PLINQ:声明式数据并行

在PLINQ查询中,AsOrdered()和orderby子句都会降低运行速度,所以如果顺序并不是必须的,那么在请求特定顺序的结果之前,将加速效果与串行执行的性能进行比较是非常重要的。

指定执行模式 WithExecutionMode

对串行化代码进行并行化,会带来一定的额外开销,Plinq查询执行并行化也是如此,在默认情况下,执行PLINQ查询的时候,.NET机制会尽量避免高开销的并行化算法,这些算法有可能会将执行的性能降低到地狱串行执行的性能。

.NET会根据查询的形态做出决策,并不开了数据集大小和委托执行的时间,不过也可以强制并行执行,而不用考虑执行引擎分析的结果,可以调用WithExecutionMode方法来进行设置。、

下面贴代码,方便大家理解

C#并行编程-PLINQ:声明式数据并行
C#并行编程-PLINQ:声明式数据并行
C#并行编程-PLINQ:声明式数据并行

通过PLINQ执行归约操作

PLINQ可以简化对一个序列或者一个组中所有成员应用一个函数的过程,这个过程称之为归约操作,如在PLINQ查询中使用类似于Average,Max,Min,Sum之类的聚合函数就可以充分利用并行所带来好处。

并行执行的规约和串行执行的规约的执行结果可能会不同,因为在操作不能同时满足可交换和可传递的情况下产生摄入,在每次执行的时候,序列或组中的元素在不同并行任务中分布可能也会有区别,因而在这种操作的情况下可能会产生不同的最终结果,因此,一定要通过对于的串行版本来兴义原始的数据源,这样才能帮助PLINQ获得最优的执行结果。

下面贴代码:

C#并行编程-PLINQ:声明式数据并行
C#并行编程-PLINQ:声明式数据并行

如上述代码所示

在LINQ版本中,该方法会返回一个 IEumerable<int>,即调用 Eumerable.Range方法生成指定范围整数序列的结果,

在PLINQ版本中,该方法会返回一个 ParallelQuery<int>,即调用并行版本中System.Linq.ParallelEumerable的ParallelEumerable.Range方法,通过这种方法得到的结果序列也是并行序列,可以再PLINQ中并行运行。

如果想对特定数据源进行LINQ查询时,可以定义为  private IEquatable<int> products

如果想对特定数据源进行PLINQ查询时,可以定义为 private ParallelQuery<int> products

并发PLINQ任务

C#并行编程-PLINQ:声明式数据并行
C#并行编程-PLINQ:声明式数据并行

如代码所示tk1,tk2,tk3三个任务,tk2,tk3任务的运行需要基于tk1任务的结果,因此,参数中指定了TaskContinuationOptions.OnlyOnRanToCompletion,通过这种方式,每个被串联的任务都会等待之前的任务完成之后才开始执行,tk2,tk3在tk1执行完成后,这两个任务的PLINQ查询可以并行运行,并将会可能地使用多个逻辑内核。

C#并行编程-PLINQ:声明式数据并行

取消PLINQ WithCancellation

通过WithCancellation取消当前PLINQ正在执行的查询操作,代码如下:

C#并行编程-PLINQ:声明式数据并行
C#并行编程-PLINQ:声明式数据并行
C#并行编程-PLINQ:声明式数据并行

指定查询时所需的并行度 WithDegreeOfParallelism

默认情况下,PLINQ总是会试图利用所有的可用逻辑内核达到最佳性能,在程序中我们可以利用WithDegreeOfParallelism方法指定一个不同最大并行度。

C#并行编程-PLINQ:声明式数据并行
C#并行编程-PLINQ:声明式数据并行

好处:如果计算机有8个可用的逻辑内核,PLINQ查询最多运行4个并发任务,这样可用使用Parallel.Invoke 加载多个带有不同并行度的PLINQ查询,有一些PLINQ查询的可扩展性有限,因此这些选项可用让您充分利用额外的内核。

使用ForAll 并行遍历结果

下面贴代码:

C#并行编程-PLINQ:声明式数据并行
C#并行编程-PLINQ:声明式数据并行

ForAll是并行,foreach是串行,如果需要以特定的顺序处理数据,那么必须使用上述串行循环或方法。

WithMergeOptions

通过WithMergeOptions扩展方法提示PLINQ应该优先使用哪种方式合并并行结果片段,如下:

C#并行编程-PLINQ:声明式数据并行

下面贴代码查看下差异:

C#并行编程-PLINQ:声明式数据并行
C#并行编程-PLINQ:声明式数据并行
C#并行编程-PLINQ:声明式数据并行

需要注意的是:每一个选项都有其优点和缺点,因此一定奥测量显示第一个结果的时间以及完成整个查询所需要的时间,这点很重要 。

使用PLINQ执行MapReduce算法 ILookup IGrouping

mapreduce ,也称为Map/reduce 或者Map&Reduce ,是一种非常流行的框架,能够充分利用并行化处理巨大的数据集,MapReduce的基本思想非常简单:将数据处理问题分解为以下两个独立且可以并行执行的操作:

映射(Map)-对数据源进行操作,为每一个数据项计算出一个键值对。运行的结果是一个键值对的集合,根据键进行分组。

规约(Reduce)-对映射操作产生的根据键进行分组的所有键值对进行操作,对每一个组执行归约操作,这个操作可以返回一个或多个值。

下面贴代码,方便大家理解,但是该案列所展示的并不是一个纯粹的MapReduce算法实现:

C#并行编程-PLINQ:声明式数据并行
C#并行编程-PLINQ:声明式数据并行
C#并行编程-PLINQ:声明式数据并行

关于PLINQ:声明式数据并行就写到这,主要是PLINQ下的查询注意项和查询调优的一些扩展方法。如有问题,欢迎指正。

继续阅读