并行最大计算

时间:2013-11-06 18:04:39

标签: haskell parallel-processing

我有一系列问题需要并行评估。使用与此非常相似的简单表达式表示这些问题:

-- Expressions are either a constant value or two expressions
-- combined using a certain operation
data Expr 
    = Const NumType
    | Binary BinOp Expr Expr

-- The possible operations
data BinOp = Add | Sub | Mul | Div
    deriving (Eq)

这些表达式是动态构建的,应该评估某个可能有效或无效的结果。这表示为在遇到无效结果时停止计算的monad。

data Result a
    = Val { val :: a }
    | Exc { exc :: String }

instance Monad Result where
    return = Val
    (Exc e) >>= _ = (Exc e)
    (Val v) >>= g = g v

为了确定每个已解决问题的值,我有两个相关的功能:

eval :: Expr -> Result NumType
score :: Expr -> NumType

最后我解决了将返回[Expr]的函数。这导致我的主要功能目前看起来像这样:

main :: IO ()
main = do
    strAvailableNumbers <- getLine
    strTargetNumber <- getLine
    let numbers = parseList strAvailableNumbers 
        target = parseTargetNumber strTargetNumber in
            sequence $ map (print) $ 
                solveHeuristic1 (Problem target numbers) [Add] [Sub] ++
                solveHeuristic2 (Problem target numbers) 

    return ()

基本思想是我从stdin中读取数字列表和目标数字,然后在stdout上打印表达式。

但我有两个问题需要解决,我不太确定它们有多相关:

  • 这些启发式方法完全没有意识到彼此,因此不知道他们的解决方案的score是否高于其他解决方案。我想在map函数中引入某种状态,只有在其得分高于先前打印的Expr时才打印新Expr

  • 我想并行执行这些计算,并尝试使用(parMap rseq)代替map,使用-threaded选项进行编译并使用{{{}运行它1}}。结果是运行时间从5秒增加到7秒。不是我的预期,尽管+RTS -N2显示CPU利用率更高。我想我没有正确使用time或使用parMap做错事。那么我如何运行一个独立函数列表,每个函数并行返回一个元素列表?

更新:创建了gist with complete source code

1 个答案:

答案 0 :(得分:3)

此处的问题是,使用IO评估seq操作几乎不起作用。所以你只是按顺序运行,而且开销略有增加。

你可以折射东西以使它们再次变得纯净

main :: IO ()
main = do
    mapM_ (`seq` print "found it") -- make sure we're not 
                                   -- benchmarking printing stuff
          . concat
          . parMap rdeepseq (solve [1..10000000])
          $ [42, 42]

    return ()

添加NFData的实例以使用rdeepseq来完全评估内容

instance NFData BinOp -- Binop is just an enum, WHNF = NF

instance NFData Expr where
  rnf (Const a) = a `deepseq` ()
  rnf (Binary b e1 e2) = b `deepseq` e1 `deepseq` e2 `deepseq` ()

现在,如果我们运行它,我们得到一个stackoverflow。我提高了足够大的尺寸以便我们进行搜索,以便实际上花费足够长的时间来进行基准测试,现在将两个结构完全加载到内存中会使堆栈爆炸。将堆栈大小提升到我们不会破坏所有内容的程度使得我们使用-N2比使用{4}更快(3 vs 5秒)。我会考虑预期的结果。在运行时,我可以看到2个核心短暂地跳到100%。

最终编译序列

> ghc -O2 -threaded -rtsops bench.hs
> ./bench +RTS -K10000000 -N2