2

如果我有一个函数进行四次非常长的计算并返回一个包含四次计算结果的列表,但是每个计算不依赖于另一个,你如何在 Haskell 中“并行化”它?

为了更好地解释我的问题,这是我想到的 Clojure 示例:

(defn some-function [arg1 arg2 arg3 arg4]
  let [c1 (very-long-computation arg1)
       c2 (very-long-computation arg2)
       c3 (very-long-computation arg3)
       c4 (very-long-computation arg4)]
    [c1 c2 c3 c4])

您可以生成三个额外的线程,例如:

(defn some-function [arg1 arg2 arg3 arg4]
  let [c1 (future (very-long-computation arg1))
       c2 (future (very-long-computation arg2))
       c3 (future (very-long-computation arg3))
       c4 (very-long-computation arg4)] ; no need to wrap c4 in a future
    [@c1 @c2 @c3 c4])

类似于 Haskell 中的内容是否是等价的?

someFunction :: (a -> a -> a ->a) -> [a]
  do c1 <- rpar (very-long-computation arg1)
     c2 <- rpar (very-long-computation arg2)
     c3 <- rpar (very-long-computation arg3)
     c4 <- (very-long-computation arg4)
     rseq c1
     rseq c2
     rseq c3
     return (c1, c2, c3, c4)

我需要 rpar/rseq c4 吗?

rpar/rseq 是进行这种并发计算的方式吗?

如果我不 rseq,稍后当我尝试访问返回的列表中的返回值时,程序会等待吗?

这是透明的,还是您需要在使用“@”时执行 Clojure 中发生的“deref”之类的操作?

4

2 回答 2

4

您很可能正在寻找async包裹。例如,如果您想对这三个计算进行竞赛并采用最先完成的计算:

someFunction :: IO a
someFunction = do
    c1 <- async $ veryLongComputation1
    c2 <- async $ veryLongComputation2
    c3 <- async $ veryLongComputation3
    (_, a) <- waitAny $ [c1, c2, c3]
    return a

或者您可以wait在特定async线程上使用,并通过stm. 对于这类事情,这是一个非常有用的软件包。您在 OP 中要求的精​​确版本如下所示:

someFunction :: IO (a, b, c, d)
someFunction = do
    c1 <- async $ veryLongComputation1
    c2 <- async $ veryLongComputation2
    c3 <- async $ veryLongComputation3
    v4 <- veryLongComputation4
    -- wait for all the results and return them as a tuple
    wait $ (,,,) <$> c1 <*> c2 <*> c3 <*> (return v4)

这当然假设c1, c2, c3所有这些都是副作用并且您对结果不感兴趣。waitpoll为您提供价值。

我还强烈推荐 Simon Marlow 的《Haskell 中的并行和并发编程》一书。

于 2014-04-29T01:32:15.817 回答
1

我强烈推荐MonadPar。我以前使用过 Strategies,但您仍然必须知道“魔术”命令才能让所有内容并行评估。根据我的经验,MonadPar 可以正常工作。

这是一个简单的例子:

import Control.Monad.Par
import Control.Monad

foo :: [a] -> a
foo [x] = x
foo xs = 
   let len = length xs
       x1 = take len xs
       x2 = drop len xs
   in runPar $ do
     p1 <- spawnP $ foo x1
     p2 <- spawnP $ foo x2
     liftM2 (*) (get p1) $ get p2

当然,这确实需要足够的并行性才能真正受益。根据我的经验,Haskell 的并行开销相当高。

于 2014-04-29T01:27:32.713 回答