2013-11-03 83 views
6

我有這個看似微不足道的並行快速排序的實現,代碼如下:是否有可能在Haskell中加快par的速度?

import System.Random 
import Control.Parallel 
import Data.List 

quicksort :: [a] -> [a] 
quicksort xs = pQuicksort 16 xs -- 16 is the number of sparks used to sort 

-- pQuicksort, parallelQuicksort 
-- As long as n > 0 evaluates the lower and upper part of the list in parallel, 
-- when we have recursed deep enough, n==0, this turns into a serial quicksort. 
pQuicksort :: Int -> [a] -> [a] 
pQuicksort _ [] = [] 
pQuicksort 0 (x:xs) = 
    let (lower, upper) = partition (< x) xs 
    in pQuicksort 0 lower ++ [x] ++ pQuicksort 0 upper 
pQuicksort n (x:xs) = 
    let (lower, upper) = partition (< x) xs 
     l = pQuicksort (n `div` 2) lower 
     u = [x] ++ pQuicksort (n `div` 2) upper 
    in (par u l) ++ u 

main :: IO() 
main = do 
    gen <- getStdGen 
    let randints = (take 5000000) $ randoms gen :: [Int] 
    putStrLn . show . sum $ (quicksort randints) 

我編譯

ghc --make -threaded -O2 quicksort.hs 

./quicksort +RTS -N16 -RTS 

運行無論我做什麼我無法讓它比運行在一個cpu上的簡單順序實現運行得更快。

  1. 是否有可能解釋爲什麼它在多個CPU上的運行速度比在一個上運行速度慢得多?
  2. 是否有可能通過做一些技巧來使這個縮放比例(至少是線性的)與CPU的數量成正比?

編輯:@tempestadept暗示,快速排序它是自己的問題。爲了檢查這一點,我以與上例相同的精神實施了一個簡單的合併排序。它具有相同的行爲,執行速度越慢,添加的功能越多。

import System.Random 
import Control.Parallel 

splitList :: [a] -> ([a], [a]) 
splitList = helper True [] [] 
    where helper _ left right [] = (left, right) 
     helper True left right (x:xs) = helper False (x:left) right xs 
     helper False left right (x:xs) = helper True left (x:right) xs 

merge :: (Ord a) => [a] -> [a] -> [a] 
merge xs [] = xs 
merge [] ys = ys 
merge (x:xs) (y:ys) = case x<y of 
    True -> x : merge xs (y:ys) 
    False -> y : merge (x:xs) ys 

mergeSort :: (Ord a) => [a] -> [a] 
mergeSort xs = pMergeSort 16 xs -- we use 16 sparks 

-- pMergeSort, parallel merge sort. Takes an extra argument 
-- telling how many sparks to create. In our simple test it is 
-- set to 16 
pMergeSort :: (Ord a) => Int -> [a] -> [a] 
pMergeSort _ [] = [] 
pMergeSort _ [a] = [a] 
pMergeSort 0 xs = 
    let (left, right) = splitList xs 
    in merge (pMergeSort 0 left) (pMergeSort 0 right) 
pMergeSort n xs = 
    let (left, right) = splitList xs 
     l = pMergeSort (n `div` 2) left 
     r = pMergeSort (n `div` 2) right 
    in (r `par` l) `pseq` (merge l r) 

ris :: Int -> IO [Int] 
ris n = do 
    gen <- getStdGen 
    return . (take n) $ randoms gen 

main = do 
    r <- ris 100000 
    putStrLn . show . sum $ mergeSort r 
+1

請注意,這實際上是一個quicksort的實現:http://stackoverflow.com/questions/7717691/why-is-the-minimalist-example-haskell-quicksort-not-a-true-quicksort – ErikR

+1

至少我即使使用'sum'清除任何可能的thunk時,也無法使用'pseq'將其執行得更好。也許有一個完全不同的問題。 - 現在我已經通過回答刪除,這裏再次作爲評論:1。命名該函數只是'quicksort'可能會造成混淆,因爲您不希望這樣的函數接受額外的並行性參數; 2.使用類型簽名,只有_always_用於頂層函數,甚至當它們可能與名稱建議略有不同時使用; 3.如果可能的話,使用庫函數,例如'partition'。 - 好問題,順便說一句。 – leftaroundabout

+4

我沒有足夠的時間發佈完整的答案,但我想有兩個可能的問題:(1)你應該使用'''''''''''''''''' 。 (2)雖然你並行運行子計算,但是直到需要時才進行真正的評估。所以你應該強制每個子列表到NF(或者至少是它的完整結構),像'''forceList''''' forceList''''''''forceList''擁有)強制評估列表的功能。另外爲了適當的基準測試,我建議使用[標準](http://hackage.haskell.org/package/criterion)。 –

回答

3

我不知道如何能爲慣用的快速排序工作,但它可以工作(一個有點弱程度)對於真正的當務之急快速排序爲Sparking Imperatives所示由羅馬。儘管如此,他從未獲得過很好的加速比。這真的需要一個真正的work-stealing deque,不像火花隊列那樣溢出來正確優化。

+0

我在火花隊列中撞牆了嗎?我只用16倍的比分將問題細分爲我的16項能力。之後,該算法是連續的。也許我並不理解關於帕,火,火的本質。 – lysgaard

2

由於您的僞快速排序涉及列表連接,無法並行化並且需要二次時間(所有連接的總時間),所以您不會得到任何顯着改進。我建議你嘗試使用鏈接列表上的合併排序,即O(n log n)

另外,要運行帶有大量線程的程序,您應該使用-rtsopts進行編譯。

+0

我已添加合併排序實現。我已經注意使算法在分裂和合並期間只遍歷一次,這應該是最優的。它顯示與快速排序相同的症狀。放慢你投入的更多功能。 -rtsopts只需要24個以上的功能。 – lysgaard

+0

如何進行列表連接_quadratic_及時? – leftaroundabout

+0

我的意思是,所有連接的總時間是二次的 – tempestadept

0

par只評估弱頭標準形式的第一個參數。也就是說:如果第一個參數的類型是Maybe Int那麼par將檢查結果是Nothing還是Just something並停止。它根本不會評估something。同樣,對於列表,它僅評估足以檢查列表是否爲[]something:something_else。要並行評估整個列表:您不會直接將列表傳遞給par,那麼您將按照列表的方式創建一個表達式,以便在將它傳遞給par時需要整個列表。例如:

evalList :: [a] ->() 
evalList [] =() 
evalList (a:r) = a `pseq` evalList r 

pMergeSort :: (Ord a) => Int -> [a] -> [a] 
pMergeSort _ [] = [] 
pMergeSort _ [a] = [a] 
pMergeSort 0 xs = 
    let (left, right) = splitList xs 
    in merge (pMergeSort 0 left) (pMergeSort 0 right) 
pMergeSort n xs = 
    let (left, right) = splitList xs 
     l = pMergeSort (n `div` 2) left 
     r = pMergeSort (n `div` 2) right 
    in (evalList r `par` l) `pseq` (merge l r) 

另注:在Haskell中推出新線程的開銷是非常低,所以pMergeSort 0 ...的情況下可能是沒有用的。

相關問題