Parallel processing 为什么我们不能通过PSeq获得性能改进? 问题:
我们认为并行处理每个“尾部排列”将减少该算法的实际运行时间。为什么不呢?我们如何配置PSeq以提高运行时间Parallel processing 为什么我们不能通过PSeq获得性能改进? 问题:,parallel-processing,f#,seq,plinq,parallelism-amdahl,Parallel Processing,F#,Seq,Plinq,Parallelism Amdahl,我们认为并行处理每个“尾部排列”将减少该算法的实际运行时间。为什么不呢?我们如何配置PSeq以提高运行时间 let rec permute (xs:'t list) = if xs.Length = 1 then [xs] else ([], permute xs.Tail) // Why do we not get a performance // improvement when we use PSeq here // re
let rec permute (xs:'t list) =
if xs.Length = 1 then [xs]
else ([], permute xs.Tail)
// Why do we not get a performance
// improvement when we use PSeq here
// relative to using Seq?
||> PSeq.fold (fun acc ps ->
(insertAtEach xs.Head ps)@acc)
permute ["A";"B";"C"] |> ignore
我们计算出排列工作将按如下方式进行拆分,并且算法的insertAtEach
阶段将并行工作
[C]
[BC] [CB]
[ABC] [BCA] [BCA] [ACB] [CAB] [CBA]
CPU01 CPU02
实际上,即使我们使用一个大的初始列表,例如permute[0..9]
,它的并行速度也会变慢。我们还没有弄清楚带degreeofparallelism的和相关PSeq选项是否有帮助
在一边
下面是代码列表的其余部分:
let put a q xs =
([], xs)
||> Seq.fold (fun acc x ->
if x = q then a::x::acc
else x::acc)
|> List.rev
// Insert x at each possible location in xs
let insertAtEach a xs =
([a::xs], xs)
||> Seq.fold (fun acc x ->
(put a x xs)::acc)
第一部分:“为什么不呢?”
因为从序列--
进程执行调度到PAR--| | | | |
的每一次转换(转换)都需要做一些努力,这证明了它们确实是有形的[时间]
-域和[空间]
-域设置+终止管理费用
有关更多详细信息,请查看与部分的对比:批评,以及随后衍生的法律重新制定。
没有任何方法可以避免支付税款和管理费用
在并行计算中,以及在仅并行计算环境中稍微不那么密集的情况下,在交换中支付比接收更容易
此外,您可能会发现大量的示例和惊喜,在StackOverflow上,使用搜索标签,您可以看到许多具有基准间接成本的示例(如果用户保持开放的思想,并且足够系统地运行它们,并记录和发布实际的[时间]
-域成本)
第二部分:“如果有的话,我们如何……提高运行时间?”
没有人能逃脱严格的,原子处理意识重新制定的阿姆达尔定律
没有人能
然而,如果在初始产品规划期间使用,该工具也会带来设计方面的优势
净结果处理开销受资源池大小、强制数据传输、(未)避免的共享和/或其他形式的相互依赖性、结果大小返回到{
并发|
并行}的影响
-处理终止--所有这些附加成本都可以系统地进行基准测试,以便更好地了解这样做的成本,包括内存分配成本、垃圾回收或类似实际操作方式的释放开销
在已知的部署生态系统中,没有人被禁止在进入纯串行/分布式/并行过程执行时使用基准数据进行系统成本效益评估
鉴于这些细粒度的知识已经过基准测试,
它可以作为解决困境的公平标准:
i、 e.--《代码》PAR--| | | | | |
部分的最小大小是多少,这样它至少可以支付自己无法避免的《代码》{setup+termination}-间接附加成本
如果没有做这两件事,人们只会感到惊讶,重新分解的过程是多么昂贵,与直接的“负”或比预期的处理性能改进更差的方式形成对比
始终:基准,基准,基准
Next:永远不要选择更昂贵的方式。
PSeq.fold
不会做你认为它会做的事PSeq.fold实际上根本不是平行的。它是连续的
你不能只把“平行”这个词放在算法的中间,希望最好。并行化不是这样工作的。你必须真正理解发生了什么,并行发生了什么,什么是顺序的,什么原则上可以并行,什么不能并行
以折叠
为例:它将提供的折叠功能应用于序列的每个元素和上一次调用的结果。由于每次下一次调用都必须有上一次调用的结果才能执行,因此很明显,fold
根本无法并行执行。必须是顺序的。事实上,如果你看一下它的。因此每次转换为并行序列
都会有一些开销,但没有任何收益
现在,如果您仔细查看您的算法,您可以梳理出可并行化的部分。您的算法的作用:
递归地计算尾部的排列
对于这些排列中的每一个,通过在每个索引处插入头部来将其分解
连接步骤2的所有结果
当你这样说的时候,很容易看出第二步实际上只依赖于它本身。每个尾部排列都是完全独立于所有其他排列进行处理的
当然,仅从源代码看,这一点并不明显,因为您已将步骤2和3组合在一个表达式中(insertatech xs.Head ps)@acc
,但使用以下通用标识很容易区分:
xs |> fold (fun a x -> g a (f x))
===
xs |> map f |> fold (fun a x -> g a x)
也就是说,您可以使用map
“提前”将函数f
应用于每个元素x
将此想法应用到您的算法中,您可以得到:
let rec permute (xs:'t list) =
if xs.Length = 1 then [xs]
else permute xs.Tail
|> Seq.map (insertAtEach xs.Head)
|> Seq.fold (fun acc ps -> ps@acc) []
现在很容易看出Seq.map
步骤是可并行的:map
将函数独立于其他元素应用于每个元素,因此原则上它可以并行工作。只需将Seq
替换为PSeq
即可获得并行版本:
let rec permute (xs:'t list) =
if xs.Length = 1 then [xs]
else permute xs.Tail
|> PSeq.map (insertAtEach xs.Head)
|> PSeq.fold (fun acc ps -> ps@acc) []
无论PSeq
是否真正执行pe