我正在Haskell中进行一些并行性实验。其中一部分是检查策略与普通par
和pseq
组合器之间的差异。
我创建了一个像这样的函数:
parMap :: NFData b => (a -> b) -> [a] -> [b]
parMap _ [] = []
parMap f (a:as) =
let v = f a
vs = parMap f as
in rnf v `par` vs `pseq` v : vs
我正在尝试使用尽可能少的库(因此没有Strategies,没有Par)以及像Haskell那样的抽象层来实现并行化。
使用此功能,我尝试使用以下两个功能之一执行矩阵矩阵乘法:
matmultListPar :: (Num a, NFData a) => [[a]] -> [[a]] -> [[a]]
matmultListPar a b = parMap multiplyRowByEachColumn a
where multiplyRowByEachColumn r = map (\c -> sum . zipWith (*) r $ c) $ b'
b' = transpose b
matmultListParChunks :: (Num a, NFData a) => Int -> [[a]] -> [[a]] -> [[a]]
matmultListParChunks size a b
= let
chunks = toChunks size a
b' = transpose b
in concat $ parMap (flip matmultList $ b) chunks
matmultList
的定义如下:
matmultList :: (Num a) => [[a]] -> [[a]] -> [[a]]
matmultList a b = fmap multiplyRowByEachColumn a
where multiplyRowByEachColumn r = fmap (\c -> sum $ zipWith (*) r c) $ b'
b' = transpose b
还有toChunks
:
toChunks :: Int -> [a] -> [[a]]
toChunks _ [] = []
toChunks size list
= let (i, f) = splitAt size list
in i : (toChunks size f)
为清楚起见-矩阵以行为主存储。
我的问题以及因此的问题是:
在将策略parList rdeepseq
应用于纯粹的matmultList
时,我在两个内核上的速度几乎提高了两倍。使用我的parMap
时,加速几乎没有。
使用-s
开关检查RTS的火花统计信息,我可以看到parMap
的大多数火花都是GC生成的(在转换为Strategy时)。
您可以猜到我的parMap
(以criterion
基准衡量)并没有达到并行加速,而我的策略是
我在检查Haskell库代码时注意到,par
组合器使用函数par#
进行激增,而策略使用类似spark#
的事物(我再也找不到它了,所以我可能错了)。
我需要在rnf
中使用parMap
,因为a
可能是[Double]
,所以激发WHNF是不够的(我在不同地方做了force
的实验,因为好)。
是由于我的错误实现,太细粒度的计算(GC火花)还是par
与策略之间的差异而导致的提速不足?还是完全其他?如何使用par
和pseq
实现加速?