【发布时间】:2013-09-23 02:20:19
【问题描述】:
我有一个基本上执行以下操作的计算:
f :: [a] -> ([b],Bool)
这个函数其实可以写
f = foldr h ([],False) . map g
where h (b,bool) (bs,boolSoFar) = (b:bs,bool || boolSoFar)
g :: a -> (b,Bool) 是一些需要花费大量时间的函数。 f 通常在小列表上调用,因此尝试并行计算地图似乎很有趣。这可以通过 Control.Parallel.Strategies parMap 来完成。所以现在我们使用
f = foldr h ([],False) . parMap rseq g
where h (b,bool) (bs,boolSoFar) = (b:bs, bool || boolSoFar)
这一切都很好。现在,您会注意到可以在f 的第一个定义中执行顺序优化。即,我可以使用 map-fold fusion 将其编写为单个折叠,因此循环遍历列表。但是,这样我就失去了并行处理的好处。
现在,有人可能会说,在f 的第二个定义中,再次循环遍历列表并不是那么糟糕,那么为什么不直接这样做呢。我想我在想的是,如果 Haskell 有可变变量,那么可以在 map 的主体中更新这个布尔变量(我想你必须锁定和解锁它)。对做这样的事情有什么建议吗?
【问题讨论】:
-
我非常怀疑使用可变变量来包含布尔值是否会使其更快。您是否对代码运行了分析器以查看减速发生的位置?当您的效率问题出在其他地方时,您可能过于专注于优化一些简单的事情。您确定
g正在并行评估,还是并行创建 thunk 并让单核foldr进行评估? -
您好,感谢您的评论。实际上,如果这可能,我想知道更多。我一直在玩分析,什么不是。当然,大部分时间都花在地图上。我知道这一点,并在问题中说明。但是我已经看到这种模式出现在我的代码中不止一个地方,我并行执行一些操作,然后按顺序执行其他操作 - 顺序部分是附加到计算末尾的东西并且可以完成在并行部分有一个共享变量。所以我在问如何在 Haskell 中做到这一点。
-
我刚刚写了一些相当幼稚的东西作为概念证明。使用 Chan 而不是 MVar,我将是第一个承认代码没有完全优化的人,但是
map和parMap之间的差异可以忽略不计,它们和使用 Chans 之间的差异是一个数量级慢点。这是一个很好的例子,说明 GHC 如何在不需要引入复杂的线程机制的情况下自行很好地处理事情。 -
如何判断我是在并行创建 thunk 并通过 foldr 正常评估还是实际并行评估?有没有一种方法可以让我分析并查看设置并行执行的开销是否否定了并行处理的好处?
-
这里haskell.org/haskellwiki/Performance/Strictness 详细描述了几种技术,但一般要点是使用a) 模式匹配、b) 严格的
$!运算符和c) BangPatterns。就我个人而言,我喜欢使用$!,因为它简短而甜美,但并非在所有情况下都适用,因此 BangPatterns 也非常有用,例如let !result = expr in result。
标签: haskell parallel-processing