在 Haskell 中使用并行策略时速度变慢

Posted

技术标签:

【中文标题】在 Haskell 中使用并行策略时速度变慢【英文标题】:Slowdown when using parallel strategies in Haskell 【发布时间】:2012-04-04 10:31:31 【问题描述】:

我正在完成 Andre Loh 在 haskell 练习中的确定性并行编程练习。我试图通过使用策略将 N-Queens 顺序代码转换为并行代码,但我注意到并行代码的运行速度比顺序代码慢得多,并且还会因堆栈空间不足而出错。

这是并行 N-Queens 的代码,

import Control.Monad
import System.Environment
import GHC.Conc
import Control.Parallel.Strategies
import Data.List
import Data.Function

type PartialSolution = [Int] -- per column, list the row the queen is in
type Solution = PartialSolution

type BoardSize = Int

chunk :: Int -> [a] -> [[a]]
chunk n [] = []
chunk n xs = case splitAt n xs of
         (ys, zs) -> ys : chunk n zs

-- Generate all solutions for a given board size.
queens :: BoardSize -> [Solution]
--queens n = iterate (concatMap (addQueen n)) [[]] !! n
queens n = iterate (\l -> concat (map (addQueen n) l `using` parListChunk (n `div`            numCapabilities) rdeepseq)) [[]] !! n


-- Given the size of the problem and a partial solution for the
-- first few columns, find all possible assignments for the next
-- column and extend the partial solution.
addQueen :: BoardSize -> PartialSolution -> [PartialSolution]
addQueen n s = [ x : s | x <- [1..n], safe x s 1 ]

-- Given a row number, a partial solution and an offset, check
-- that a queen placed at that row threatens no queen in the
-- partial solution.
safe :: Int -> PartialSolution -> Int -> Bool
safe x []    n = True
safe x (c:y) n = x /= c && x /= c + n && x /= c - n && safe x y (n + 1)

main = do
        [n] <- getArgs
        print $ length $ queens (read n)

(\l -&gt; concat (map (addQueen n) l using parListChunk (n div numCapabilities) rdeepseq)) 行是我对原始代码所做的更改。我看过 Simon Marlow 的解决方案,但我想知道我的代码速度变慢和出错的原因。

提前致谢。

【问题讨论】:

您是否使用-O2 编译并使用-threaded -Nn 运行(其中n 是您的cpu 数?) 请注意,-threaded 是编译时选项,而不是运行时选项。还有,你什么时候回贝利家,唐?水龙头想念你。 不要忘记 -rtsopts 和 +RTS。 即使有-threaded 和他的考虑的东西。我对并行策略一无所知,但在我的机器上,线程版本的运行速度慢了大约 3 倍。 为什么只添加 -thread 选项会使代码运行速度变慢?我有做同样事情的代码。这是由于不正确的并行设计/使用还是其他原因? 【参考方案1】:

你的工作量太大了。 div n numCapabilitiesparListChunk 参数可能是你系统上的 7 个(2 个内核,你正在运行 n ~ 14)。该列表将很快变大,因此引发如此小的工作单元毫无意义(我不明白为什么将其与n 的值联系起来是有意义的)。

如果我增加 10 倍(在这种情况下使触发单元为 70),那么我将获得明显优于单线程的性能。另外,我没有您提到的堆栈问题 - 如果它随着您的 parListChunk 值的更改而消失,那么我会将其报告为错误。

如果我每 800 次进行一次分块,那么时间会在 5.375 秒和 7.9 秒之间达到顶峰。超过800,性能又开始变差了,ymmv。

编辑:

[tommd@mavlo Test]$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.0.4
[tommd@mavlo Test]$ ghc -O2 so.hs -rtsopts -threaded -fforce-recomp ; time ./so 13 +RTS -N2
[1 of 1] Compiling Main             ( so.hs, so.o )
Linking so ...
73712
real    0m5.404s

[tommd@mavlo Test]$ ghc -O2 so.hs -rtsopts -fforce-recomp ; time ./so 13
[1 of 1] Compiling Main             ( so.hs, so.o )
Linking so ...
73712
real    0m8.134s

【讨论】:

感谢您指出这个错误。我实际上想在核心之间平均分配部分解决方案。现在我将它修改为 `div (length l) numCapabilities` 应该没问题。即使在这样做之后,我发现并行版本仍然比顺序版本慢(编译时没有 -thread 选项),对于 -N1 选项,我得到相同的堆栈溢出异常。当我尝试对 14 号板进行相同操作时,顺序版本可以正常工作,但并行版本会出现内存不足错误。 我知道这些信息可能还不够,如果我能附上这些案例的事件日志文件会有所帮助吗? 一个版本的 GHC 将有助于您当前使用的代码以及您如何为每种情况编译它(我希望包括 -fforce-recomp 标志)。我不建议您使用length l,只需选择一个足够大的值,以使火花产生的成本微不足道,但又足够小,以使您不会注意到一个或两个核心为那个火花工作的时间差. 我目前使用的是 ghc 7.4.1。我尝试运行的代码与上面的代码相同,只是我在评论中提到的更改。当我使用长度时,产生的火花数量急剧下降(最大值约为 60)。我也尝试使用其他值,但仍然出现错误。我使用 -threaded -O2 -fforce-recomp -rtsopts -eventlog 标志编译文件并使用 -N2、-N3、-N4 运行。标志

以上是关于在 Haskell 中使用并行策略时速度变慢的主要内容,如果未能解决你的问题,请参考以下文章

haskell中的并行映射

尽管总内存使用量只有 22Mb,但 Haskell 线程堆溢出?

Sprite 在接近接触点时速度变慢

为啥我的haskell程序这么慢? Haskell 编程,人生游戏

如何在 Haskell 中查找和修复由于 GC 导致的性能问题?

在分区 Spark DataFrame 中使用多列是不是会使读取速度变慢?