修复绩效与列表

时间:2012-12-03 20:25:53

标签: haskell ghc repa

在Numeric Haskell Repa Tutorial Wiki中,有一段读取(用于上下文):

  

10.1 Fusion,以及为什么需要它

     

Repa主要依靠阵列融合来实现快速代码。 Fusion是一个奇特的名字   GHC执行的内联和代码转换的组合   它编译你的程序。融合过程合并阵列填充   在Repa库中定义的循环,具有“worker”功能   你在自己的模块中写。如果融合过程失败,那么   结果程序将比它需要的慢很多,通常是10倍   使用普通Haskell列表减慢等效程序。在另一   手,提供融合工作,结果代码将运行速度快   相当干净的C程序。融合工作并不难   一旦你明白发生了什么。

我不明白的部分是:

  

“如果融合过程失败,那么   结果程序将比它需要的慢很多,通常是10倍   使用普通Haskell列表减慢等效程序。“

我理解为什么如果流融合失败会导致运行速度变慢,但为什么运行速度比列表慢得多?

谢谢!

2 个答案:

答案 0 :(得分:9)

通常,因为列表是惰性的,并且Repa数组是严格的。

如果你没有融合懒惰列表遍历,例如

map f . map g

你支付 O(1)每个值的成本,以便在那里留下中间(懒惰)cons单元格。

如果您未能通过严格的序列融合相同的遍历,则每个值至少支付 O(n)以分配严格的中间数组。

此外,由于融合将您的代码转换为无法识别的Stream数据类型,为了改进分析,您可能会留下具有太多构造函数和其他开销的代码。

答案 1 :(得分:3)

编辑:这是不正确的 - 请参阅Don Nelson的评论(以及他的答案 - 他对图书馆的了解远远超过我所知道的。)

不可变数组不能共享组件;无视融合,对不可变数组的任何修改都必须重新分配整个数组。相比之下,虽然列表操作是非破坏性的,但它们可以共享部分:f i (h:t) = i:t,例如,通过创建一个新列表来替换列表的头部,其中第一个单元格指向第二个单元格。原始清单。此外,因为列表可以逐步构建,所以通过重复调用函数构建列表的生成器等函数仍然可以在O(n)时间内运行,而不融合的不可变数组上的等效函数需要重新分配数组。每次调用该函数,花费O(n ^ 2)时间。