gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-02 19:08:27 24 4
gpt4 key购买 nike

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

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

更新:

令人费解;我将 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 数组写入标准输出,但正如我提到的,如果我交换自己的矩阵乘法代码,我确实会观察到并行性增益。还是摸不着头脑。

最佳答案

这看起来确实很奇怪,但也许您只是为并行性付出了通常的代价,但没有获得好处? --所以这类似于负载极其不平衡的并行化?

看来一定还有什么地方出了问题。但令我震惊的是,您只使用了一个 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

关于haskell - 在 repa-algorithms 函数中观察到意外的性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11741905/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com