关于算法:纯函数式编程的效率

Efficiency of purely functional programming

是否有人知道,当编程完全是功能性的而不是强制性的(即允许副作用)时,可能发生的最糟糕的渐进式减速是什么?

伊托尔森评论中的澄清:是否存在任何问题,其中最著名的非破坏性算法渐进地比最著名的破坏性算法更差,如果是,则差多少?


根据Pippenger[1996]的观点,当比较一个纯粹的功能性(并且具有严格的评估语义,而不是懒惰的)Lisp系统和一个可以改变数据的系统时,为在O(n)中运行的不纯Lisp编写的算法可以翻译为在O(n logn)时间内运行的纯Lisp中的算法(基于Ben Amram和Galli[1992]AB的工作)。只使用指针模拟随机存取存储器)。Pippenger还确定,有一些算法是你能做的最好的;在不纯系统中有一些问题是O(n),在纯系统中是Ω(n logn)。好的。

关于这篇论文,有几点需要注意。最重要的是,它不处理懒惰的函数语言,如haskell。Bird、Jones和De Moor[1997]证明了Pippenger构造的问题可以在O(n)时间内用一种懒惰的函数语言来解决,但它们不能确定(据我所知,没有人能够确定)一种懒惰的函数语言是否能在与一种突变语言相同的渐进运行时间内解决所有问题。好的。

Pippenger提出的问题需要专门构造Ω(n logn)来实现这一结果,并不一定代表实际的现实问题。对这个问题有一些意想不到的限制,但这对证明工作是必要的;特别是,这个问题要求在线计算结果,而不能够访问未来的输入,并且输入由来自一组无限可能原子的原子序列组成,而不是由一组固定大小的原子组成。本文只建立了线性运行时间不纯算法的(下界)结果,对于需要较大运行时间的问题,在较大运行时间的算法所需的额外操作过程中,线性问题中的额外O(log n)因子可能被"吸收"。Ben Amram[1996]简要探讨了这些澄清和开放性问题。好的。

在实践中,许多算法可以在纯函数语言中实现,其效率与具有可变数据结构的语言相同。关于高效实现纯功能数据结构的技术,请参阅Chris Okasaki的"纯功能数据结构"【Okasaki 1998年】(这是他的论文的扩展版本【Okasaki 1996年】)。好的。

任何需要在纯功能数据结构上实现算法的人都应该阅读《冈崎》。通过使用平衡二叉树模拟可变内存,每次操作都会遇到最坏的O(log n)减速,但在许多情况下,您可以做得比这更好,并且冈崎介绍了许多有用的技术,从摊余技术到以增量方式进行摊余工作的实时技术。纯功能数据结构可能有点难以使用和分析,但它们提供了许多好处,例如在编译器优化、并行和分布式计算以及版本控制、撤消和回滚等功能的实现中有帮助的引用透明性。好的。

还要注意,所有这些只讨论渐进运行时间。许多实现纯功能数据结构的技术会给您带来一定量的常量因子减速,这是因为它们需要额外的簿记,以及所讨论语言的实现细节。纯功能数据结构的好处可能超过这些常量因子的减速,因此您通常需要根据所讨论的问题进行权衡。好的。工具书类

  • 本阿姆兰,阿米尔和加利利,ZVI 1992年。"关于指针与地址的对比"ACM杂志,39(3),第617-648页,1992年7月
  • Ben Amram,Amir,1996年。"关于皮蓬对纯粹与不纯口齿不清的比较的注释",未发表的手稿,丹麦哥本哈根大学迪库
  • Bird、Richard、Jones、Geraint和De Moor,Oege,1997年。"更快、更快:懒惰与渴望的评估",《功能编程杂志》第7期,第5页,541-547页,1997年9月
  • Okasaki,Chris,1996年。"卡内基梅隆大学纯粹功能数据结构博士论文
  • 1998年,克里斯·冈崎。纯功能数据结构",剑桥大学出版社,英国剑桥
  • 皮蓬,尼古拉斯,1996年。纯Lisp与不纯Lisp"ACM编程语言原理研讨会",第104-109页,1996年1月

好啊。


确实有几种算法和数据结构,对于这些算法和数据结构,即使存在惰性,也不知道渐进有效的纯函数解(t.i.在纯lambda微积分中可实现的一个)。

  • 上述工会发现
  • 哈希表
  • 数组
  • 一些图形算法

然而,我们假设在"命令式"语言中,对内存的访问是O(1),而在理论上,不可能是渐进的(即对于无边界的问题大小),在一个巨大的数据集中对内存的访问总是O(log n),可以用函数式语言来模拟。

此外,我们必须记住,实际上所有现代函数语言都提供可变的数据,Haskell甚至在不牺牲纯度的情况下提供这些数据(St Monad)。


本文声称已知的union-find算法的纯函数实现都比它们发布的具有纯函数接口但在内部使用可变数据的算法具有更差的渐进复杂性。

其他答案声称永远不会有任何区别,例如,纯函数代码的唯一"缺点"是它可以并行化,这让您了解函数编程社区在这些问题上的信息性/客观性。

编辑:

下面的评论指出,纯函数编程的优缺点的有偏见的讨论可能不是来自"函数编程社区"。好点。也许我看到的拥护者只是引用了一句话,"文盲"。

例如,我认为这个博客文章是由一个可以说是功能编程社区代表的人写的,因为它是一个"懒惰评估的要点"列表,所以它是一个很好的地方来提到懒惰和纯功能编程可能存在的任何缺点。一个好的地方可以代替以下(技术上是正确的,但有点不好笑)解雇:

If strict a function has O(f(n)) complexity in a strict language then it has complexity O(f(n)) in a lazy language as well. Why worry? :)


对于内存使用的固定上限,应该没有区别。

证明草图:给定一个固定的内存使用上限,应该能够编写一个虚拟机,该虚拟机以相同的渐进复杂性执行命令集,就好像您实际上在该机器上执行一样。这是因为您可以将可变内存作为持久的数据结构来管理,给O(log(n))提供读和写,但是使用固定的内存使用上限,您可以拥有固定数量的内存,从而使这些内存衰减为O(1)。因此,功能实现可以是运行在虚拟机功能实现中的必要版本,因此它们都应该具有相同的渐进复杂性。


我建议阅读haskell的性能,然后看看函数语言和过程/oo语言的基准游戏性能。