2012-07-31 65 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