何时使用Partitioner类?

And*_*zub 33 .net c# .net-4.0

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

Bri*_*sen 33

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

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

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

这将调用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;
   }
});
Run Code Online (Sandbox Code Playgroud)

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

  • Partitioner.Create()的默认rangeSize是一个.因此,对于两个代码示例,分区是相同的.除非Partitioner.Create(0,Input.Length,i); 其中i> 1,它仍然具有相同数量的线程. (8认同)
  • @Pingpong,这似乎不正确,至少目前是这样。我发现默认分区程序在我的 8 处理器机器上创建了 24 个块。这比输入的长度小得多。我_想_想了解它如何确定此默认值。 (2认同)

Bob*_*yan 9

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

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

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

其中一个示例可能是读入内存并处理 100 个大小差异很大的文件。处理 1K 文件的时间比处理 1mb 文件要少得多。如果为此使用范围分区,则某些线程可能会空闲一段时间,因为它们碰巧处理较小的文件。

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

默认块分区器以每个块 1 个元素的块大小开始。每个线程处理三个 1 元素块后,块大小将增加到每个块 2 个元素。每个线程处理完三个 2 元素块后,块大小再次增加到每个块 3 个元素,依此类推。至少根据 Microsoft 工作人员Dixin Yan 的说法(请参阅块分区部分),这是它的工作方式。

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

其他资源:

MSDN:PLINQ 和 TPL 的自定义分区器

第 5 部分:并行编程 - 优化 PLINQ,作者:Joseph Albahari