【问题标题】:Shared variable in Haskell parMapHaskell parMap 中的共享变量
【发布时间】:2013-09-23 02:20:19
【问题描述】:

我有一个基本上执行以下操作的计算:

f :: [a] -> ([b],Bool)

这个函数其实可以写

f = foldr h ([],False) . map g
    where h (b,bool) (bs,boolSoFar) = (b:bs,bool || boolSoFar)

g :: a -> (b,Bool) 是一些需要花费大量时间的函数。 f 通常在小列表上调用,因此尝试并行计算地图似乎很有趣。这可以通过 Control.Parallel.Strategies parMap 来完成。所以现在我们使用

f = foldr h ([],False) . parMap rseq g
    where h (b,bool) (bs,boolSoFar) = (b:bs, bool || boolSoFar)

这一切都很好。现在,您会注意到可以在f 的第一个定义中执行顺序优化。即,我可以使用 map-fold fusion 将其编写为单个折叠,因此循环遍历列表。但是,这样我就失去了并行处理的好处。

现在,有人可能会说,在f 的第二个定义中,再次循环遍历列表并不是那么糟糕,那么为什么不直接这样做呢。我想我在想的是,如果 Haskell 有可变变量,那么可以在 map 的主体中更新这个布尔变量(我想你必须锁定和解锁它)。对做这样的事情有什么建议吗?

【问题讨论】:

  • 我非常怀疑使用可变变量来包含布尔值是否会使其更快。您是否对代码运行了分析器以查看减速发生的位置?当您的效率问题出在其他地方时,您可能过于专注于优化一些简单的事情。您确定 g 正在并行评估,还是并行创建 thunk 并让单核 foldr 进行评估?
  • 您好,感谢您的评论。实际上,如果这可能,我想知道更多。我一直在玩分析,什么不是。当然,大部分时间都花在地图上。我知道这一点,并在问题中说明。但是我已经看到这种模式出现在我的代码中不止一个地方,我并行执行一些操作,然后按顺序执行其他操作 - 顺序部分是附加到计算末尾的东西并且可以完成在并行部分有一个共享变量。所以我在问如何在 Haskell 中做到这一点。
  • 我刚刚写了一些相当幼稚的东西作为概念证明。使用 Chan 而不是 MVar,我将是第一个承认代码没有完全优化的人,但是 mapparMap 之间的差异可以忽略不计,它们和使用 Chans 之间的差异是一个数量级慢点。这是一个很好的例子,说明 GHC 如何在不需要引入复杂的线程机制的情况下自行很好地处理事情。
  • 如何判断我是在并行创建 thunk 并通过 foldr 正常评估还是实际并行评估?有没有一种方法可以让我分析并查看设置并行执行的开销是否否定了并行处理的好处?
  • 这里haskell.org/haskellwiki/Performance/Strictness 详细描述了几种技术,但一般要点是使用a) 模式匹配、b) 严格的$! 运算符和c) BangPatterns。就我个人而言,我喜欢使用$!,因为它简短而甜美,但并非在所有情况下都适用,因此 BangPatterns 也非常有用,例如 let !result = expr in result

标签: haskell parallel-processing


【解决方案1】:

这实际上是在惰性写入器Applicative 下的遍历,写入器状态为Bool,因为(False, (||)) 形成一个幺半群。您还需要unamb 包,因此您可以在g 的任何并行调用第一次返回True 时获取该值。

import Control.Parallel.Strategies
import Data.Unamb

newtype EvalWB a = EvalWB { runEvalWB :: Eval (a, Bool) }

instance Functor EvalWB where
  fmap f (EvalWB m) = EvalWB $ fmap (\ ~(a, b) -> (f a, b)) m

instance Applicative EvalWB where
  pure a = EvalWB $ pure (a, False)

  EvalWB mf <*> EvalWB ma = EvalWB $ (\ ~(f, bf) ~(a, ba) -> (f a, por bf ba)) <$> mf <*> ma

然后你有

f :: [a] -> ([b], Bool)
f l = runEval $ runEvalWB $ traverse (\a -> EvalWB $ rpar $ g a) l

这会并行传递整个列表,懒惰地累积值和标志。它使用por 在第一个True 返回时短路。

【讨论】:

    【解决方案2】:

    你不能使用 State Monad 吗?将函数 f 从:

    f :: [a] -> ([b], Bool)
    

    到:

    f :: [a] -> State Bool [b]
    

    您只需要通过列表的折叠来更新您的状态值,不是吗?不过,我不确定你是否可以将它与并行的东西一起应用。我对 Haskell 的了解有些有限。

    【讨论】:

    • 不,您不能并行执行此操作。
    • 我想一些细节可能是有序的:你不能并行执行,因为State 表示为s -&gt; (a, s),并且状态通过&gt;&gt;= 沿着计算传播,因此计算的每个部分都必须相对于计算的其他部分严格排序。 (其他State monad 的工作方式类似。)
    猜你喜欢
    • 1970-01-01
    • 2021-06-22
    • 1970-01-01
    • 1970-01-01
    • 2014-12-14
    • 2012-07-21
    • 2011-08-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多