2011-07-19 45 views
4

類似this related question,我想在一個Vector執行並行的地圖,但在我來說,我有一個嵌套向量,我似乎無法得到正確的評價。哈斯克爾嵌套向量並行的戰略

這是我到目前爲止有:

import qualified Data.Vector as V 
import qualified Data.Vector.Unboxed as U 
import Data.Vector.Strategies 
import Control.DeepSeq 

main = do 
    let res = genVVec 200 `using` parVector 2 
    print res 

genUVec :: Int -> U.Vector Int 
genUVec n = U.map (ack 2) $ U.enumFromN n 75 

genVVec :: Int -> V.Vector (U.Vector Int) 
genVVec n = V.map genUVec $ V.enumFromN 0 n 

ack :: Int -> Int -> Int 
ack 0 n = n+1 
ack m 0 = ack (m-1) 1 
ack m n = ack (m-1) (ack m (n-1)) 

instance (NFData a, U.Unbox a) => NFData (U.Vector a) where 
    rnf = rnf . U.toList 

給出:

$ ./vectorPar +RTS -N8 -s >/dev/null 
    SPARKS: 200 (17 converted, 183 pruned) 
    Total time 1.37s ( 1.30s elapsed) 
$ ./vectorPar +RTS -s >/dev/null 
    SPARKS: 200 (0 converted, 200 pruned) 
    Total time 1.25s ( 1.26s elapsed) 

我也曾嘗試直接修改parVector功能in vector-strategies,但我的企圖是笨拙而無效的。

我意識到REPA是爲嵌套工作負載設計的,但這似乎是一個足夠簡單的問題,我寧願不必重寫很多代碼。

回答

4

注意:這裏是矢量策略的有罪作者(這是一個非常小的標題,因爲這只是一個搗亂的功能,我想其他人會覺得有用)。

你的觀察結果parVector是錯誤的,因爲它允許火花在使用前被GCed似乎是正確的。 SimonM的建議意味着我必須正確地做我想要避免的事情,用一些代價來建造一個新的載體,而不是舊的載體。認識到這一點是必要的,有什麼理由不改變parVector來的更簡單的定義:

parVector2 :: NFData a => Int -> Strategy (V.Vector a) 
parVector2 n = liftM V.fromList . parListChunk n rdeepseq . V.toList 

通知由約翰·L給定的修補程序只適用,因爲它「拍」收集器利用收集之前迫使計算會發生。

我將改變矢量策略庫,所以這是不必要的 - 使您的原始代碼工作正常。不幸的是,這將導致構建新Vector的上述成本(通常最小)。

+0

我希望我能接受這兩個答案(這是長期的答案)。順便說一句:不要感到內疚,我只能希望有一天可以提供像矢量策略(破碎或不破碎!)的東西。 :) – JKnight

+0

@JKnight我已經上傳了vector-strategies-0.2來解決這個問題和一個文檔錯誤。 –

+1

@Thomas M. DuBuisson:至少在這種情況下,我懷疑原始矢量是否構建完成,因爲它是通過'enumFromN'構造的,並且相對較快地轉換爲列表。無論如何,我可以確認你的新版本的作品,並提供比我的解決方案更好的性能。 –

5

問題似乎是parVector不強制評估向量的元素。每個元素仍然是一個thunk,直到vector被消耗掉(通過打印),什麼都不會引發,這對於火花的工作來說太遲了。您可以通過使用rdeepseq組合parVector策略來強制評估每個元素。

import qualified Data.Vector as V 
import qualified Data.Vector.Unboxed as U 
import Data.Vector.Strategies 
import Control.DeepSeq 
import Control.Parallel.Strategies 

main = do 
    let res = genVVec 200 `using` (rdeepseq `dot` parVector 20) 
    print res 

genUVec :: Int -> U.Vector Int 
genUVec n = U.map (ack 2) $ U.enumFromN n 75 

genVVec :: Int -> V.Vector (U.Vector Int) 
genVVec n = V.map genUVec $ V.enumFromN 0 n 

ack :: Int -> Int -> Int 
ack 0 n = n+1 
ack m 0 = ack (m-1) 1 
ack m n = ack (m-1) (ack m (n-1)) 

instance (NFData a, U.Unbox a) => NFData (U.Vector a) where 
    rnf vec = seq vec() 

instance (NFData a) => NFData (V.Vector a) where 
    rnf = rnf . V.toList 

我也改變了你的NFData (U.Vector a)實例。由於U.Vector被拆箱,所以對WHNF的評估就足夠了,並且通過列表轉換強制每個元素都是浪費的。實際上,如果您願意,rnf的默認定義可以正常工作。

有了這兩個變化,我得到以下

SPARKS: 200 (200 converted, 0 pruned) 

和運行時間已經減少了近50%。我還將矢量塊大小調整爲20,但結果非常類似於塊大小2.

+0

優秀!這很奇怪,因爲'parVector'已經在外部向量的每個元素上調用了'rdeepseq',但是如果沒有使用策略組成'dot',它似乎沒有任何作用。另外,我發郵件給vector-strategies的維護者,因爲我認爲塊大小目前被錯誤地忽略。 – JKnight

+0

@JKnight:我同意塊大小被忽略。或者更恰當地說,它控制'parVector'遞歸多遠,但矢量的每個元素都是並行計算的,無論如何。無論大小如何,我總能看到200個火花。 –

+0

@John L,是的,200火花。那是我相當愚蠢的。我會盡快上傳一個新版本(但不會立即出於害怕發現其他需要在15分鐘後更改的內容)。 –