2010-10-27 45 views

回答

26

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; 
    } 
}); 

这将减少所调用的数量,因为每个调用将在更大的一组工作。根据我的经验,这可以在非常简单的操作并行化时显着提升性能。

+8

Partitioner.Create()的默认rangeSize是1。因此,这两个代码示例的分区是相同的。除非Partitioner.Create(0,Input.Length,i);其中i> 1,它仍然具有相同数量的线程。 – Pingpong 2014-01-24 15:31:28

2

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

更多here

+2

我添加到:一般来说*你*不使用它。 PLINQ的确如此,你可能会逃脱默认分区。 – 2010-10-27 09:51:24

1

如Brian Rasmussen所建议的,范围分区是一种在CPU密集型工作时应该使用的分区类型,往往很小(相对于虚拟方法调用),必须处理许多元素,并且当涉及到每个元素的运行时间时,大多是不变的。

应该考虑的另一种类型的分区是块分区。这种类型的分区也被称为负载平衡算法,因为工作线程在很多工作要做的时候很少会闲置 - 这不是范围分区的情况。

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

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

与范围分区不同,无法指定每个任务要处理的元素数 - 除非您编写自己的定制分区程序。使用块分区的另一个缺点是,当它返回到另一个块时可能会有一些争用,因为在此时使用了排它锁。所以,很显然,一个块分区不应该用于短时间的CPU密集型工作。

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

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

其他资源:

MSDN: Custom Partitioners for PLINQ and TPL

Part 5: Parallel Programming - Optimizing PLINQ约瑟夫阿尔巴哈利