我们认为并行处理每个“尾部置换”会减少该算法的实际运行时间。为什么不呢?如何配置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
// 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]
这样的大型初始列表,它实际上并行较慢。我们还没有弄清楚withDegreeOfParallelism
和相关的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)
答案 0 :(得分:4)
PSeq.fold
没有按照您的想法行事。 PSeq.fold
实际上根本不是平行的。这是顺序的。
你不能在算法中间的某处抛出“并行”这个词,并希望最好。这不是并行化的工作方式。你必须真正理解发生了什么,并行发生了什么,顺序发生了什么,原则上可以并行化什么,什么不可以。
以fold
为例:它将提供的折叠函数应用于序列的每个元素和上一次调用的结果。由于每次下一次调用必须具有前一次调用的结果才能执行,因此很明显fold
根本不能并行执行。必须顺序。事实上,如果你看一下source code,那就是PSeq.fold
实际做的事情。因此,每次转换为ParallelSequence
都会产生一些开销,但没有收获。
现在,如果仔细查看算法,可以梳理出可并行化的部分。你的算法做了什么:
当你这样说时,很容易看出第2步并不真正依赖于它本身。每个尾部排列都与其他尾部排列完全分开处理。
当然,仅通过查看源代码就不明显了,因为您已将步骤2和3合并到一个表达式(insertAtEach xs.Head ps)@acc
中,但使用以下一般标识可以轻松区分:< / p>
xs |> fold (fun a x -> g a (f x))
===
xs |> map f |> fold (fun a x -> g a x)
也就是说,您可以在f
期间将功能x
应用于每个元素fold
,而不是使用map
将其“提前”应用于每个元素。< / p>
将此想法应用于您的算法,您将得到:
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
是否确实并行执行map
是另一回事,但这应该可以通过经验轻松验证。
事实上,在我的机器上,并行版本始终优于7到10个元素列表的顺序版本(11个元素列表导致OOM)。
P.S。请记住,在测量时间时,您需要先强制生成的序列(例如,将其转换为列表或取Seq.last
)。否则,您所测量的只是并行化开销成本。
答案 1 :(得分:2)
因为从 SEQ -- -- --
流程执行计划到 PAR -- || ||
的每次转换(转换)都需要付出一些努力才能完成确实明显具体化[TIME]
- 域和[SPACE]
- 域设置+终止费用。
有关详细信息,请查看re-formulated Amdahl's Law, particularly the naive-form Fig.1与部分:批评,然后推出限制性重新制定法律。
没有办法避免因支付税款和间接成本......
在并行计算中,在并发计算环境中并不那么密集,在交换中确实非常容易付款而不是接收。
另外,人们可能会在StackOverflow上找到确实众多示例和惊喜,使用搜索标记parallelism-amdahl,您可以看到许多带有基准开销成本的示例(如果用户仍然存在)思想开放,系统性足以确实能够运行并记录和发布现实的[TIME]
- 域费用。
没有人可以逃脱严格的,原子处理意识重新制定的阿姆达尔定律。
没人能。
然而,如果在初始产品规划期间使用,该工具还具有设计方面的优势。
净结果处理开销受资源池大小,强制数据传输,(非)避免共享和/或其他形式的相互依赖性影响,结果'大小返回{
并发|
并行}
- 处理终止 - 所有这些附加成本都可以系统地进行基准测试,以便很好地理解这样做的成本,直到内存分配成本,垃圾收集或类似的现实运作方式解除分配费用。
鉴于已知的部署生态系统,在进入纯串行/分布式/并行流程执行之前,不允许任何人使用基准数据进行系统成本效益评估。
鉴于这种细粒度的知识是基准的,
它可以作为解决困境的公平指标:
ie - PAR -- ||||
部分的最小尺寸是多少,以便它至少支付其自身不可避免的{ setup + termination }
- 开销 - 附加费用?
没有完成其中任何一项,人们会感到惊讶,重新分解的过程有多昂贵,与直接“负面”或处理性能差于预期的方式形成鲜明对比改善。
始终:基准,基准,基准。
下一步:根本不要选择更昂贵的方式。