并行的quicksort

haskell搞并行还是挺方便的。纯函数式没状态嘛,这儿锁啊钥匙啊什么的都不用管了。有个Control.Parallel,里貌似只有两个函数,一个par,表示两个并行计算;一个pseq,表示连续计算。参数都是两个名字。像处理个分治算法啥的,就再合适不过了。于是再次膜拜惰性求值,写起来真的太奇特了。

module Main where
import Control.Parallel
 
--the classic one
qsort :: (Ord a) => [a] -> [a]
qsort [] = []
qsort (x:xs) =
 (qsort lt) ++ [x] ++ (qsort gt)
 where
  lt = filter (<x) xs
  gt = filter (>=x) xs
 
 
--the parallel one
psort :: (Ord a) => [a] -> [a]
psort [] = []
psort (x:xs) =
 sorted_lt `par` sorted_gt `pseq` (sorted_lt ++ [x] ++ sorted_gt) ----unbelieveable,isn't it? :>
 where
  sorted_lt = psort $ filter (<) xs
  sorted_gt = psort $ filter (>=x) xs
 
 
main = do {
 list <- return [5000,4999..1];
 print $ qsort list;
 print $ psort list;
}

profile下,给ghc添加一个编译选项 -prof -O:

ghc --make -prof -O -auto-all quicksort.hs

执行程序,加一个选项 +RTS -p,它会在本目录下生成一个quicksort.prof文件

quicksort +RTS -p

quicksort.prof文件的部分内容:

 Mon Jul 20 16:40 2009 Time and Allocation Profiling Report  (Final)

    quicksort +RTS -p -RTS

 total time  =        6.86 secs   (343 ticks @ 20 ms)
 total alloc = 1,401,627,416 bytes  (excludes profiling overheads)

COST CENTRE                    MODULE               %time %alloc

qsort                          Main                  69.1   49.9
psort                          Main                  30.9   49.9

可见在这台双核的机器上,性能提高了一半多 :)
ps:测试的数据貌似不是很好(按说该用个随机数列),不过知道有这回事就行了~

9 Responses to “并行的quicksort”


  • 让我想起了INTEL的Parallel Studio

    回复回复
  • 记得intel貌似有个关于haskell并行计算的paper很有名…忘了叫什么名字

    回复回复
  • 原来传说中的 FP 并行优势是这么用的……

    有几个问题,前辈解释下。

    Hoogle 了一下,英文太差,par is generally used when the value of a is likely to be required later 没看明白什么意思。这就是正文中所指的惰性求值?是不是推迟到第二个参数计算的时候再计算第一个参数的意思啊?

    还有
    sorted_lt `par` sorted_gt `pseq` (sorted_lt ++ [x] ++ sorted_gt)
    这句是不是也可以写成这样:
    par sorted_lt $ pseq sorted_gt $ sorted_lt ++ [x] ++ sorted_gt

    最后一个问题,如果是
    lt `par` gt `pseq` (psort lt ++ [x] ++ psort gt)
    结果会怎样?是不是还是 1/2 时间?

    回复回复
  • @xpycc

    haskell真的把惰性求值玩到极致了,我也是刚刚才搞明白 :P

    sorted_lt `par` sorted_gt `pseq` (sorted_lt ++ [x] ++ sorted_gt)

    在这个程序里,按照设定,会先并行计算sorted_lt和sorted_gt,在两个函数都求出值来之后再计算(sorted_lt ++ [x] ++ [sorted_gt])
    若是没有惰性求值的命令式语言,在函数调用前先把参数的值都求出来,像sorted_lt `par` sorted_gt `pseq` (sorted_lt ++ [x] ++ sorted_gt)这样的语句,非但不会有并行,反而会执行两次sorted_lt和sorted_gt。不过这里有了惰性求值呢,参数中的sorted_lt和sorted_gt都不会立即执行,这样就可以将行为传递了。

    par的类型声明:par :: a -> b -> b
    par is generally used when the value of a is likely to be required later
    “推迟到第二个参数计算的时候再计算第一个参数”,貌似就是这样。我想这句话的意思应该就是par的第一个参数在真正需要时不会进行计算,像sorted_lt `par` sorted_gt `pseq` (sorted_lt ++ [x] ++ sorted_gt)这句,只有在最后(sorted_lt++[x]++sorted_gt)用到了sorted_lt和sorted_gt时,它才会进行并行计算sorted_gt和sorted_lt。如果改成sorted_lt `par` sorted_gt `pseq` (1+1),我想sorted_lt和sorted_gt可能就永远不会被计算了。

    反单引号`允许以中缀形式调用haskell的函数
    sorted_lt `par` sorted_gt `pseq` (sorted_lt ++ [x] ++ sorted_gt)

    par sorted_lt $ pseq sorted_gt $ sorted_lt ++ [x] ++ sorted_gt
    就是等价的,感觉在这里用中缀的形式貌似更好看些 :)

    关于
    lt `par` gt `pseq` (psort lt ++ [x] ++ psort gt)
    应该会先并行计算lt和gt的值(即filter),由于在这里计算的主要时间貌似都花在filter上,我想应该也会有性能提升,有时间试下 :)

    回复回复
  • @ssword 刚刚看了Hoogle 的 par 源码,一头雾水……

    如果都有性能提升,那会是哪个提升得多一些呢?我感觉是后一个快一些,因为 sorted_lt 和 sorted_gt 的时间不一定是一致的,于是等慢的算完了,才合并。而 lt 和 gt 的时间肯定大致是一样的。

    回复回复
  • 前来膜拜~

    回复回复
  • @xpycc
    profile下试试就知道了,呵呵

    @fledna
    您的每次出现都是让我内牛满面啊,囧

    回复回复
  • 毫无深度可言

    回复回复
  • @focus
    膜拜有深度的大牛

    回复回复

Leave a Reply