1

分区是如何完成的

Parallel.For(0, buffer.Length, (i)=> buffer[i] = 0);

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

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

我注意到Array.Clear(...)在这种特定情况下会执行得更快。你如何合理化这一点?

4

2 回答 2

4

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

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

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

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

至于 PLINQ 如何调度任务 - 使用了许多不同的分区方案,具体取决于您使用的运算符。检查LINQ 中的分区以获得很好的介绍。在任何情况下,分区器都会尝试确定是否可以从分区中获得任何好处,并且可能根本不会对数据进行分区。

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

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

于 2013-07-22T10:57:12.810 回答
3

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

输入端优化:

PLINQ 具有三种用于将输入元素分配给线程的分区策略:

Strategy                    Element allocationRelative performance
Chunk partitioning         Dynamic                Average      
Range partitioning         Static                    Poor to excellent      
Hash partitioning           Static                    Poor      

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

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

对于每个元素占用相似 CPU 时间的长序列,范围分区更快。否则,块分区会更快。

它们是如何工作的:

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

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

平行ForForeach:

默认情况下,for For/ Foreachloops PLINQ 将使用范围分区。

我希望这有帮助。

于 2013-07-22T11:19:47.983 回答