如何利用haskell并行代码中的任何并行性?

如何利用haskell并行代码中的任何并行性?,haskell,parallel-processing,multicore,Haskell,Parallel Processing,Multicore,我刚才提到了使用GHC6.12在haskell半显式并行中工作。我已经编写了下面的haskell代码来并行计算fibonnaci函数在列表中4个元素上的映射,同时计算函数sumEuler在两个元素上的映射 import Control.Parallel import Control.Parallel.Strategies fib :: Int -> Int fib 0 = 0 fib 1 = 1 fib n = fib (n-1) + fib (n-2) mkList :: Int -

我刚才提到了使用GHC6.12在haskell半显式并行中工作。我已经编写了下面的haskell代码来并行计算fibonnaci函数在列表中4个元素上的映射,同时计算函数sumEuler在两个元素上的映射

import Control.Parallel
import Control.Parallel.Strategies

fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

mkList :: Int -> [Int]
mkList n = [1..n-1]

relprime :: Int -> Int -> Bool
relprime x y = gcd x y == 1

euler :: Int -> Int
euler n = length (filter (relprime n) (mkList n))

sumEuler :: Int -> Int
sumEuler = sum . (map euler) . mkList

-- parallel initiation of list walk                                                                                                                                    
mapFib :: [Int]
mapFib = map fib [37, 38, 39, 40]

mapEuler :: [Int]
mapEuler = map sumEuler [7600, 7600]

parMapFibEuler :: Int
parMapFibEuler = (forceList mapFib) `par` (forceList mapEuler `pseq` (sum mapFib + sum mapEuler))

-- how to evaluate in whnf form by forcing                                                                                                                                
forceList :: [a] -> ()
forceList [] = ()
forceList (x:xs) = x `pseq` (forceList xs)


main = do putStrLn (" sum : " ++ show parMapFibEuler)

并行地改进我的程序,我用PAR和PSEQ重写它,并用强制函数来强迫WHNF评估。我的问题是,通过查看threadscope,我似乎没有获得任何并行性。情况更糟,因为我没有获得任何加速

这就是为什么我有这两个问题

问题1如何修改代码以利用任何并行性

