【问题标题】:Stack and recursive function Maximum call stack size exceeded how to solve while keeping recursion?堆栈和递归函数最大调用堆栈大小超出了如何在保持递归的同时解决?
【发布时间】:2015-11-01 22:23:07
【问题描述】:

我将其添加为问答 wiki,因为我遇到过几次。

这一次是针对行读取器和处理器,可以从流中逐行馈送大型数组或行。

由于 getLine 会调用 lineHandler,而 lineHandler 会调用 getLine,因此在处理数十万行时,堆栈会变得太大。

为了解决这个问题,我使用 setTimeout 来清除调用堆栈,而不是直接调用函数。下面是一些示例代码:

function startPromise(){
  return new Promise(function(resolve,reject){
      resolve();
  })
}
var lineCount=0;
var totalLines=2000000;
var totalCount=0;
var linesProcessed=0;
function processFile(){console.log('we are done');}
function lineHandler(){
  if(linesProcessed%100===0){
    console.log('processed:',linesProcessed);
  }
  lineCount++;
  linesProcessed++;
  setTimeout(getLine,0);
  return;
}
function getLine(){
 startPromise()
  .then(function(){
    if(lineCount>=totalLines){
      setTimeout(processFile,0);
      return;
    }
    setTimeout(lineHandler,0);
  })
  .then(null,function(error){//failed to process this line
    console.log('fail:',error);
  })
}
getLine();

由于每行有 2 次超时,此代码运行速度非常慢(每秒 100 行)。这太慢了,因为我希望在一小时内处理 1000 万。

【问题讨论】:

  • 您的标题未能提出旨在解决问题的问题。相反,您要问的是,当完全不同的解决方案可能会更好时,如何使您提出的解决方案发挥作用。查看更多:XY problem.

标签: node.js recursion stack-overflow settimeout


【解决方案1】:

我把所有的 setTimeout 调用都换成了 speedup,speedup 函数如下:

var speedup = (function() {
  var stack=0;
  return function(fn,t){
    stack++;
    //if call stack gets over 5000 then do a setTimeout
    //  to reset the stack
    if(stack%5000===0){
      setTimeout(fn,t);
      stack=0;
    }else{
      fn();
    }
  };
}())

[更新]

使用原生 Promise 无需使用 setTimeout 来控制执行流程并保持堆栈短。一些库可能不会中断或使用 setTimeout (=slow down),但尝试了没有 setTimeout 的代码,即使进行了 50000 次递归也没有耗尽堆栈空间。

【讨论】:

  • 无论您在做什么,这似乎都不是正确的解决方案。看起来流将是一个更好的解决方案。
  • @naomik 感谢您的回复,lineProcessor 可以用于多种用途,巨大的数组就是其中之一。你建议我应该把它转换成一个流或迭代器(有点像有一段时间(lines.next()))?目前我正在使用递归,因为它让我有机会替换 lineHandler 和 getLine 函数。
  • 你能举一个输入和预期输出的例子吗?
  • @naomik,输入可以来自文件、http post或大数组。取决于从哪里实现 getLine。该行被提供给 lineHandler 函数,该函数将使用具有 toString 方法的 documentFactory 生成 csv 行、json 或 xml 输出。 lineHandler 会将处理后的行发送到一个发送函数,该函数可以返回一个指示 lineHandler 应该暂停的对象。如果是这种情况,那么 lineHanler 应该每半秒调用一次 getLine,直到发送返回继续。示例代码被剥离了所有这些。
  • 由于所有这些复杂性,我决定 getLine 应该调用一个 lineHandler,它会在完成后调用 getLine。发送可能会失败,但需要发送以记录此失败。 getLine 和 lineHandler 应该尽可能快地获取线路并给予发送。
猜你喜欢
  • 2021-07-24
  • 1970-01-01
  • 2012-03-18
  • 1970-01-01
  • 2014-01-18
  • 2019-09-09
  • 2016-08-20
  • 2011-11-24
  • 2011-01-30
相关资源
最近更新 更多