如何使用Haskell中的策略编写并行缩减?

时间:2010-10-26 21:32:08

标签: haskell parallel-processing

在高性能计算中,总和,产品等通常使用“并行缩减”计算,该“并行缩减”采用 n 元素并在O(log n )时间内完成(给予足够的并行性)。在Haskell中,我们通常使用 fold 进行此类计算,但评估时间在列表长度上始终是线性的。

Data Parallel Haskell内置了一些内容,但是在列表的通用框架中呢?我们可以使用Control.Parallel.Strategies吗?

因此,假设f是关联的,我们如何编写

parFold :: (a -> a -> a) -> [a] -> a

所以parFold f xs只需要length xs中的时间对数?

3 个答案:

答案 0 :(得分:7)

我认为列表不是正确的数据类型。因为它只是一个链表,所以必须按顺序访问数据。虽然您可以并行评估项目,但在减少步骤中您将无法获得太多收益。如果你真的需要一个List,我认为最好的功能就是

parFold f = foldl1' f . withStrategy (parList rseq)

或者

parFold f = foldl1' f . withStrategy (parBuffer 5 rseq)

如果减少步骤很复杂,您可以通过细分列表来获得收益:

parReduce f = foldl' f mempty . reducedList . chunkList . withStrategy (parList rseq)
 where
  chunkList list = let (l,ls) = splitAt 1000 list in l : chunkList ls
  reducedList = parMap rseq (foldl' f mempty)

我冒昧地假设你的数据是Monoid的mempty,如果这是不可能的,你可以用你自己的空类型替换mempty,或者更糟糕的情况下使用foldl1'

此处使用Control.Parallel.Strategies的两个运算符。 parList并行评估列表中的所有项目。之后,chunkList将列表分成1000个元素的块。然后,每个块都由parMap并行减少。

您也可以尝试

parReduce2 f = foldl' f mempty . reducedList . chunkList
 where
  chunkList list = let (l,ls) = splitAt 1000 list in l : chunkList ls
  reducedList = parMap rseq (foldl' f mempty)

根据工作的确切分配方式,其中一项可能比其他工作更有效。

如果您可以使用对索引具有良好支持的数据结构(数组,矢量,地图等),那么您可以为缩减步骤执行二进制细分,这可能会更好。

答案 1 :(得分:1)

这似乎是一个好的开始:

parFold :: (a -> a -> a) -> [a] -> a
parFold f = go
  where
  strategy = parList rseq

  go [x] = x
  go xs = go (reduce xs `using` strategy)

  reduce (x:y:xs) = f x y : reduce xs
  reduce list     = list   -- empty or singleton list

它有效,但并行性并不是那么好。用parList之类的内容替换parListChunks 1000会有所帮助,但在8核计算机上加速仍然限制在1.5倍以下。

答案 2 :(得分:1)

不确定您的parFold函数应该做什么。如果这是一个foldr或foldl的并行版本,我认为它的定义是错误的。

parFold :: (a -> a -> a) -> [a] -> a

// fold right in haskell (takes 3 arguments)
foldr :: (a -> b -> b) -> b -> [a] -> b

Fold将相同的函数应用于列表的每个元素,并累积每个应用程序的结果。我想,提出它的并行版本将要求元素的函数应用程序并行完成 - 有点像parList那样。

    par_foldr :: (NFData a, NFData b) => (a -> b -> b) -> b -> [a] -> b
    par_foldr f z [] = z
    par_foldr f z (x:xs) = res `using` \ _ -> rseq x' `par` rdeepseq res
                       where x' = par_foldr f z xs
                             res = x `f` x'