Parallel.For分区

时间:2013-07-22 10:19:23

标签: c# .net performance task-parallel-library

如何为

之类的内容完成分区
Parallel.For(0, buffer.Length, (i)=> buffer[i] = 0);

我的假设是,对于n核心计算机,工作将被分区n way,而n threads将执行工作负载。这意味着例如buffer.Length = 100 and n = 4, each thread will get 0-24, 25-49, 50-74, 75-99块。 (100个元素数组是一个说明分区的例子,但请考虑数百万个项目的数组。)

这是一个公平的假设吗?请讨论。

我注意到Array.Clear(...)在这种特定情况下的执行速度会快得多。你如何理顺这个?

2 个答案:

答案 0 :(得分:4)

首先是简单的部分。 100个元素的数组非常小,可以很容易地放入核心的缓存中。此外,清除阵列相当于将内存区域设置为0,这可以作为CPU命令使用,因此可以尽可能快地完成。

事实上,SSE命令和并行优化的内存控制器意味着芯片组可以使用单个CPU命令并行地清除内存。

另一方面,Parallel.For引入了一些开销。它必须对数据进行分区,创建适当的任务来处理它们,收集结果并返回最终结果。在Parallel.For之下,运行时必须将数据复制到每个核心,处理内存同步,收集结果等。在您的示例中,这可能比将内存位置归零所需的实际时间大得多。

事实上,对于小尺寸,很可能99.999%的开销是内存同步,因为每个内核都试图访问相同的内存页。请记住,内存锁定在页面级别,您可以在4K内存页面中容纳2K 16位整数。

至于PLINQ如何安排任务 - 根据您使用的运算符,使用了许多不同的分区方案。查看Partitioning in LINQ以获得精彩的介绍。在任何情况下,分区程序都会尝试确定是否可以从分区中获得任何好处,并且可能根本不对数据进行分区。

在您的情况下,分区程序可能会使用Ranged分区。您的有效负载仅使用几个CPU周期,因此您只看到分区,创建任务,管理同步和收集结果的开销。

更好的基准是在大型阵列上运行一些聚合,例如。计数和平均值等。

答案 1 :(得分:2)

PFX / PLINQ的优化很复杂。但是,这是基本图片......

输入端优化:

PLINQ有三种分区策略,用于为线程分配输入元素:

策略 元素分配 相对效果
块分区 动态 平均值
范围分区 静态 差到极
哈希分区 静态

对于需要比较元素(GroupByJoinGroupJoin等的查询运算符。)PLINQ总是选择相对低效的散列分区,因为它必须预先计算哈希码每个元素(以便具有相同代码的元素可以在同一个线程上运行)。

对于所有其他查询运算符,您可以选择范围或块分区。默认情况下,如果输入序列是可索引的(如果是,并且继承自IList<T>的数组)PLINQ将选择范围分区;否则它会选择块分区。

对于每个元素占用相似CPU时间的长序列,

范围分区更快。否则,块分区会更快。

他们的工作方式:

块分区的工作原理是让每个工作线程周期性地从输入序列中获取要处理的小“元素”元素。 PLINQ首先分配非常小的块,然后随着查询的进行增加这个数量;这确保了小序列被有效地并行化,并且大序列不会过度“往返”。如果一个工作线程很快完成它的工作,它将最终得到更多的块。该系统使每个线程保持同样繁忙,并使机器的核心“平衡”。这种方法的缺点是从共享输入序列中获取元素需要锁定,这可能会增加开销。

范围分区绕过正常的输入端枚举,并为每个工作线程预先分配相同数量的元素,从而避免对输入序列的争用。如果线程使用此方法提前完成,它将处于空闲状态,直到其他线程完成。

Parralell ForForeach

默认情况下,对于For / Foreach个循环,PLINQ将使用范围分区。

我希望这会有所帮助。