我的目标是具有并行折叠功能。起初,似乎 相当直接地实现,这就是我的想法:
首先根据数量将输入列表分解为分区
核心(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。
任何有关改进的建议和想法都值得赞赏:)
答案 0 :(得分:9)
答案 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]
总而言之,这不是 非常丑陋。