问题2如何编写程序以使用策略(parMap、parList、rdeepseq等

策略的首次改进

根据他的贡献

parMapFibEuler = (mapFib, mapEuler) `using` s `seq` (sum mapFib + sum mapEuler) where
    s = parTuple2 (seqList rseq) (seqList rseq)
并行性出现在threadscope中,但不足以显著加速

嗯。。。也许吧

((forceList mapFib) `par` (forceList mapEuler)) `pseq` (sum mapFib + sum mapEuler)
也就是说,在后台生成
mapFib
,并计算
mapEuler
,然后(
mapEuler
)进行
(+)
。 事实上,我想你可以这样做:

parMapFibEuler = a `par` b `pseq` (a+b) where
     a = sum mapFib
     b = sum mapEuler
关于第二季度: 据我所知,策略是将数据结构与
par
seq
相结合的“策略”
您可以编写您的
forceList=with策略(seqList-rseq)

您还可以编写如下代码:

parMapFibEuler = (mapFib, mapEuler) `using` s `seq` (sum mapFib + sum mapEuler) where
    s = parTuple2 (seqList rseq) (seqList rseq)

也就是说,应用于两个列表的元组的策略将强制并行计算它们的值,但每个列表将强制按顺序计算。

首先,我假设您知道您的
fib
定义很糟糕,您这样做只是为了玩并行包

您似乎在错误的级别上追求并行性。并行化
mapFib
mapEuler
不会提供很好的速度,因为需要计算
mapFib
。您应该做的是并行计算这些非常昂贵的元素中的每一个,这些元素的粒度稍细,但不会太细:

mapFib :: [Int]
mapFib = parMap rdeepseq fib [37, 38, 39, 40]

mapEuler :: [Int]
mapEuler = parMap  rdeepseq sumEuler [7600, 7600, 7600,7600]

parMapFibEuler :: Int
parMapFibEuler = sum a + sum b
  where
  a = mapFib
  b = mapEuler
另外,我最初是使用Control.Parallel.Strategies而不是Control.Parallel进行斗争的,但我开始喜欢它,因为它更具可读性,并且避免了类似于您的问题,因为人们会期望并行性,并且不得不眯着眼睛看它,以找出您没有得到任何并行性的原因

最后,您应该始终发布如何编译以及如何运行希望并行化的代码。例如:

$ ghc --make -rtsopts -O2 -threaded so.hs -eventlog -fforce-recomp
[1 of 1] Compiling Main             ( so.hs, so.o )
Linking so ...
$ ./so +RTS -ls -N2
 sum : 299045675
收益率:

您的并行性太粗,无法产生太多有益的效果。可以高效并行完成的最大工作块在
sumEuler
中,因此您应该在那里添加
par
注释。尝试将
sumEuler
更改为:

sumEuler :: Int -> Int
sumEuler = sum . (parMap rseq euler) . mkList
parMap
来自
Control.Parallel.Strategies
;它表示可以并行完成的映射。第一个参数,
rseq
具有类型
Strategy a
,用于将计算强制到特定点,否则由于懒惰,将无法完成任何工作
rseq
适用于大多数数字类型

fib
中添加并行性是没有用的,下面是关于
fib 40
的内容,没有足够的工作使其有价值

除了threadscope之外,使用
-s
标志运行程序也很有用。找一条像这样的线:

SPARKS: 15202 (15195 converted, 0 pruned)
在输出中。每个spark都是可能并行执行的工作队列中的一个条目。转换的火花实际上是并行完成的,而修剪的火花意味着主线程在工作线程有机会到达火花之前到达火花。如果删减的数字很高,则表示并行表达式的粒度太细。如果火花总数较低,则表示您没有尝试进行足够的并行处理

最后,我认为
parMapFibEuler
最好写为:

parMapFibEuler :: Int
parMapFibEuler = sum (mapFib `using` parList rseq) + sum mapEuler
mapEuler
太短,无法在此处有效地表达任何并行性,尤其是当
euler
已经并行执行时。我怀疑它对
mapFib
是否有实质性的影响。如果列表
mapFib
mapEuler
更长,那么这里的并行性将更有用。您可以使用
parBuffer
,而不是
parList
,这对于列表使用者来说效果很好


使用GHC 7.0.2,对我来说,进行这两项更改将运行时间从12s缩短到8s

您在这里看不到任何并行性的原因是因为您的火花已被垃圾收集。使用
+RTS-s
运行程序,并记下此行:

  SPARKS: 1 (0 converted, 1 pruned)
火花已被“修剪”,这意味着由垃圾收集器清除。在GHC7中,我们对sparks的语义进行了更改,这样,如果程序的其余部分未引用spark,则spark现在被垃圾收集(GC'd);详情见附件

你的案子里为什么会有火花?看看代码:

parMapFibEuler :: Int
parMapFibEuler = (forceList mapFib) `par` (forceList mapEuler `pseq` (sum mapFib + sum mapEuler))
这里的火花是表达式
forklistmapfib
。请注意,该表达式的值不是程序其余部分所必需的;它仅作为
par
的参数显示。GHC知道它不是必需的,所以它会收集垃圾

最近对并行软件包所做的全部更改都是为了让您轻松避免这一熊陷阱。一个好的经验法则是使用
Control.Parallel.Strategies
,而不是直接使用
par
pseq
。我更喜欢这样写

parMapFibEuler :: Int
parMapFibEuler = runEval $ do
  a <- rpar $ sum mapFib
  b <- rseq $ sum mapEuler
  return (a+b)

现在我得到了与GHC7.0.2的良好并行性。但是,请注意@John的评论也适用:通常,您希望寻找更细粒度的并行性,以便GHC使用您的所有处理器。

感谢回复ony,但您提出的代码与我在问题中编写的代码类似,我已经测试了您的命题和线程
parMapFibEuler :: Int -> Int
parMapFibEuler n = runEval $ do
  a <- rpar $ sum (take n mapFib)
  b <- rseq $ sum (take n mapEuler)
  return (a+b)

main = do [n] <- fmap (fmap read) getArgs
          putStrLn (" sum : " ++ show (parMapFibEuler n))