【发布时间】:2019-07-29 03:06:40
【问题描述】:
我正在开发一个用于进行信念传播立体视觉的练习程序。这里的相关方面是我有一个相当长的数组表示图像中的每个像素,并且希望在 for 循环的每次迭代中对数组中的每个 second 条目执行操作 -前一半的条目,然后在下一次迭代中的另一半(这来自 Felzenswalb 和 Huttenlocher 在他们 2006 年的论文“早期视觉的有效信念传播”中描述的优化。)因此,您可以将其视为具有运行多次的外部 for 循环,对于该循环的每次迭代,我都会迭代数组中一半的条目。
我想像这样并行化遍历数组的操作,因为我相信这样做是线程安全的,当然可能更快。该操作涉及更新表示相邻像素的数据结构内的值,这些像素本身并不用于外部循环的给定迭代。最初我只是一次性遍历整个数组,这意味着执行此操作相当简单——我需要做的就是将.Parallel 放在Array 和.iteri 之间。但是,更改为对第二个数组条目进行操作比较棘手。
为了从简单地遍历每个条目进行更改,我从 Array.iteri (fun i p -> ... 到使用 for i in startIndex..2..(ArrayLength - 1) do,其中 startIndex 是 1 或 0,具体取决于我最后使用的那个(通过切换布尔值来控制)。这意味着虽然我不能简单地使用非常好的.Parallel 来让事情并行运行。
我无法找到任何关于如何在 .NET 中实现步长大于 1 的并行 for 循环的具体信息。我能找到的最好的是 a paragraph in an old MSDN document on parallel programming in .NET,但那段只是含糊其辞关于在循环体内转换索引的声明。我不明白那里的意思。
我查看了Parallel.For 和Parallel.ForEach,以及creating a custom partitioner,但似乎没有一个包含更改步长的选项。
我想到的另一个选择是使用序列表达式,例如
let getOddOrEvenArrayEntries myarray oddOrEven =
seq {
let startingIndex =
if oddOrEven then
1
else
0
for i in startingIndex..2..(Array.length myarray- 1) do
yield (i, myarray.[i])
}
然后使用来自ParallelSeq 的PSeq.iteri,但我不确定它是否能与.NET Core 2.2 一起正常工作。 (请注意,目前至少,我需要知道数组中给定元素的索引,因为它在处理过程中用作另一个数组的索引)。
如何并行迭代数组的每个第二个元素? IE。使用大于 1 的步长迭代数组?
【问题讨论】:
-
如果不进行测量,通常几乎不可能知道一种解决方案是否比另一种解决方案更高效。 (当然,除非一个是 O(N),另一个是 O(N^2),但我说的是相同大 O 量级的解决方案)。我怀疑任何人都能够给你一个好的答案,除非他们已经遇到了你的确切情况并进行了基准测试。换句话说,github.com/dotnet/BenchmarkDotNet 可能是您获得答案的方式。
-
@Jarak 因为你只关心 even 个像素,你可以在
Parallel.For中从 0 迭代到array.Length/2。另一种选择是使用 PLINQ 的 Where 重载,它接受、索引和过滤掉奇数位置。这样你也可以并行搜索 -
@Jarak 至于更改步长,将索引从
0增加到Nstep与从0迭代到N/step并将索引乘以相同step使用前 -
@Jarak 确保你 do 对你的代码进行基准测试。访问每一个第二个像素最终会将所有数据加载到 CPU 的缓存中。您仍将只处理一半像素,但不会获得预期的 4x/8x 改进。另一方面,如果您根据索引(奇数/偶数)在循环内执行不同的操作,您将只加载一次数据
-
我还鼓励测量性能并将并行性能与简单但高效的顺序循环进行比较。有时抽象的开销是如此之大,以至于顺序循环结束得更快。
标签: .net-core f# parallel.foreach parallel.for