Performance 在即时评估中重用不可变状态的内存?
我正在学习纯函数式语言,目前正在考虑一些不变的数据实现 这是一个伪代码Performance 在即时评估中重用不可变状态的内存?,performance,optimization,functional-programming,immutability,Performance,Optimization,Functional Programming,Immutability,我正在学习纯函数式语言,目前正在考虑一些不变的数据实现 这是一个伪代码 List a = [1 .. 10000] List b = NewListWithoutLastElement a b 在评估b时,必须复制b,以急切/严格地实现不可变数据。 但在这种情况下,a不再在任何地方使用,因此可以安全地重复使用“a”的内存,以避免复制成本 此外,程序员可以通过在类型列表上标记一些关键字来强制编译器始终这样做,这意味着必须在使用后处理。这使得逻辑上的编译时错误无法避免复制成本 这可以获得巨大的性能
List a = [1 .. 10000]
List b = NewListWithoutLastElement a
b
在评估b
时,必须复制b
,以急切/严格地实现不可变数据。
但在这种情况下,a
不再在任何地方使用,因此可以安全地重复使用“a”的内存,以避免复制成本
此外,程序员可以通过在类型列表
上标记一些关键字来强制编译器始终这样做,这意味着必须在使用
后处理。这使得逻辑上的编译时错误无法避免复制成本
这可以获得巨大的性能。因为它也可以应用于大型对象图
你觉得怎么样?任何实现?这都是可能的,但范围受到严重限制。请记住,函数程序中的绝大多数复杂值都会传递给许多函数以从中提取各种属性,而且,大多数情况下,这些函数本身就是其他函数的参数,这意味着您无法对它们进行任何假设 例如:
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)
这在功能代码中是相当标准的,并且在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)
为了允许在复杂计算\u 2
中重用内存,您需要证明复杂计算\u 1
不会改变a
,不会在结果\u 1
中存储a
的任何部分,并且在复杂计算\u 2
开始工作时使用a
完成。虽然前两个要求看起来最难,但请记住这是一种纯函数式语言:第三个要求实际上会导致性能大幅下降,因为complex\u computation\u 1
和complex\u computation\u 2
不能再在不同的线程上运行了
实际上,这在绝大多数函数式语言中都不是一个问题,原因有三:
- 他们专门为此设计了一个垃圾收集器。对他们来说,只分配新内存并回收废弃的内存比尝试重用现有内存更快。在绝大多数情况下,这将足够快
- 他们的数据结构已经实现了数据共享。例如,
newlistwithoutfirstement
已经提供了转换列表的内存的完全重用,而无需任何努力。函数式程序员(以及任何类型的程序员)基于性能考虑来确定数据结构的使用是相当常见的,而将“remove last”算法重写为“remove first”算法是很容易的
- 惰性计算已经做了一些等效的事情:惰性列表的尾部最初只是一个闭包,如果需要,它可以计算尾部,因此没有可重用的内存。另一方面,这意味着在您的示例中,从
b
中读取一个元素将从a
中读取一个元素,确定它是否是最后一个元素,并返回它而不需要存储(一个cons单元可能会被分配到其中的某个地方,但在函数式编程语言中这种情况经常发生,而对于GC来说,短命的小对象是完全合适的)
这是可能的,但范围非常有限。请记住,函数程序中的绝大多数复杂值都会传递给许多函数,以从中提取各种属性,而且,大多数情况下,这些函数本身就是其他函数的参数,这意味着您无法对其进行任何假设他们
例如:
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)
这在函数代码中是相当标准的,并且在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)
为了允许在复杂计算\u 2
中重用内存,您需要证明复杂计算\u 1
不会改变a
,不会在结果\u 1
中存储a
的任何部分,并且在复杂计算\u 2
开始工作时使用a
完成第一个要求似乎最难,请记住这是一种纯函数式语言:第三个要求实际上会导致性能大幅下降,因为complex\u computation\u 1
和complex\u computation\u 2
不能再在不同的线程上运行了
实际上,这在绝大多数函数式语言中都不是一个问题,原因有三:
- 他们专门为此构建了一个垃圾收集器。对于他们来说,分配新内存并回收废弃的内存比尝试重用现有内存更快。在绝大多数情况下,这将足够快
- 它们的数据结构已经实现了数据共享。例如,
newlistwithoutfirstement
已经可以毫不费力地完全重用转换后的列表的内存。这对于函数式程序员(以及任何类型的pr