【问题标题】:Memory leak while using tail-recursion使用尾递归时内存泄漏
【发布时间】:2019-06-16 19:17:18
【问题描述】:

想象一下我有这个假设的函数(在 javascript 中):

function paginationRecursive(results) {
  let intermediate = retrieveNextPage();
  results = results.concat(intermediate)
  if (haveAllResults()) return results;
  return paginationRecursive(results);
}

intermediate 的每个值是否会一直保存在内存中,直到整个递归处理完成,因此每次递归调用都会增加内存使用量?或者引擎/gc 是否足够聪明,可以在我们调用“更深”的任何时候释放此内存,因为它知道该变量将永远不会被再次使用?

它可能是特定于引擎的(例如V8SpiderMonkeyChakraetc.)所以为了使这个问题不笼统,我更愿意知道 V8 引擎的答案.

【问题讨论】:

  • 从你的标题来看,你的真实代码中一个实际的内存问题吗?顺便说一句,这不是泄漏,因为肯定会在函数结束后清理内存。
  • 我没有内存问题,但我正在考虑使用这种方法实现一个功能。我知道当函数结束时内存会被释放,但是在实际函数结束之前处理一个(或多个)调用更深时它会释放内存吗?
  • 我认为这并不重要。堆栈帧将被保留,是的,因为 V8 仍然没有正确实现尾调用,但是为 intermediate 变量分配的内存可以忽略不计。
  • 嗯,你不能假设。我们不知道intermediate 变量中包含的数据的大小。据我们所知,每次可能是 500mb。但是你所说的 V8 没有正确实现尾调用是我感兴趣的。所以如果 GC 需要更多内存,它不会释放所有以前的 intermediate 值?
  • 我强烈建议在问题中提及“尾调用”或“尾递归”。这可能会被标记为 stackoverflow.com/questions/37224520/… 的副本

标签: javascript recursion memory-leaks garbage-collection tail-recursion


【解决方案1】:

我认为目前的答案是“intermediate 变量应该被释放,但它不会被释放”。

ES6 确实提到了tail-position calls,甚至需要优化:

尾部位置调用必须释放任何瞬态内部 与当前执行的函数执行相关的资源 调用目标函数或重用这些资源之前的上下文 支持目标函数。

JS 中 TCO(尾调用优化)的一个不错的概述是 http://2ality.com/2015/06/tail-call-optimization.html

话虽如此,该页面链接到该表,表明实际上几乎没有运行时支持它:https://kangax.github.io/compat-table/es6/#test-proper_tail_calls_(tail_call_optimisation)

This r/node reddit thread 提供了一些见解和指向this v8 blog post 的链接,其中包括:

[...] V8 团队强烈支持用特殊符号表示正确的尾调用 句法。有一个待定的 TC39 提案,称为语法尾调用 指定这种行为,由委员会成员共同倡导 Mozilla 和微软。 [...] V8 团队计划解决 发货前下一次 TC39 会议上的问题 默认情况下尾调用或语法尾调用。

也就是说,V8 不想按照规范实现 ES6 功能,而是更喜欢尾部调用的显式语法,例如 the ones proposed for TC39

另见:Are functions in JavaScript tail-call optimized?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-02-08
    • 2014-12-21
    • 2021-07-11
    • 2010-11-04
    • 2012-10-31
    • 2012-08-31
    • 2021-10-06
    相关资源
    最近更新 更多