Haskell 并行树搜索

Haskell 并行树搜索,haskell,parallel-processing,Haskell,Parallel Processing,假设我有一棵懒惰的树,它的叶子是问题的可能解决方案 data Tree a = Node [Tree a] | Leaf (Maybe a) 我只需要找到一个解决方案(或者发现根本没有) 我有一台p-core机器。从时间和内存效率两方面考虑,只需沿P个不同的分支并行搜索 例如,假设您有四个计算复杂度大致相同的分支(对应于CPU时间的T秒),每个分支都有一个答案 如果您在双核机器上真正并行地评估所有四个分支,那么它们都将在大约2T秒内完成 如果只计算前两个分支,而推迟其他两个分支,那么只需T秒就

假设我有一棵懒惰的
,它的叶子是问题的可能解决方案

data Tree a = Node [Tree a] | Leaf (Maybe a)
我只需要找到一个解决方案(或者发现根本没有)

我有一台p-core机器。从时间和内存效率两方面考虑,只需沿P个不同的分支并行搜索

例如,假设您有四个计算复杂度大致相同的分支(对应于CPU时间的T秒),每个分支都有一个答案

如果您在双核机器上真正并行地评估所有四个分支,那么它们都将在大约2T秒内完成

如果只计算前两个分支,而推迟其他两个分支,那么只需T秒就可以得到答案,同时占用的内存也会减少两倍


我的问题是,是否可以使用任何并行Haskell基础设施(Par monad,并行策略,…)来实现这一点,或者我必须使用诸如async之类的低级工具?

至少
Par
monad和
parallel
包中的策略只允许构建纯的、无条件的并行系统,这些照片看起来很漂亮:

a / \ b c \ /\ d e \ ...
另外,我觉得我对这个问题的理解并不比你们强(至少是这样),所以你们可能已经知道了上面的所有内容。我写了这个答案来发展讨论,因为这个问题对我来说很有意思。

两个策略和Par Maad只会在一个CPU可用时开始评估一个新的并行任务,所以在你的例子中,2个核心机上有四个分支,只有两个将被评估。此外,一旦你有了答案,战略将取代其他任务(尽管这可能需要一段时间)

但是,如果这两个分支中的每一个都创建了更多的任务,那么您可能希望优先考虑较新的任务(即深度优先),但策略至少会优先考虑较旧的任务。我认为Par Maad优先于更新的(但我必须检查),但是Par Mad将在返回答案之前评估所有的任务,因为这就是强制决定论的方式。
<> P>这可能是目前唯一能让你工作的方法,就是为PAR MUNAD编写一个自定义调度器。

你看了吗?我知道,但我不知道它如何解决我的问题。嵌套数据并行性是树结构的理想选择。因此DPH将自动处理将嵌套结构转换为可自动矢量化的平面结构的问题,因此您不必担心手动优化它。缺点是,您必须更改代码才能使用它。如果您的问题只是递归地爬下树访问所有的叶子,并且每一步的成本非常小,那么分叉的开销可能会主导您的计算,导致性能的净损失。OTOH,如果你有N个核,并且树分布很好,你可以把树的顶部分成N个部分。如果我要写一个IO解决方案,实际上会有点不同。我只需要设置一个信号量(比如,以
TVar
的形式),然后等待一个“许可令牌”开始探索一个新分支。我甚至不需要并行策略——只需要普通的forkIO/async。原因是树是惰性生成的,将其拆分为块已经意味着不必要的评估。另外,在您的解决方案中,我需要做出我不需要做的假设(在计算上,一切都大致相等;哪个块大小足够)。相反,我可以一边走一边安排评估。。。。但这个问题的真正意义在于,是否有可能完全在并行Haskell框架内完成这项工作,而不必求助于显式并发的东西。@RomanCheplyaka我以为你有相当小的“昂贵”的叶子树。否则我同意。
solve :: Tree a -> Maybe a

smartPartition :: Tree a -> Int -> [[Tree a]]
smartPartition tree P = ... -- split the tree in fairly even chunks,
                            -- one per each machine core

solveP :: Tree a -> IO (Maybe a)
solveP tree = do
    resRef <- newIORef Nothing
    results <- parallel (map work (smartPartition tree))
    return (msum results)
  where work [] = return Nothing
        work (t:ts) = do
            res <- readIORef resRef
            if (isJust res) then (return res) else do
                let tRes = solve t
                if (isNothing tRes) then (work ts) else do
                    writeIORef tRes
                    return tRes
partitionLeafs :: Tree a -> Int -> [[Tree a]]

solveP :: Tree a -> Maybe a
solveP = msum . map step . transpose . partitionLeafs
  where step = msum . parMap rdeepseq solve