Haskell:并行列表计算

时间:2019-04-19 16:08:08

标签: haskell

我正在尝试跟踪该视频(https://www.youtube.com/watch?v=rlwSBNI9bXE&)中显示的倒计时问题的实现,我认为尝试并行运行将是一个好问题?

instance NFData Op where
  rnf Add = Add `deepseq` ()
  rnf Sub = Sub `deepseq` ()
  rnf Mul = Mul `deepseq` ()
  rnf Div = Div `deepseq` ()

instance NFData Expr where
  rnf (Val a)     = a `deepseq` ()
  rnf (App o l r) = (rnf  o) `deepseq` (rnf l) `deepseq` (rnf r)

solutions' :: [Int] -> Int -> [Expr]
solutions' ns n =
  ([e | ns' <- choices ns, (e, m) <- results ns', m == n]
    `using` parList rdeepseq)

我尝试着关注其他一些有关如何执行此操作的帖子,并提出了类似这样的内容

>λ= r = (solutions' [1,3,7,10,25,50] 765)
(0.00 secs, 0 bytes)
>λ= mapM_ print r
*** Exception: stack overflow
>λ=

它可以编译,但是当我尝试运行它时该程序崩溃。老实说,我真的只是在猜测我写的东西。

如何使它并行运行?

当我在GHCI中跑步

ghc ./Countdown.hs +RTS -N8 -s

如果我使用 .add

然后运行可执行文件,它不会终止。

1 个答案:

答案 0 :(得分:1)

好吧,所以我只是单击了视频中的随机时间戳记,很幸运,我得到了一张幻灯片,描述了什么地方出了问题。

  

在我们的示例中,3300万个可能表达式中只有大约500万个有效。

所以,这意味着您正在评估

_fiveMillionList `using` parList rdeepseq

现在,(`using` parList _strat)的工作方式是立即强制列表的整个书脊。当您开始计算表达式时,parList强制列表中的所有单元格都存在。此外,正如@DavidFletcher指出的那样,您的并行性实际上是没有用的。由于过滤条件位于using之下,因此强制列表的整个主干也将强制所有3,300万个Expr存在,因为您需要知道有多少元素通过了(==)测试,因此您需要创建Expr进行测试。它们不需要同时全部存在,而是最终有500万个(不包括它们中递归包含的Expr个)加上500万个{{1} }构造函数,将保留在内存中。为了加重侮辱性伤害,您将继续创建500万个呈暗火花形式的对象。而且,所有这些都由对(:) monad的Eval函数的500万次调用精心策划。我不确定其中哪一个确切地驻留在内存中足够长的时间而导致堆栈溢出,但是我很确定(>>=)是罪魁祸首。

也许尝试更合理的parList。我认为您几乎被迫使用Strategy,因为您需要懒惰。使用parBuffer,如果您评估一个parBuffer n strat单元格,那么该策略将确保触发下一个(:)元素。因此,从本质上讲,它“领先”于列表首位的所有使用者,并保持着并行评估元素的缓冲。像n - 1之类的东西就可以了。


您的parBuffer 1000 rdeepseq实例可能需要一些工作。它们不是问题,但它们并没有真正表现出对评估工作原理的深刻理解。我就把它们留在这里:

NFData