Haskell中并行计算的性能问题

时间:2018-07-16 16:41:40

标签: haskell parallel-processing

我正在比较两个运行相同计算的haskell程序的性能。

第一个是顺序的:

main :: IO()
main = putStr $ unlines . map (show . solve) $ [100..107]
  where solve x = pow x (10^7) (982451653)

第二个使用Control.Parallel.Strategies

import Control.Parallel.Strategies

main :: IO()
main = putStr $ unlines . parMap rdeepseq (show . solve) $ [100..107]
  where solve x = pow x (10^7) (982451653)

在两种情况下,powmodular exponentiation天真地实现为:

pow :: Int -> Int -> Int -> Int
pow a 0 m = 1
pow a b m = a * (pow a (b-1) m) `mod` m

使用预期的100%CPU,顺序程序在大约3秒钟内运行。

$ stack ghc seq.hs -- -O2
$ \time -f "%e s - %P" ./seq > /dev/null
2.96 s - 100%

如果将并行程序限制为单个内核,则还可以在100%CPU上运行3秒钟。

$ stack ghc par.hs -- -O2 -threaded
$ \time -f "%e s - %P" ./par +RTS -N1 > /dev/null
3.14 s - 99%

但是当我在4个内核上运行它时,我没有观察到预期的性能提升:

$ \time -f "%e s - %P" ./par +RTS -N4 > /dev/null
3.31 s - 235%

更令人惊讶的是,当在多个内核上运行时,顺序程序使用100%以上的CPU:

$ stack ghc seq.hs -- -O2 -threaded
$ \time -f "%e s - %P" ./seq +RTS -N4 > /dev/null
3.26 s - 232%

如何解释这些结果?


编辑-根据@RobertK和@Yuras的建议,我将rdeeseq替换为rpar,它确实解决了最初的问题。但是,性能仍然远远低于我的预期:

$ stack ghc par.hs -- -O2 -threaded
$ \time -f "%e s - %P" ./par +RTS -N1 > /dev/null
3.12 s - 99%
$ \time -f "%e s - %P" ./par +RTS -N4 > /dev/null
1.91 s - 368%

即使4个内核平均运行时间超过90%,执行时间也几乎不除以2。

此外,线程范围图的某些部分看起来非常顺序: enter image description here

2 个答案:

答案 0 :(得分:2)

首先,<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@mipmap/ic_notification" /> 似乎是buggy。尝试运行rdeepseq,您将看不到火花。这就是为什么您看不到4核有任何加速的原因。请改用./seq +RTS -N4 -s

还要注意rnf x ‘pseq‘ return x输出中的GC静力学。实际上,GC占用了大部分CPU。使用+RTS -s,您将运行4个并行GC,它们将花费更多时间。这就是为什么顺序编程在4个内核上占用更多CPU的原因。基本上,您有3个GC线程在旋转锁中处于空闲状态,等待同步。无用的事情,是在繁忙的循环中吃掉CPU。尝试使用-N4选项限制并行GC线程的数量。

关于性能提升。您不应期望完美的缩放比例。另外,我认为您有1个起泡的火花-并行评估,但未使用其结果。

添加:与您在注释中链接的python实现相比,我发现您在haskell中使用了完全不同的算法。下一个或多或少类似的方法(要求-qn1):

BangPatterns

您的原始算法使用堆栈来构建结果,因此它受GC约束,而不是受实际计算约束。因此,您不会看到很大的加速。在使用新算法时,我看到了3倍的加速(我不得不增加工作量才能看到加速,因为算法变得太慢了。)

答案 1 :(得分:1)

我不认为您的并行示例是并行的。 parMap接受一个策略,您的策略只是告诉它执行一个deepseq。您需要将此策略与定义并行行为的策略相结合,例如rpar。您正在告诉haskell“使用此策略执行此地图”,现在您的策略未定义任何并行行为。

还要确保您在编译程序时指定-rtsopts标志(我不知道stack是否为您完成此操作,但是ghc要求它启用运行时选项)。