Performance 链表在函数式语言中有实际的性能优势吗?

Performance 链表在函数式语言中有实际的性能优势吗?,performance,haskell,linked-list,functional-programming,scheme,Performance,Haskell,Linked List,Functional Programming,Scheme,我知道,在惰性函数语言中,链表具有生成器式的语义,在优化编译器下,当它们实际上不用于存储时,可以完全消除它们的开销 但在急切的函数式语言中,它们的使用似乎同样繁重,而优化它们似乎更加困难。Scheme之类的语言在平面数组上使用它们作为主要的序列表示,这有什么好的性能原因吗 我知道使用单链表会带来明显的时间复杂性;我更多地考虑了使用急切的单链表作为主要序列表示的实际性能后果,考虑了编译器优化。TL;DR:没有性能优势 第一个Lisp将cons(链表单元格)作为唯一的数据结构,并将其用于所有操作。现

我知道,在惰性函数语言中,链表具有生成器式的语义,在优化编译器下,当它们实际上不用于存储时,可以完全消除它们的开销

但在急切的函数式语言中,它们的使用似乎同样繁重,而优化它们似乎更加困难。Scheme之类的语言在平面数组上使用它们作为主要的序列表示,这有什么好的性能原因吗


我知道使用单链表会带来明显的时间复杂性;我更多地考虑了使用急切的单链表作为主要序列表示的实际性能后果,考虑了编译器优化。

TL;DR:没有性能优势

第一个Lisp将
cons
(链表单元格)作为唯一的数据结构,并将其用于所有操作。现在,常见的Lisp和Scheme都有向量,但对于函数式样式来说,这不是一个很好的匹配。链表可以有一个递归步骤,即在累加器前面添加零个或多个元素,最后生成一个在迭代之间共享的列表。该操作可能会执行多个递归,使多个版本共享尾部。我认为分享是链表中最重要的一个方面。如果使用极大极小算法并将状态存储在链表中,则无需复制状态的未更改部分即可更改状态

< C++ Bjarne Stroustrup的Creator提到,即使插入顺序,需要移动数据结构的加扰和双倍大小,但即使需要按顺序排列,也需要移动数据结构中的一半元素。请记住,这些是双链接列表和变异插入,但他提到,大多数时间是线性跟踪指针,获取所有CPU缓存未命中,以找到正确的位置,因此对于排序列表中的每个O(n)搜索,向量会更好

如果你有一个程序,在一个不在前面的列表中插入许多内容,那么也许树是一个更好的选择,你可以在CL和Scheme中使用
cons
。事实上,所有Chris Okasaki purly功能数据结构都可以通过
cons
实现。Haskell中大多数“可变”结构的实现与之类似


如果您在Scheme中遇到性能问题,并且在分析之后发现您应该尝试用数组替换链表操作,那么没有任何东西可以阻挡这一点。最后,所有的算法选择都有利弊。在任何语言中,困难的计算都是困难的

1)简单性2)平凡O(1)前置3)Sharing@Bergi简单性并不是真正的“性能”优势,但它是一个原因。不过,我不太明白你的分享点。假设不可变,您可以同样轻松地共享平面数组,但不能共享部分数组。列表可以共享它们的尾部。@Bergi共享是在一种渴望的语言的上下文中发生的吗?我想你可以手动操作。@DavidYoung我不是说共享惰性计算,我是说共享数据结构组件。我想这是在每一种语言中都有不可变的数据结构,不管是懒惰还是急切。应该提到C++数组/向量只有很快,因为它们默认是未装箱的(内存打包)。这允许在没有缓存未命中的情况下实际访问元素。但在动态语言(包括有限的OO动态类型)和Haskell等人中,通常只能将数组作为指针数组,这意味着如果需要在O(n)搜索中查找元素,仍然会出现缓存未命中。数组仍然比链接列表快,但几乎没有,但在C++中有这么大的因素。@左撇子我同意,但是现在我们在Haskell中有未装箱的向量。这些必须使用低级别不安全的原语来定义,并且只适用于不可装箱的类型,但是这些性能应该更接近C++,至少在左边没有,速度主要是从删除内存依赖性中获得的;指针向量仍然比值链表快得多。取消对内存的引用是非常潜在的,但并没有非常高的吞吐量。@Veedrac只有在独立于其他元素处理每个元素时,以及编译器和处理器能够很好地对指令进行重新排序,从而使延迟完全无序时,才有帮助。在实践中,这在今天通常可以正常工作,但速度仍然不如完全避免大多数延迟导致的缓存未命中时快。@leftaroundabout OoO即使在处理不是独立的情况下也可以工作,只要它大致以线性方式遍历;循环将以流水线结束,延迟将被隐藏。显然,拥有连续数据更好,但相对而言,它不那么重要。