Haskell并行列表计算性能

时间:2013-09-02 13:53:47

标签: multithreading parallel-processing benchmarking haskell

我正在使用并行的Haskell函数parpseq,我发现了一些有趣的东西。

我的示例基于Real World Haskell的书(Parallel programming in Haskell)中的示例:

常用代码:

import Control.Parallel (par, pseq)

-- <<sorting code goes here>>

force :: [a] -> ()
force xs = go xs `pseq` ()
    where go (_:xs) = go xs
          go [] = 1

main = do
    print $ take 10 $ parSort [0..1000000]

排序代码1(取自本书):

parSort :: (Ord a) => [a] -> [a]
parSort (x:xs)    = force greater `par` (force lesser `pseq`
                                         (lesser ++ x:greater))
    where lesser  = parSort [y | y <- xs, y <  x]
          greater = parSort [y | y <- xs, y >= x]
parSort _         = []

排序代码2(我的自定义变体):

parSort :: (Ord a) => [a] -> [a]
parSort (x:xs)    = force greater `par` (lesser ++ x:greater)
    where lesser  = parSort [y | y <- xs, y <  x]
          greater = parSort [y | y <- xs, y >= x]
parSort _         = []

编译&amp;运行:ghc -O2 -threaded --make Main.hs && time ./Main +RTS -N8

有趣的是,我的变体比书籍快一点:

sorting code 1 - avg. 16 seconds
sorting code 2 - avg. 14 seconds

我想问你为什么我们可以观察到这种行为,以及这本书的解决方案是否会给我带来任何好处。我很想深深理解为什么这个解决方案可以表现更好。

1 个答案:

答案 0 :(得分:7)

我会说这是因为您的自定义变体不会强制列表的第一部分。让我们来看看顶层发生的事情:你强制列表的右半部分,但不强制左侧部分。当你打印前10个元素时,你只会懒得评估左边部分的前10个元素,而剩下的部分则没有评估。

另一方面,本书的解决方案强制要求两个部分,所以在打印前10个元素之前,你要评估左边和右边的部分。

不要打印前10个元素,而是尝试打印最后一个元素,如

print $ last $ parSort data

然后算法的两个变体都必须评估整个列表。或者在排序之后和打印之前强制整个列表。


注意使用此算法对[0..100000]进行排序效率非常低,因为您总是选择最差的枢轴,因此需要 O(n ^ 2)时间。测量结果根本不会给出有意义的结果。如果您希望使用 O(n log n)时间获得不错的结果,请使用类似随机的数据提供算法。您可以找到一种简单的方法来创建随机排列here

注意:我建议您使用criterion来衡量代码,而不是使用time。然后,您可以仅测量代码的相关部分,不包括初始化等,并强制输入和输出数据,以便精确测量您感兴趣的部分。