性能与列表的对比
在Numeric Haskell Repa Tutorial Wiki中,有一段文字(用于上下文):
10.1融合,以及为什么你需要它
修复严重取决于阵列融合以实现快速代码。 Fusion编译你的程序时,融合是GHC执行的内联和代码转换的组合。 融合过程将Repa库中定义的数组填充循环与您在自己的模块中编写的“worker”函数进行合并。 如果融合过程失败,那么产生的程序将比需要的慢得多,通常比使用普通Haskell列表的等效程序慢10倍。 另一方面,提供的融合工作,所产生的代码将运行得如同一个相当干净的C程序一样快。 一旦你明白了发生了什么,做融合工作并不难。
我不明白的部分是这样的:
“如果融合过程失败,那么最终的程序将比需要的慢得多,通常比使用普通Haskell列表的等效程序慢10倍。”
我明白为什么如果流聚合失败会运行得更慢,但为什么它比列表慢呢?
谢谢!
编辑:这是不正确的 - 参见唐纳尔逊的评论(和他的回答 - 他比我知道更多关于图书馆的信息)。
不可变阵列不能共享组件; 无视融合,对不可变数组的任何修改都必须重新分配整个数组。 相反,虽然列表操作是非破坏性的,但他们可以共享部分:例如, fi (h:t) = i:t
通过创建一个新列表来替换列表中的头部,其中第一个单元格指向到原始列表的第二个单元格。 此外,因为列表可以逐步构建,所以通过重复调用函数来构建列表的生成器的函数仍然可以在O(n)时间内运行,而不带融合的不可变数组上的等价函数将需要重新分配数组每次调用该函数,需要O(n ^ 2)次。
通常,因为列表是懒惰的,并且Repa数组是严格的。
如果你没有融合懒惰列表遍历,例如
map f . map g
您需要为每个值支付O(1)成本,以便将中间(惰性)缺陷单元留在那里。
如果你不能在一个严格的序列中融合相同的遍历,你至少要花费O(n)来分配一个严格的中间数组。
此外,由于融合将您的代码转换为无法识别的Stream
数据类型,为了改善分析,您可以留下代码,这些代码只有太多的构造函数和其他开销。