use*_*183 68 stack-overflow recursion callstack node.js
当我运行我的代码时,Node.js抛出"RangeError: Maximum call stack size exceeded"了太多递归调用引起的异常.我试图增加Node.js堆栈大小sudo node --stack-size=16000 app,但Node.js崩溃没有任何错误消息.当我在没有sudo的情况下再次运行时,Node.js打印出来'Segmentation fault: 11'.有没有可能在不删除递归调用的情况下解决这个问题?
谢谢
hei*_*nob 92
你应该将你的递归函数调用包装成一个
setTimeout,setImmediate 要么 process.nextTick 函数给node.js清除堆栈的机会.如果你不这样做并且有许多循环而没有任何真正的异步函数调用,或者如果你不等待回调,那么你RangeError: Maximum call stack size exceeded将是不可避免的.
有很多关于"潜在异步循环"的文章.这是一个.
现在更多示例代码:
// ANTI-PATTERN
// THIS WILL CRASH
var condition = false, // potential means "maybe never"
max = 1000000;
function potAsyncLoop( i, resume ) {
if( i < max ) {
if( condition ) {
someAsyncFunc( function( err, result ) {
potAsyncLoop( i+1, callback );
});
} else {
// this will crash after some rounds with
// "stack exceed", because control is never given back
// to the browser
// -> no GC and browser "dead" ... "VERY BAD"
potAsyncLoop( i+1, resume );
}
} else {
resume();
}
}
potAsyncLoop( 0, function() {
// code after the loop
...
});
Run Code Online (Sandbox Code Playgroud)
这是正确的:
var condition = false, // potential means "maybe never"
max = 1000000;
function potAsyncLoop( i, resume ) {
if( i < max ) {
if( condition ) {
someAsyncFunc( function( err, result ) {
potAsyncLoop( i+1, callback );
});
} else {
// Now the browser gets the chance to clear the stack
// after every round by getting the control back.
// Afterwards the loop continues
setTimeout( function() {
potAsyncLoop( i+1, resume );
}, 0 );
}
} else {
resume();
}
}
potAsyncLoop( 0, function() {
// code after the loop
...
});
Run Code Online (Sandbox Code Playgroud)
现在你的循环可能会变得太慢,因为我们每轮松开一点时间(一次浏览器往返).但你不必setTimeout每回合都打电话.通常每1000次都可以这样做.但这可能因您的堆栈大小而异:
var condition = false, // potential means "maybe never"
max = 1000000;
function potAsyncLoop( i, resume ) {
if( i < max ) {
if( condition ) {
someAsyncFunc( function( err, result ) {
potAsyncLoop( i+1, callback );
});
} else {
if( i % 1000 === 0 ) {
setTimeout( function() {
potAsyncLoop( i+1, resume );
}, 0 );
} else {
potAsyncLoop( i+1, resume );
}
}
} else {
resume();
}
}
potAsyncLoop( 0, function() {
// code after the loop
...
});
Run Code Online (Sandbox Code Playgroud)
use*_*183 21
我找到了一个脏的解决方案
/bin/bash -c "ulimit -s 65500; exec /usr/local/bin/node --stack-size=65500 /path/to/app.js"
Run Code Online (Sandbox Code Playgroud)
它只是增加了调用堆栈限制.我认为这不适合生产代码,但我需要它只运行一次的脚本.
在某些语言中,这可以通过尾调用优化来解决,其中递归调用在引擎盖下转换为循环,因此不存在最大堆栈大小达到错误.
但是在javascript中,当前的引擎不支持这一点,可以预见新版本的Ecmascript 6语言.
Node.js有一些标志来启用ES6功能但尾部调用尚不可用.
因此,您可以重构代码以实现一种称为trampolining或重构的技术,以便将递归转换为循环.
我有一个类似的问题。我在连续使用多个 Array.map() 时遇到了问题(一次大约 8 个地图),并且出现了 maximum_call_stack_exceeded 错误。我通过将地图更改为“for”循环来解决此问题
因此,如果您使用大量 map 调用,将它们更改为 for 循环可能会解决问题
编辑
只是为了清楚起见和可能不需要但最好知道的信息, using.map()会导致准备好数组(解析 getter 等)并缓存回调,并且还在内部保留数组的索引(因此回调提供了正确的索引/值)。这与每个嵌套调用堆叠在一起,并且在未嵌套时建议小心,因为.map()可以在第一个数组被垃圾收集之前调用下一个数组(如果有的话)。
拿这个例子:
var cb = *some callback function*
var arr1 , arr2 , arr3 = [*some large data set]
arr1.map(v => {
*do something
})
cb(arr1)
arr2.map(v => {
*do something // even though v is overwritten, and the first array
// has been passed through, it is still in memory
// because of the cached calls to the callback function
})
Run Code Online (Sandbox Code Playgroud)
如果我们将其更改为:
for(var|let|const v in|of arr1) {
*do something
}
cb(arr1)
for(var|let|const v in|of arr2) {
*do something // Here there is not callback function to
// store a reference for, and the array has
// already been passed of (gone out of scope)
// so the garbage collector has an opportunity
// to remove the array if it runs low on memory
}
Run Code Online (Sandbox Code Playgroud)
我希望这是有道理的(我没有最好的语言表达方式)并帮助一些人防止我经历的头部挠头
如果有人感兴趣,这里还有一个性能测试比较 map 和 for 循环(不是我的工作)。
https://github.com/dg92/Performance-Analysis-JS
for 循环通常比 map 好,但不包括 reduce、filter 或 find
| 归档时间: |
|
| 查看次数: |
111143 次 |
| 最近记录: |