在急切的评估中重用不可变状态的记忆?

时间:2011-03-01 07:51:16

标签: performance optimization functional-programming immutability

我正在研究纯粹的函数式语言,目前正在考虑一些不可变的数据实现。

这是伪代码。

List a = [1 .. 10000]
List b = NewListWithoutLastElement a
b

在评估b时,必须在急需/严格执行不可变数据时复制b。 但在这种情况下,a不再在任何地方使用,因此可以安全地重复使用'a'的内存以避免复制成本。

此外,程序员可以强制编译器始终通过使用某些关键字含义List标记类型must-be-disposed-after-using来执行此操作。这使得逻辑上的编译时错误无法避免复制成本。

这可以获得巨大的成就。因为它也可以应用于巨大的对象图。

您怎么看?任何实现?

1 个答案:

答案 0 :(得分:1)

这是可能的,但范围严重受限。请记住,功能程序中的绝大多数复杂值将传递给许多函数以从中提取各种属性 - 而且,大多数情况下,这些函数本身就是其他函数的参数,这意味着您无法做出任何假设关于他们。

例如:

let map2 f g x = f x, g x 

let apply f = 
  let a = [1 .. 10000]
  f a 

// in another file :

apply (map2 NewListWithoutLastElement NewListWithoutFirstElement)

这在功能代码中是相当标准的,并且无法在must-be-disposed-after-using上放置a属性,因为没有特定位置对程序的其余部分有足够的了解。当然,您可以尝试将该信息添加到类型系统中,但对此类型推断显然是非平凡的(更不用说类型会变得非常大)。

当您拥有可能在值之间共享子元素的复合对象(例如树)时情况会变得更糟。考虑一下:

let a = binary_tree [ 1; 2; 5; 7; 9 ]
let result_1 = complex_computation_1 (insert a 6)
let result_2 = complex_computation_2 (remove a 5)

为了在complex_computation_2中允许内存重用,您需要证明complex_computation_1不会改变a,不会将a的任何部分存储在result_1内1}}并在a开始工作时使用complex_computation_2完成。虽然这两个第一个要求可能看起来最难,但请记住,这是一个纯函数式语言:第三个要求实际上会导致大量性能下降,因为complex_computation_1complex_computation_2不能再在不同的线程上运行!

实际上,这绝不是绝大多数功能语言的问题,原因有三:

  • 他们有专门为此建造的垃圾收集器。他们更快地分配新内存并回收被遗弃的内存,而不是尝试重用现有内存。在绝大多数情况下,这将足够快。
  • 他们拥有已实现数据共享的数据结构。例如,NewListWithoutFirstElement已经完全重用了转换列表的内存,而没有任何努力。功能程序员(以及任何类型的程序员,实际上)基于性能考虑来确定他们对数据结构的使用是相当普遍的,并且将“删除最后”算法重写为“先删除”算法很容易。
  • 懒惰评估已经做了相同的事情:懒惰列表的尾部最初只是一个闭包,可以评估尾部,如果你需要 - 所以没有内存可以重复使用。另一方面,这意味着从示例中的b读取元素将从a中读取一个元素,确定它是否是最后一个元素,并在不需要存储的情况下返回它(可能是一个利弊单元)在那里被分配,但这种情况一直发生在函数式编程语言中,而短暂的小对象在GC中完全没问题。