递归的尾调用是否有性能优化

本文最后更新于:2022年4月22日 上午

递归的尾调用是否有性能优化?

我们在网络上看到 js 的性能优化的方式中,经常会看到函数的尾调用,那么实际是否有优化呢,查了对应的资料后发现,虽然在 es6 的标准中已经提出了尾调用优化(PTC),但是目前除了 safari 浏览器实现了之外,其它浏览器的引擎均未实现

示例:

function f(n) {
  if (n <= 0) {
    return 'foo';
  }
  return f(n - 1);
}

console.log(f(1000000) === 'foo');

追其原因,chromejs引擎 v8团队在2016年的时候发布了一遍博客 ES2015, ES2016, and beyond,这篇博客指出了尾调用存在的两个问题

  • 很难辨别哪些函数是在正确的尾调用

  • 尾调用会清空堆栈帧,会丢失有关执行流程的信息

&ensp;&ensp;&ensp;&ensp;- 由于堆栈帧不连续性,很难在调试期间确认当前在某一个点

&ensp;&ensp;&ensp;&ensp;- error.stack 中只有较少的执行流程信息,这可能会在分析客户端错误时对错误收集产生破坏

通过 shadow stack 可以实现查看堆栈信息,它内部会复制一份当前维护的堆栈信息,最大可以显示 128 条调用栈信息。但是依旧可能会产生开发和生产环境不一致的问题,且在性能方面过于昂贵,但当时v8团队其实是实现了(PTC)的,只需要在 执行的时候加上 --harmony-tailcalls--harmony-explicit-tailcalls,但是后面将该补丁进行了移除

You can test out each version in the meantime by using the V8 flags --harmony-tailcalls and --harmony-explicit-tailcalls. Update: These flags have been removed.

The tail call implementation is hidden behind the --harmony-tailcalls
flag, which is off-by-default (and has been unstaged since February).
It is known to be broken in a variety of cases, including clusterfuzz
security issues (see sample Chromium issues below). To avoid letting
the implementation bitrot further on trunk, this patch removes it.

出于以上原因,v8拒绝实现该尾调用,以至于到目前对于该标准的实现都处于僵局的情况,但是v8 提出了一种新的显示指定尾调用的方式 (STC),且得到了 MozillaMicrosoft 的委员会成员共同支持,但是 Apple 处于反对状态。

它的示例如下,在需要使用尾调用的时候 通过 return continue关键字,会显示的开启尾调用,不会新增新的堆栈,并且还会检查是否有语法错误,(例如:return continue 1 + foo()) 就会报出语法错误

function factorial(n, acc = 1) {
  if (n === 1) {
    return acc;
  }

  return continue factorial(n - 1, acc * n)
}

总结

所以,就目前来说,只有 Safari 实现了尾递归的优化,其它浏览器都没有对其进行支持

参考资料

https://kangax.github.io/compat-table/es6/

https://v8.dev/blog/modern-javascript

https://es6.ruanyifeng.com/#docs/function#尾调用优化

https://github.com/tc39/proposal-ptc-syntax

https://chromium.googlesource.com/v8/v8.git/+/1769f892cef0822e6a8b5334e2ad909a0c33e906


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处。