Javascript 迭代是比递归更快,还是更不容易出现堆栈溢出?

Javascript 迭代是比递归更快,还是更不容易出现堆栈溢出?,javascript,recursion,iteration,stack-overflow,Javascript,Recursion,Iteration,Stack Overflow,我知道您可以使用一个简单的循环重写递归函数,方法是使用数组作为“剩余工作”的先进先出队列。我听说这会降低堆栈溢出的可能性 但是,如果堆栈溢出不是问题(因为递归不是很深),那么有什么理由更喜欢迭代而不是递归?快一点吗 我最感兴趣的是V8上的JavaScript。在JavaScript中,它不(不需要,也可能不能?参见注释)进行尾部递归优化,递归都比迭代慢(因为在几乎所有语言中,函数调用都比跳转贵得多)如果递归太深,则可能导致堆栈溢出错误(但是,限制可能相当深;在我的实验中,Chrome放弃了递归深

我知道您可以使用一个简单的循环重写递归函数,方法是使用数组作为“剩余工作”的先进先出队列。我听说这会降低堆栈溢出的可能性

但是,如果堆栈溢出不是问题(因为递归不是很深),那么有什么理由更喜欢迭代而不是递归?快一点吗


我最感兴趣的是V8上的JavaScript。

在JavaScript中,它不(不需要,也可能不能?参见注释)进行尾部递归优化,递归都比迭代慢(因为在几乎所有语言中,函数调用都比跳转贵得多)如果递归太深,则可能导致堆栈溢出错误(但是,限制可能相当深;在我的实验中,Chrome放弃了递归深度16316)


然而,性能影响有时值得您在编写递归函数时获得清晰的代码,并且某些事情在没有递归的情况下更难做到(递归函数几乎总是比迭代函数短得多),例如使用树(但实际上Javascript并不能做到这一点编辑:GGG提到DOM是一个树,而使用它在JS中非常常见).

递归可能会更快失败,因为无限递归函数会破坏堆栈,产生程序可以恢复的异常,而迭代解决方案将运行,直到被外部代理停止

对于能够产生给定时间的有效输出的代码,递归的主要代价是函数调用开销。迭代解决方案根本没有这个代价,所以在性能关键代码中,往往不会显式地优化递归

这在基准测试中是显而易见的,但除非您编写的是性能关键型代码,否则您的用户可能不会注意到

当时的基准测试试图量化各种JS解释器中的函数调用开销。如果您担心的话,我会编写一个类似的基准测试来显式测试递归


请注意,在EcmaScript 3中很难正确执行尾部调用递归优化

例如,JavaScript中数组折叠的一个简单实现:

function fold(f, x, i, arr) {
  if (i === arr.length) { return x; }
  var nextX = f(x, arr[i]);
  return fold(f, nextX, i+1, arr);
}
无法进行尾部优化,因为调用

 fold(eval, 'var fold=alert', 0, [0])
eval('var fold=alert')
放在
fold
的主体内,导致对
fold
的看似尾部递归调用实际上不是递归的


EcmaScript 5将
eval
更改为只能通过名称
eval
调用,并且严格模式防止
eval
引入局部变量,但尾部调用优化取决于静态确定尾部调用位置的能力,这在JavaScript等动态语言中并不总是可能实现这取决于……当我在递归函数中遇到IE8中的“慢脚本”错误时,我问了同样的问题。我很惊讶迭代实际上更慢

我在一棵树中搜索一个特定的节点。我使用类似的方法将递归函数重写为迭代方式(使用堆栈保持上下文):

然而,从那以后,我开始从IE8中得到比以前多得多的“慢脚本”错误。做一些分析证实,迭代版本甚至更慢


原因可能是,在JS中使用方法调用堆栈可能比在循环中使用带有相应push()和pop()操作的数组快。为了验证这一点,我创建了一个测试,在这两种情况下模拟树行走:结果令人惊讶。而在Chrome中,迭代版本是(在我的例子中)19%慢,在IE8中,迭代版本比递归版本慢65%。

您可以尝试对其进行基准测试。如果它是一个小数组,我会选择最可读的解决方案。回答很好,解释了选择递归或交互实现时的所有注意事项。+1用于提及可维护性。这里有一个问题这就引出了一个有趣的观点,即事物的存在(非标准)<代码>函数>参数< /COD>意味着现实世界的JS引擎不能做尾调用消除……我想他们会做它,否则它是否需要。@ GGG HMM,这很有趣。我在我的答案中添加了一个注释。我也认为DOM是一棵树,和DOM一起工作在JS中非常普遍。十个迭代代码可以比递归代码短,在可读性方面大致相当……也许我应该发布一个答案,抱歉在评论中漫无边际;)@GGG,不是真的。这是出于更安全的原因。马克·米勒和我为此而努力。它们启用了某些其他优化,这有助于销售它们。我还发现,迭代实现并不总是更快。请尝试预分配数组,并通过跟踪当前索引来维护堆栈。我打赌这比推/跳快得多。e、 g.
var堆栈=新数组(大小);var stack_指数=0啊,是的,IE8,我们都用它来运行我们最新的、可读性最高的递归代码。也许最好也检查一下它的回答时间。那是在2013年。。。