为什么 ECMAScript 2017 中的尾调用优化对函数性能有重要影响

从ECMAScript 6开始,Tail Call Optimization(尾调用优化,TCO)已经成为该规范的一部分。尾调用优化是指JavaScript引擎可以优化函数中的最后一次函数调用,使其不再出现在函数调用栈中。这是通过在返回致命的调用时重用当前帧来实现的。从而可以大大减少函数调用时的内存占用和运行时的时间开销。

尾调用优化在递归函数中特别适用。递归函数调用本身就很消耗内存,因此尾调用优化可以有效地解决这个问题,使递归函数能更安全地使用。

让我们看看尾调用优化如何提高递归函数的性能。下面是一个简单的递归函数,用于计算斐波那契数列的第n项:

该函数非常简单,但由于它是递归的,因此当n的值很大时,它的性能显著下降。这是因为每次递归调用都需要在堆栈中创建新的函数调用帧,将函数返回后,这些框架会保留在堆栈中,直到所有调用完成。

现在,让我们重写这个函数,以便使用尾调用优化:

这里有什么不同之处?首先,我们将第三个参数acc2重命名为斐波那契数列的前一个数字,并将第二个参数acc1重命名为当前的斐波那契数列。这是因为尾调用优化需要将所有先前计算的数字传递给下一次递归调用,并用它们替换当前数字。

这个新的函数现在也处理了一个计算斐波那契数列最后一位的变量n。但是,我们在递归调用中只传递了三个参数,而不是四个。

这就是尾调用优化真正的魔力。当递归调用的函数返回时,它将返回到调用该函数的函数,而不是创建一个新的帧,这将清除调用堆栈,从而减少内存使用和执行时间。

虽然这个例子看起来很简单,但请注意,尾调用最大的好处在于它在处理大型递归函数或长时间运行的递归代码时的表现。但是,这种优化并不总是产生效果,对于某些类型的递归函数,改为迭代可能更快。

下面是一个更多的例子以说明这个问题,接下来我们将修改斐波那契数列函数,使它更长时间运行。我们将使用Measuring Execution Time(算法执行时间)库来测试函数的执行时间(这里我们假设你能够安装和使用该库)。

上面的代码将分别测试两个函数斐波那契数列和TCO斐波那契数列的执行时间。在经过几次运行之后,您将看到一个显着的时间差异。尾调用优化(即TCO斐波那契数列)的执行时间要比基本递归(即普通斐波那契数列)要少得多。

总结:

尾调用优化可大大减少函数调用时的内存占用和运行时的时间开销。 它在处理大型递归函数或长时间运行的递归代码时是非常有效的,但并不总是产生效果。某些类型的递归函数可能会有更快的替代方法,例如迭代。

为了在您的JavaScript代码中充分利用尾调用优化,请始终使用Tail Recursive结构来处理递归问题,并通过可用的性能测试库(如Measuring Execution Time)来测试代码的性能。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/654dc4697d4982a6eb72916b


纠错
反馈