在F#树上并行化函数的选项

时间:2011-03-16 00:59:57

标签: f# functional-programming tree parallel-processing

在我的项目中,我的数据结构如下所示:

type 'a Tree =
| Leaf of 'a
| Node of 'a Tree array

由于遍历大树的成本,我必须在这个数据结构上并行化一些以下函数:

  • 地图
  • 折叠
  • 存在(存在满足a的节点) 谓词)
  • reduce / transform(可选)

由于我正在处理的问题的性质,每个节点的分支数量不同,叶级别的工作负载非常小。 第一个问题是我应该考虑在树上并行执行的选项。我试图在每个节点上使用来自Array.Parallel模块的函数,但是,因为并行性的开销太大,并行版本比顺序版本更慢。如果有必要,我可以将数组表示更改为ListPSeq

第二个问题是如何控制这些函数的并行度。我正在考虑通过树的深度控制,每个节点的分支数量,叶级别的工作负载复杂度和数量然而,树上的叶子组合在一起似乎是复杂和不可预测的。

2 个答案:

答案 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末所描述的那样。