【问题标题】:Unexpected performance observed in repa-algorithms function在 repa-algorithms 函数中观察到意外的性能
【发布时间】:2012-07-31 14:01:15
【问题描述】:

我正在使用以下代码测试来自repa-algorithms-3.2.1.1mmultP 函数(为简洁起见,此处稍作浓缩):

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 矩阵乘法代码的并行性带来了一些小的收益。

更新

令人费解;我用

替换了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 的 Criterion 基准测试产生:

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 数组写入标准输出,但正如我所提到的,如果我交换我自己的矩阵乘法代码,我确实观察到了并行度的提高。还在挠头。

【问题讨论】:

  • 使用 3 核会得到什么?
  • 也比顺序慢,但比 -N4 快。 -N2 的情况相同。

标签: haskell repa


【解决方案1】:

这看起来确实很奇怪,但也许您只是为并行性支付了通常的费用,但没有获得收益? --所以它类似于与荒谬的不平衡负载并行化?

似乎还有更多的问题。不过,令我印象深刻的是——它可能会部分解释你的结果——你只使用了一个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

【讨论】:

  • 是的,我也从您的代码中获得了不错的加速。实际上,在更新这个问题时,我几乎自欺欺人,因为我用sumAllP 等人做了一些并行缩减,这很好地改善了我在-N4 下的时间;看来我真的只是没有在mmultP 上获得所需的利用率。使用我的示例中的简单 main = mmultP arr brr &gt;&gt;= print 得到相似的时间吗?
  • 是的,我的结果与原始模块的结果相似。但我的 main 越复杂,我的结果就越好,至少就并行化而言。
【解决方案2】:

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

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

【讨论】:

  • 谢谢,这是我的怀疑。手动切换代码导致性能比mmultP 更慢,但在-N2-N4 下的时间确实比w/-N1 稍快。这就是我困惑的根源。
猜你喜欢
  • 2014-01-07
  • 2011-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-19
相关资源
最近更新 更多