在repa-algorithms函数中观察到意外的性能

时间:2012-07-31 14:01:16

标签: haskell repa

我使用以下代码测试了来自mmultP的{​​{1}}函数(为简洁起见,这里有一段时间):

repa-algorithms-3.2.1.1

和指定的here,使用

编译
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

这是线程运行时中的顺序运行:

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

这里有一个使用4核(在四核MacBook Air上运行):

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

任何人对这里发生的事情都有直觉吗?对于$ time ./mmultTest +RTS -N4 -K100M > /dev/null real 0m13.008s user 0m18.591s sys 0m2.067s -N2,我的性能也会慢于连续性。每个核心似乎都会增加一些额外的时间。

请注意,我在一些手工修复的矩阵乘法代码上观察到并行性的一些微小收益。

更新

困惑;我用

替换了-N3
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]) 的Criterion基准产生:

-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

这是一个可爱的加速。我几乎认为之前的行为是由于将生成的1000x1000数组写入stdout,但正如我所提到的,如果我交换自己的矩阵乘法代码,我确实会观察到并行性增益。仍在挠头。

2 个答案:

答案 0 :(得分: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

然后用我的双核jalopy,我得到了两核心并行化的梦想:

 $ 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

答案 1 :(得分:1)

1)将矩阵打印到stdout将使程序IO绑定。在这种情况下记录的任何加速数字都将是谎言。

2)没有4核MacBook Airs。它们都是2核心,每个核心有2个超线程。一次只能运行2个线程。任何加速&gt; -N2将由于延迟隐藏 - 核心上的第二个超线程可以运行,而第一个在缓存未命中时停止。