【发布时间】:2010-10-27 09:44:51
【问题描述】:
谁能建议可以/应该使用 .NET 4.0 中引入的 Partitioner 类的典型场景?
【问题讨论】:
谁能建议可以/应该使用 .NET 4.0 中引入的 Partitioner 类的典型场景?
【问题讨论】:
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;
}
});
这将减少调用次数,因为每次调用都将在更大的集合上工作。以我的经验,当并行化非常简单的操作时,这可以显着提高性能。
【讨论】:
范围分区,正如 Brian Rasmussen 所建议的那样,是一种在 CPU 密集型工作时应该使用的分区类型,往往很小(相对于虚拟方法调用),必须处理许多元素,并且大部分是在每个元素的运行时间方面保持不变。
应该考虑的另一种分区类型是块分区。这种类型的分区也称为负载平衡算法,因为工作线程很少会在有更多工作要做时处于空闲状态——范围分区不是这种情况。
当工作有一些等待状态时,应该使用块分区,每个元素往往需要更多的处理,或者每个元素可能有显着不同的工作处理时间。
这方面的一个例子可能是读入内存并处理 100 个大小截然不同的文件。 1K 文件的处理时间比 1mb 文件要少得多。如果为此使用范围分区,则某些线程可能会闲置一段时间,因为它们碰巧处理较小的文件。
与范围分区不同,无法指定每个任务要处理的元素数量 - 除非您编写自己的自定义分区程序。使用块分区的另一个缺点是,当它返回获取另一个块时可能会出现一些争用,因为此时使用了排他锁。因此,显然块分区不应该用于少量 CPU 密集型工作。
默认的块分区器以每个块 1 个元素的块大小开始。在每个线程处理三个 1 元素块之后,块大小增加到每个块 2 个元素。在每个线程处理了三个 2 元素块之后,块大小再次增加到每个块 3 个元素,依此类推。至少这是它的工作方式 Dixin Yan,(请参阅块分区部分)为 Microsoft 工作。
顺便说一下,他的博客中不错的可视化工具似乎是Concurrency Visualizer profile tool。 docs for this tool 声称它可用于定位性能瓶颈、CPU 利用率不足、线程争用、跨核线程迁移、同步延迟、DirectX 活动、重叠 I/O 区域和其他信息。它提供图形、表格和文本数据视图,显示应用程序中的线程与整个系统之间的关系。
其他资源:
【讨论】:
要对数据源进行并行操作,基本步骤之一是将源划分为多个部分,这些部分可以由多个线程同时访问。 PLINQ 和任务并行库 (TPL) 提供了默认分区器,当您编写并行查询或 ForEach 循环时,这些分区器可以透明地工作。对于更高级的场景,您可以插入自己的分区器。
阅读更多here:
【讨论】: