【问题标题】:When to use Partitioner class?何时使用 Partitioner 类?
【发布时间】:2010-10-27 09:44:51
【问题描述】:

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

【问题讨论】:

    标签: c# .net .net-4.0


    【解决方案1】:

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

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

    【讨论】:

    • Partitioner.Create() 的默认 rangeSize 为 1。因此,两个代码示例的分区相同。除非 Partitioner.Create(0, Input.Length, i);如果 i > 1,它仍然有相同数量的线程。
    • @Pingpong,这似乎不正确,至少目前是这样。我发现默认分区器在我的 8 处理器机器上创建了 24 个块。这比输入的长度要小得多。我了解它是如何确定这个默认值的。
    • Brian,你能粗略估计一下“很多”是什么意思吗? 1000 个任务有问题吗?还是 1000 可以,但 1.000.000 值得使用分区器?
    【解决方案2】:

    范围分区,正如 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 约瑟夫·阿尔巴哈里

    【讨论】:

      【解决方案3】:

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

      阅读更多here:

      【讨论】:

      • 我要补充一点:一般来说不要使用它。 PLINQ 可以,而且您可能会摆脱默认的分区程序。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-18
      • 1970-01-01
      • 1970-01-01
      • 2017-09-09
      • 2023-03-27
      • 1970-01-01
      相关资源
      最近更新 更多