为什么JS中递归比迭代快



我一直在LeetCode上做JS问题,注意到递归解决方案几乎在任何情况下都比迭代解决方案运行得更快(例如,带有堆栈的DFS与递归)。

我在网上查了一下,发现有几篇博客文章对性能得出了类似的结论。https://www.c-sharpcorner.com/blogs/performance-of-recursion-vs-loop-using-javascript

Oreily的书,就像大多数关于这个主题的正式材料一样,指出迭代更具表演性,因为";较低的开销";我理解,但为什么在实践中不是这样?https://www.oreilly.com/library/view/high-performance-javascript/9781449382308/ch04.html

我确实看到递归在特别大的堆栈大小下可能会失败,但它似乎总是比迭代更具性能。怎么回事?我看到TCO还没有在JS中实现,所以我很困惑。

为了理解迭代中的递归(反之亦然),我们需要了解这两种技术是如何工作的。

什么是递归?

  1. 这是一个调用self的函数。因此,调用的次数,函数属性(包括变量、数据结构、主函数使用的子函数,将一次又一次地重新创建)的次数。最后,编译器或(在JavaScript的情况下为JIT)必须面临一些开销。

  2. 该过程将继续,直到/除非找到基本情况,在这种情况下,递归过程中的所有开销都将释放。

  3. 在这种情况下,如果没有找到/匹配基本情况,那么为这个特定函数分配的所有堆栈帧都将耗尽,我们将得到一些东西,比如:Maximum call stack size exceeded

什么是迭代?

  1. 顾名思义,这是一条重复指令,将执行编码器给出的一些操作
  2. 与Recursion相比,如果没有主堆栈帧,就不会使用其他堆栈帧。并且函数的所有属性将一次又一次地用新值重新初始化

根据我所做的比较,您可以清楚地了解编译器或JIT的效率是高还是低。(它被推广到你将要研究的每一种语言)。

现在谈到性能,您提供的文章链接显然没有显示所有场景的准确测量结果。明确执行函数的时间主要取决于函数执行的指令数量。

看看下面的片段,你可以重新思考到目前为止的结论:

function nthFibo(a){
if(a <= 2){
return a;
}else{
return nthFibo(a-1)+nthFibo(a-2);
}
}

console.time("looping #1");  
console.log(nthFibo(45));
console.timeEnd("looping #1");

并检查迭代所需的时间,这将为您提供与递归相同的输出:

function nthFiboIterative(a){
var first = 0, second = 1;
var result = 0;
var i = 1;
while(i ++ <= a){
result = first + second;
first = second;
second = result;
}
return result;
}
console.time("looping #2");  
console.log(nthFiboIterative(45));
console.timeEnd("looping #2");

但这不是一个公平的测试,因为我们的第一个程序创建了一个指数过程,我们将其与第二个程序中的线性过程进行比较。让我们来看看一个递归nthFibo,它发展了一个线性迭代过程-

function nthFibo (a, x = 1, y = 1)
{ if (a == 0)
return x;
else
return nthFibo(a - 1, y, x + y)
}
console.time("looping #3");  
console.log(nthFibo(45));
console.timeEnd("looping #3");

了解过程程序(函数)之间的区别非常重要。递归函数可以演化为递归过程、指数过程或迭代过程。类似地,非递归函数可以演化为迭代过程(认为循环简单)或指数过程(认为阿克曼函数)。

最新更新