我偶然发现了Haskell中Eval
monad和rpar
Strategy
的问题。请考虑以下代码:
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)
但仍然没有结果。我做错了什么?
答案 0 :(得分:11)
rpar
策略并行注释一个术语以进行可能的评估,但仅限于弱头正常形式,这实际上意味着直到最外层的构造函数。因此,对于整数或双精度,这意味着完全评估,但对于一对,只会对配对构造函数而不是其组件进行评估。
在将对传递给rpar
之前强制该对没有帮助。现在,您在对已经评估的元组进行注释以进行可能的并行评估之前,在本地评估该对。
您可能希望将rpar
与rdeepseq
策略结合使用,从而声明应该完全评估该术语,如果可能的话。你可以这样说
(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)
或者使用fst
和snd
访问该对的组件。
最后,如果您创建的火花与向整数添加一个火花一样便宜,请不要指望实际加速。虽然火花本身相对便宜,但它们并不是免费的,所以如果你对并行评估进行注释的计算成本有些高,它们的效果会更好。
您可能希望阅读一些使用策略的教程,例如Simon Marlow Parallel and Concurrent Programming using Haskell或我自己的Deterministic Parallel Programming in Haskell。