何时使用Partitioner类?

时间:2010-10-27 09:44:51

标签: c# .net .net-4.0

有人可以建议可以/应该使用.NET 4.0中引入的Partitioner类的典型场景吗?

3 个答案:

答案 0 :(得分:31)

Partitioner类用于使并行执行更加粗糙。如果要并行运行许多非常小的任务,则为每个任务调用委托的开销可能会非常高。通过使用Partitioner,您可以将工作负载重新排列为块,并使每个并行调用工作在稍大的集合上。该类抽象此功能,并能够根据数据集的实际条件和可用内核进行分区。

示例:想象一下,您希望并行运行这样的简单计算。

Parallel.ForEach(Input, (value, loopState, index) => { Result[index] = value*Math.PI; });

这会调用Input中每个条目的委托。这样做会给每个人增加一些开销。使用Partitioner我们可以做类似的事情

Parallel.ForEach(Partitioner.Create(0, Input.Length), range => {
   for (var index = range.Item1; index < range.Item2; index++) {
      Result[index] = Input[index]*Math.PI;
   }
});

这会减少调用次数,因为每次调用都可以在更大的集合上运行。根据我的经验,这可以在并行化非常简单的操作时显着提高性能。

答案 1 :(得分:2)

  

要对数据源上的操作进行并行化,其中一个基本步骤是将源分区为多个部分,这些部分可由多个线程同时访问。 PLINQ和任务并行库(TPL)提供了在编写并行查询或ForEach循环时透明工作的默认分区程序。对于更高级的方案,您可以插入自己的分区程序。

了解更多here

答案 2 :(得分:2)

Brian Rasmussen建议的范围分区是一种分区,当工作是CPU密集型时应该使用,往往很小(相对于虚拟方法调用),必须处理许多元素,并且主要是每个元素的运行时间都是常数。

应该考虑的其他类型的分区是块分区。这种类型的分区也称为负载平衡算法,因为工作线程很少处于空闲状态,而有更多的工作要做 - 而范围分区则不然。

当工作有一些等待状态时,应该使用块分区,往往需要对每个元素进行更多处理,或者每个元素可能具有明显不同的工作处理时间。

这方面的一个例子可能是读入内存并处理大小不同的100个文件。 1K文件的处理时间比1mb文件少得多。如果使用范围分区,那么一些线程可能闲置一段时间,因为它们碰巧处理较小的文件。

与范围分区不同,除非您编写自己的自定义分区程序,否则无法指定每个任务要处理的元素数。使用块分区的另一个缺点是,当它返回获取另一个块时可能会有一些争用,因为在该点使用了独占锁。因此,显然不应将块分区用于少量CPU密集型工作。

默认的块分区程序以每个块的1个元素的块大小开始。在每个线程处理三个1元素块之后,块大小增加到每个块2个元素。在每个线程处理了三个2元素块之后,块大小再次增加到每个块3个元素,依此类推。至少这是它的工作方式 Dixin Yan,(参见Chunk分区部分),为Microsoft工作。

顺便说一句,他博客中的漂亮的可视化工具似乎是Concurrency Visualizer profile tooldocs for this tool声称它可用于查找性能瓶颈,CPU利用不足,线程争用,跨核心线程迁移,同步延迟,DirectX活动,重叠I / O区域以及其他信息。它提供图形,表格和文本数据视图,显示应用程序中的线程与整个系统之间的关系。

其他资源:

MSDN: Custom Partitioners for PLINQ and TPL

Joseph Albahari的

Part 5: Parallel Programming - Optimizing PLINQ