6

我偶然发现了Evalmonad 和rpar StrategyHaskell 的问题。考虑以下代码:

module Main where

import Control.Parallel.Strategies

main :: IO ()
main = print . sum . inParallel2 $ [1..10000]

inParallel :: [Double] -> [Double]
inParallel xss = runEval . go $ xss
    where
      go []  = return []
      go (x:xs) = do
        x'  <- rpar $ x + 1
        xs' <- go xs
        return (x':xs')

inParallel2 :: [Double] -> [Double]
inParallel2 xss = runEval . go $ xss
    where
      go []  = return []
      go [x] = return $ [x + 1]
      go (x:y:xs) = do
        (x',y') <- rpar $ (x + 1, y + 1)
        xs'     <- go xs
        return (x':y':xs'

我像这样编译并运行它:

ghc -O2 -Wall -threaded -rtsopts -fforce-recomp -eventlog eval.hs
./eval +RTS -N3 -ls -s

当我使用inParallel函数并行性按预期工作时。在输出运行时统计信息中,我看到:

SPARKS: 100000 (100000 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)

当我切换到inParallel2功能时,所有并行性都消失了:

SPARKS: 0 (0 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)

为什么元组的评估不能并行工作?在将元组传递给 rpar 之前,我尝试强制执行该元组:

rpar $!! (x + 1, y + 1)

但仍然没有结果。我究竟做错了什么?

4

1 回答 1

11

rpar策略注释了一个可能并行计算的术语,但仅限于弱头范式,这基本上意味着,直到最外层的构造函数。所以对于一个整数或双精度数,这意味着完整的评估,但对于一对,只有对构造函数,而不是它的组件,将被评估。

在传递给之前强迫这对rpar是没有帮助的。现在,您正在本地评估该对,然后再注释已评估的元组以进行可能的并行评估。

您可能希望将rparrdeepseq策略结合起来,从而声明应该完全评估该术语,如果可能的话并行。你可以这样说

(rpar `dot` rdeepseq) (x + 1, y + 1)

dot运算符用于组合策略。

但是,您的代码还有另一个问题:模式匹配强制立即评估,因此对rpar带注释的表达式使用模式匹配通常是个坏主意。特别是,线

(x',y') <- (rpar `dot` rdeepseq) (x + 1, y + 1)

将破坏所有并行性,因为在另一个线程可以拾取火花进行评估之前,本地线程已经开始评估它以匹配模式。您可以通过使用惰性/无可辩驳的模式来防止这种情况:

~(x',y') <- (rpar `dot` rdeepseq) (x + 1, y + 1)

或者使用fstsnd访问该对的组件。

最后,如果您创建的火花与将整数加一一样便宜,请不要指望实际的加速。虽然火花本身相对便宜,但它们并不是免费的,因此如果您为并行评估注释的计算有点昂贵,它们会更好地工作。

您可能想阅读一些关于使用策略的教程,例如 Simon Marlow 的 Parallel and Concurrent Programming using Haskell或我自己的Deterministic Parallel Programming in Haskell

于 2012-11-12T09:46:00.783 回答