Parallel.ForEach 的具体应用技巧,性能加速

.NET 4.0 有了 Parallel.ForEach(), Parallel.For(), PLINQ 等非常有用的并行相关的支持。但是,并不是简单的利用这些方法的简单形式就可以了,很多时候根据 source list 的不同类型,以及每次循环时所需要调用的处理方法执行时间的长短不同,要想达到最高性能,则必须对 Parallel 相关特性进行详细的了解。

 

大致上,Parallel.ForEach 对 source list 有以下几种拆分模式:

 

1. Range 拆分。适用于原始输入是 Array 或 IList 的情况,这时候,list.Count 确定。所以比较容易根据 source list 的长度,拆分为对应的几个 Range 对象,一次分配好任务的拆分方式,而且因为范围互相不冲突,多个子任务之间不需要线程同步的开销。

 

2. Trunk 拆分。预先创建好几个工作者 trunk. 然后依次扫描 source,这些 chunk 轮流去获取 element 进行处理。一般每个 chunk 一次获取很少的元素,比如1个。对于不确定长度的 source 可能比较有用,但有线程同步的开销。这个可能会适用于不定长度的 source list. 比如,你自己定义一个返回 IEnumerable<T> 的 generator 方法,在里面根据具体的逻辑用 yield return 返回一系列元素,这个可能就比较复杂。

 

3. 自定义的 partitioner. 假设在自己对 source class 的内部构造比较了解的情况下,则有可能写出比系统的 partitioner 性能更高的自定义 partitioner. 但是,需要仔细阅读并了解 MSDN 的范例以创建 custom partitioner.

 

另外,如果每次执行的 delegate 中内容太过简单,以至于 delegate 本身的调用开销可能相对会比较大的时候,有如下优化技巧:

 

首先用 IEnumerable<T>.Range() 方法创建 range 对象。

然后调用 Parallel.ForEach 的合适的重载方式,接受 range 对象,在参数的匿名委托中,针对每一个 sub range,使用循环进行处理。这样,对于每个 sub range,仅有一次 delegate 的调用开销。而不是针对每次循环都要调用 delegate.

 

以上是我学习 MSDN 内容的一点初步的小结,希望能接下来能抽出一些时间,利用 Parallel.ForEach 的这些技巧,对我前一段时间写的一个不确定性计算模型进行性能优化。

 

参考:

 

 
原文地址:https://www.cnblogs.com/RChen/p/1766009.html