比这更通用的parfoldr

时间:2011-07-23 15:21:18

标签: haskell parallel-processing

我的目标是具有并行折叠功能。起初,似乎 相当直接地实现,这就是我的想法:

首先根据数量将输入列表分解为分区 核心(numCapabilities)。然后将foldr应用于每个分区 将生成每个分区的折叠值列表。然后做一个 再次在该列表上折叠以获得最终值。

    listChunkSize = numCapabilities

    chunk n [] = []
    chunk n xs = ys : chunk n zs
      where (ys,zs) = splitAt n xs

    parfoldr f z [] = z
    parfoldr f z xs = res
      where
            parts = chunk listChunkSize xs
            partsRs = map (foldr f z) parts `using` parList rdeepseq
            res = foldr f z partsRs

上面的代码不起作用,因为很明显的定义 foldr,(a -> b -> b) -> b -> [a] -> b,暗示输入列表 type是(嗯,可以)与累加器和结果类型不同。

例如,

1)foldr (+) 0 [1..10] => list type = accumulator type(Integer)

2)foldr (\i acc -> (i>5) && acc) True [1..10] =>列表类型(整数)! =累加器类型(布尔)

因此,查看上面的代码,地图将生成b类型的列表 然后将其作为参数传递给第二个折叠器。但第二个 foldr接受a类型的列表。所以,那是行不通的。

一个丑陋的解决方案是为其提供不同类型的签名 parfoldr,例如 parfoldr :: (NFData a) => (a -> a -> a) -> a -> [a] -> a

这会有效,但它并不完全等同于foldr。例 上面的1将做得很好,但不是示例2。 因此,问题1是:如何定义parfoldr以具有相同类型的签名 作为折叠?

比较2折:

    input = [1..1000000]
    seqfold = foldr (+) 0
    parfold = parfoldr (+) 0

我得到了foll。在双核机器上的时间: (无线标志)

    $ ./test
    seqfold: 4.99s
    parfold: 25.16s

( - - 开启标志)

    $ ./test
    seqfold: 5.32s
    parfold: 25.55s
    $ ./test +RTS -N1
    seqfold: 5.32s
    parfold: 25.53s
    $ ./test +RTS -N2
    seqfold: 3.48s
    parfold: 3.68s
    $ ./test +RTS -N3
    seqfold: 3.57s
    parfold: 2.36s
    $ ./test +RTS -N4
    seqfold: 3.03s
    parfold: 1.70s

这些测量的观察结果:

  • 当核心数量增加时,foldr似乎会降低运行时间。 那是为什么?

  • parfold为N =>提供更好的运行时间。 3。

任何有关改进的建议和想法都值得赞赏:)

2 个答案:

答案 0 :(得分:9)

foldr通常不可并行化,因为它的接口允许顺序依赖。为了能够以您描述的方式重新排列计算,您需要将自己限制为具有标识元素的关联运算符。这称为monoid,您实施的内容基本上是并行mconcat

答案 1 :(得分:2)

您不能,不完全是因为您必须依赖于可以拆分块的属性。这意味着,当然,您必须添加额外的类型限制...特殊情况是,如果您有f :: a -> a -> a作为累积函数,并且f是关联的。

因此,您必须提供两个函数,一个用于块中,另一个用于折叠块结果。您的原始版本只是此功能的加入。

parfoldr :: NFData a => (a -> a -> a) -> a -> [a] -> a
parfoldr f = join $ parfoldr' f f

parfoldr' :: NFData b => (a -> b -> b) -> (b -> c -> c) -> b -> c -> [a] -> c
parfoldr' f g y z [] = z
parfoldr' f g y z xs = foldr g z partsRs
  where parts = chunk listChunkSize xs
        partsRs = map (foldr f y) parts `using` parList rdeepseq
然后

示例2

parfoldr' (\i acc -> (i>5) && acc) (&&) True True [1..10]

总而言之,这不是 非常丑陋。