有人可以提供使用.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;
}
});
将每次调用的操作对象增大,可以减少调用的次数。在我个人的经验中,当并行化非常简单的操作时,这可以显著提高性能。
范围分区是一种建议在工作量需要大量CPU计算,相对于虚拟方法调用来说较小,需要处理许多元素且每个元素的运行时间大致相同的情况下使用的一种分区类型,由Brian Rasmussen提出。
应该考虑另一种分区类型——块分区。这种类型的分区也被称为负载平衡算法,因为当还有更多的工作要做时,工作线程很少会空闲——而这不适用于范围分区。
当工作存在某些等待状态、每个元素需要更多的处理或每个元素的工作处理时间明显不同时,应该使用块分区。
这种情况的一个例子可能是读取并处理大小差异巨大的100个文件。1K文件的处理时间要比1MB文件短得多。如果使用范围分区,则一些线程可能会因为它们恰好处理了较小的文件而空闲一段时间。
与范围分区不同,除非您编写自己的自定义分区器,否则无法指定每个任务要处理的元素数量。使用块分区的另一个缺点是,在返回获取另一个块时可能会存在一些争用,因为此时使用了独占锁。因此,显然,块分区不应用于短时间的CPU密集型工作。顺便提一下,他博客中的漂亮的可视化工具似乎是并发可视化分析器。该工具的文档声称它可以用于定位性能瓶颈、CPU未充分利用、线程争用、跨核心线程迁移、同步延迟、DirectX活动、重叠I/O区域以及其他信息。它提供图形、表格和文本数据视图,显示应用程序中线程与整个系统之间的关系。
其他资源:
第5部分:并行编程-优化PLINQ by Joseph Albahari