2012-07-31 30 views
3

我是從repa-algorithms-3.2.1.1檢驗出mmultP功能用下面的代碼(在此冷凝爲簡潔起見一點點):在repA的的算法功能觀察到意外的性能

import Data.Array.Repa hiding   (map) 
import Data.Array.Repa.Algorithms.Matrix (mmultP) 

import Control.Monad      (replicateM) 
import Control.Arrow      ((&&&)) 
import System.Random.MWC     (initialize, uniformR) 
import Control.Monad.ST     (runST) 
import Data.Vector.Unboxed    (singleton) 
import Data.Word       (Word32) 

-- Create a couple of dense matrices 
genRnds :: Word32 -> [Double] 
genRnds seed = runST $ do 
    gen <- initialize (singleton seed) 
    replicateM (1000^2) (uniformR (0, 1) gen) 

(arr, brr) = head &&& last $ map (fromListUnboxed (Z :. 1000 :. 1000 :: DIM2) . genRnds) [1, 100000] 

-- mmultP test 
main :: IO() 
main = mmultP arr brr >>= print 

和如使用指定here,編譯

ghc mmultTest.hs -Odph -rtsopts -threaded -fno-liberate-case -funfolding-use-threshold1000 -funfolding-keeness-factor1000 -fllvm -optlo-O3 -fforce-recomp 

這裏是在線程運行時的連續運行:

$ time ./mmultTest +RTS -K100M > /dev/null 
real 0m10.962s 
user 0m10.790s 
sys  0m0.161s 

,這裏是一個使用4芯(在四核的MacBook Air上運行):

$ time ./mmultTest +RTS -N4 -K100M > /dev/null 
real 0m13.008s 
user 0m18.591s 
sys  0m2.067s 

任何人有任何的直覺來這裏發生了什麼?我還獲得了-N2-N3的慢於序列的性能;每個核心似乎都會增加一些額外的時間。

請注意,我在觀察到一些手動軋製的Repa矩陣乘法代碼的一些小的收益。

UPDATE

困惑;我換成main

mmultBench :: IO() 
mmultBench = do 
    results <- mmultP arr brr 
    let reduced = sumAllS results 
    print reduced 

,並取消了對mwc-random的依賴:

(arr, brr) = head &&& last $ map (fromListUnboxed (Z :. 1000 :. 1000 :: DIM2)) (replicate 2 [1..1000000]) 

的判定基準與運行時選項-N1 -K100M產量:

mean: 1.361450 s, lb 1.360514 s, ub 1.362915 s, ci 0.950 
std dev: 5.914850 ms, lb 3.870615 ms, ub 9.183472 ms, ci 0.950 

-N4 -K100M給我:

mean: 556.8201 ms, lb 547.5370 ms, ub 573.5012 ms, ci 0.950 
std dev: 61.82764 ms, lb 40.15479 ms, ub 102.5329 ms, ci 0.950 

這是一個可愛的加速。我幾乎認爲之前的行爲是由於將生成的1000x1000數組寫入stdout所致,但正如我所提到的,如果我交換自己的矩陣乘法代碼,我確實會在那裏觀察到並行增益。仍在撓撓我的腦袋。

+0

哪個GHC版本? – 2012-07-31 14:07:51

+0

使用GHC 7.4.1。 – jtobin 2012-07-31 14:09:00

+0

使用3核時,你會得到什麼? – 2012-07-31 15:04:00

回答

1

1)打印矩陣到stdout會令綁定的程序IO。記錄在這種情況下的任何加速數字將是謊言。

2)沒有4核心的MacBook Airs。它們都是2核心,每個核心有2個超線程。一次只能運行2個線程。使用> -N2的任何加速都將由於延遲隱藏 - 核心上的第二個超線程可以運行,而第一個超線程在高速緩存未命中時停頓。

+0

謝謝,那是我的懷疑。在手寫代碼中交換導致性能比'mmultP'慢,但是它在'-N2'和'-N4'下產生的時間要比w /'-N1'快得多。這是我混亂的根源。 – jtobin 2012-08-06 11:37:07

2

這看起來很奇怪,但也許你只是在平行支付通常的付款,但沒有收穫好處? - 這類似於與荒謬的不平衡負載並行?

看起來似乎更多必然是錯誤的。然而,讓我感到震驚的是 - 它可能會對結果進行部分解釋 - 是,您只使用一個repa組合器,mmultP。框架幾乎沒有機會!如果我通過zipWithfoldAllP等 -

main :: IO() 
main = arr `xxx` brr >>= foldAllP (+) 0 >>= print where 
    xxx arr brr = R.zipWith (+) <$> complicated arr <*> complicated brr 
    complicated = mmultP brr >=> mmultP arr >=> mmultP brr >=> mmultP arr 

然後用我的兩個核心老爺車,我得到完全的雙核心並行化的夢想:

$ time ./mmmult +RTS -K200M -N2 
6.2713897715510016e16 

real 0m8.742s 
user 0m16.176s 
sys 0m0.444s 

$ time ./mmmult +RTS -K200M 
6.2713897715512584e16 

real 0m15.214s 
user 0m14.970s 
sys 0m0.239s 
+0

是的,我也從你的代碼中獲得了很好的加速。實際上,在更新這個問題時,我實際上幾乎是在愚弄我自己,因爲我已經用'sumAllP'等做了一些平行減少,這很好地改善了我在'-N4'下的時間。看起來我真的只是沒有在'mmultP'上獲得理想的利用率。您是否使用我的示例中的簡單'main = mmultParrbrr >> = print'獲得了類似的時間? – jtobin 2012-08-02 23:32:53

+0

是的,我的結果與您的原始模塊類似。但是,我做得越複雜,我的結果越好,至少就並行化而言。 – applicative 2012-08-03 01:11:37