纯函数式编程的效率

Opt*_*Opt 395 algorithm performance functional-programming

有没有人知道什么是最简单的渐近减速,当编程纯粹功能而不是命令性(即允许副作用)时可能发生?

来自itowlson评论的澄清:有没有哪个问题最着名的非破坏性算法渐渐比最着名的破坏性算法更糟糕,如果是这样的话多少呢?

Bri*_*ell 528

根据Pippenger [1996]的说法,当将一个纯函数的Lisp系统(并且具有严格的评估语义,而不是懒惰)与可以改变数据的系统进行比较时,可以翻译为在O(n)中运行的不纯Lisp编写的算法在纯粹的Lisp中运行在O(n log n)时间内的算法(基于Ben-Amram和Galil [1992]关于仅使用指针模拟随机存取存储器的工作).Pippenger还确定有一些算法可以做到最好; 不纯系统中存在O(n)的问题,即纯系统中的Ω(n log n).

关于这篇论文,有几点需要注意.最重要的是它没有解决惰性函数语言,例如Haskell.Bird,Jones和De Moor [1997]证明Pippenger构造的问题可以在O(n)时间内用懒惰的函数式语言解决,但是他们没有建立(据我所知,没有人有)不是一种懒惰的函数式语言可以解决与具有变异的语言相同的渐近运行时间内的所有问题.

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

实际上,许多算法可以用纯函数语言实现,效率与具有可变数据结构的语言相同.有关用于有效实现纯功能数据结构的技术的良好参考,请参阅Chris Okasaki的"Purely Functional Data Structures"[Okasaki 1998](这是他的论文的扩展版本[Okasaki 1996]).

任何需要在纯功能数据结构上实现算法的人都应该阅读Okasaki.通过使用平衡二叉树模拟可变内存,每次操作总是可以减少O(log n)减速,但在许多情况下,你可以做得更好,而Okasaki描述了许多有用的技术,从摊销技术到实际按时递增摊销工作的时间.纯功能数据结构可能有点难以使用和分析,但它们提供了许多好处,如参考透明性,有助于编译器优化,并行和分布式计算,以及版本控制,撤消和回滚等功能的实现.

另请注意,所有这些都只讨论了渐近运行时间.许多用于实现纯功能数据结构的技术会为您提供一定量的常数因子减速,这是因为它们需要额外的簿记工作,以及相关语言的实现细节.纯函数数据结构的好处可能超过这些常数因子减速,因此您通常需要根据所讨论的问题进行权衡.

参考

  • 皮平格是这个问题无可争议的权威.但我们应该强调他的结果是*理论*,不实用.在使功能数据结构变得实用和高效时,你不能比Okasaki做得更好. (49认同)
  • itowlson:我必须承认,我没有读过足够的Pippenger来回答你的问题; 它发表在Okasaki引用的同行评审期刊上,我阅读了足够的内容,以确定他的说法与这个问题有关,但还不足以理解证据.我得到的实际结果的直接结果是,通过简单地使用平衡模拟可修改的内存,将O(*n*)不纯的算法转换为O(*n*log*n*)纯算法是微不足道的.二叉树.有些问题不能做得更好; 我不知道他们是否纯粹是理论上的. (6认同)
  • 重要的一点:如果你最终要自动或手工将一个纯粹的功能规范精心地转换成一个更复杂的_efficient_纯函数实现,那么它几乎没有什么好处 - 将它转换成更高效的不纯代码.如果你可以将它保存在笼子里,例如将其锁定在一个外部无效的功能中,那么杂质并不是什么大问题. (4认同)
  • Pippenger结果做出两个限制其范围的重要假设:它考虑"在线"或"反应"计算(不是通常的模型映射有限输入到单个输出)和"符号"计算,其中输入是序列原子,只能测试相等性(即输入的解释非常原始). (3认同)
  • 非常好的答案; 我想补充一点,对于纯函数式语言,没有普遍认同的计算复杂性模型,而在不纯的世界中,单位成本RAM机器是相对标准的(因此这使得比较事情更加困难).还要注意,通过查看纯语言中的数组实现(每个操作需要lg(n)(并获得历史记录)),可以非常容易地直观地解释纯/不纯的Lg(N)差异的上限. . (2认同)
  • @Jules这不是我在回答结尾说的话吗?“由于纯粹的功能性数据结构需要额外的簿记工作以及有关语言的实现细节,因此许多用于实现纯功能数据结构的技术会给您一定程度的恒定因子减慢。” 整个答案是关于以下事实:在某些情况下,渐进式减速不可避免。我不确定您要添加什么。 (2认同)

jkf*_*kff 44

确实有几种算法和数据结构,即使有懒惰,也没有渐近有效的纯函数解(在纯lambda演算中可实现的解).

  • 上述联合发现
  • 哈希表
  • 数组
  • 一些图算法
  • ...

但是,我们假设在"命令式"语言中,对内存的访问是O(1),而理论上不能如此渐近(即对于无界问题大小)和对大型数据集中的内存的访问总是O(log n) ,可以用函数式语言模拟.

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

  • 如果数据集适合物理内存,则访问它是O(1),因为可以找到读取任何项目的时间量的绝对上限.如果数据集没有,那么你谈论的是I/O,这将是迄今为止的主导因素,但程序是写的. (3认同)
  • 我认为与函数式编程相比,命令式编程所获得的最大好处之一就是能够对一个状态的许多不同方面进行引用,并生成一个新状态,从而使所有这些引用都指向该新状态的相应方面。使用函数式编程将需要使用查找操作来替换直接解引用操作,以查找当前总体状态的特定版本的适当方面。 (2认同)

Pas*_*uoq 36

本文声称,union-find算法的已知纯函数实现都具有比它们发布的渐近复杂度更低的渐近复杂度,它具有纯粹的功能接口但在内部使用可变数据.

事实上,其他答案声称永远不会有任何差异,例如,纯功能代码的唯一"缺点"是它可以并行化,让您了解功能编程社区在这些问题上的知情/客观性.

编辑:

下面的评论指出,对纯函数式编程的优缺点的偏见讨论可能不是来自"函数式编程社区".好点子.也许我认为的倡导者只是引用一个评论,"文盲".

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

如果严格的函数在严格的语言中具有O(f(n))复杂度,那么它在惰性语言中也具有复杂度O(f(n)).为什么要担心?:)