在我的项目中,我的数据结构如下所示:
type 'a Tree =
| Leaf of 'a
| Node of 'a Tree array
由于遍历大树的成本,我必须在这个数据结构上并行化一些以下函数:
由于我正在处理的问题的性质,每个节点的分支数量不同,叶级别的工作负载非常小。 第一个问题是我应该考虑在树上并行执行的选项。我试图在每个节点上使用来自Array.Parallel
模块的函数,但是,因为并行性的开销太大,并行版本比顺序版本更慢。如果有必要,我可以将数组表示更改为List
或PSeq
。
第二个问题是如何控制这些函数的并行度。我正在考虑通过树的深度控制,每个节点的分支数量,叶级别的工作负载复杂度和数量然而,树上的叶子组合在一起似乎是复杂和不可预测的。
答案 0 :(得分:4)
如何将遍历与任何其他处理分开?也许创建一个工作队列(MailboxProcessor
是一个很好的起点),并且,当遍历树时,为后台处理排队其他工作。它并没有解决并行遍历问题(这对于所有情况来说都很难解决)但是如果将其他处理降级到后台,那么它应该会非常快。您可以尝试使用后台工作程序的数量,直到找到良好的并行度。这一切都假定每个节点要完成的工作量是非常重要的。
这是一些代码。我相信它可以改进。我不得不很快地把它锤出来。但这显示了基本概念。它只有一个“后台工作者”,即MailboxProcessor
。我将更新它以使用多个工作者来实现想象力。
type Msg<'a, 'b> =
| Work of 'a
| Done of 'b
type MapTransformer(f) =
let results = ResizeArray()
let m = MailboxProcessor.Start(fun payload ->
let rec loop() =
async {
let! msg = payload.Receive()
match msg with
| Work work ->
results.Add(f work)
return! loop()
| Done (channel : AsyncReplyChannel<_>) ->
channel.Reply(results :> seq<_>)
}
loop())
member this.Enqueue(item) = m.Post(Work item)
member this.Results = m.PostAndReply(fun c -> Done c)
let uberMap tree =
let m = MapTransformer(fun x -> x + 1)
tree |> List.iter (fun x -> m.Enqueue(x))
m.Results
uberMap [1; 2; 3]
//outputs [2; 3; 4]
答案 1 :(得分:3)
Array.Parallel
使用System.Threading.Parallel.For
。当您调用该函数时,它会尝试为给定任务找到最佳计划。但是,使用典型的递归树算法,这意味着对Parallel.For
的大量调用,并且您可能最终得到的线程太多。 (除非Parallel.For
针对我不知道的用例进行了优化。)
所以我认为如果每个节点的工作量不是太小,Daniel的建议是个好主意。另一个想法是引入一个关于树的剩余深度的阈值,如Stephen Toub在this blog entry末所描述的那样。