我必须找到非常大的数组的最大值和最小值。为此,我正在使用
Math.max.apply(Math, my_array);
Math.min.apply(Math, my_array);
它在Firefox和IE上运行良好,但在Chrome上我总是得到Maximum call stack size exceeded
错误...我当前的数组有221954个元素,这不是我最大的。
有人知道如何在Chrome上解决此错误吗?如何优化最大值和最小值的搜索?
对于那些无法相信的人,请在Chrome的控制台中尝试:
var xxx = []
for(var i=0; i<300000; i++){
xxx.push(Math.random());
}
Math.max.apply(Math, xxx);
---&GT; RangeError:超出最大调用堆栈大小
答案 0 :(得分:19)
此问题与Math.max和Math.min没有任何关系。
Function.prototype.apply只能接收有限长度的数组作为其第二个参数。
在本地,我使用以下方法在Chrome中进行了测试:
function limit(l) {
var x = []; x.length = l;
(function (){}).apply(null, x);
}
在本地,限制(l)与l = 124980完全崩溃。在金丝雀中,这是另一个数字,但也是~125k。
这是为什么会发生这种情况的一个示例解释:https://code.google.com/p/v8/issues/detail?id=2896(它在其他JS引擎中也是可重复使用的,例如MDN提到了这个问题:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply#Using_apply_and_built-in_functions(以“但要注意”开头。 ..“),指向WebKit bugzilla中的这个问题:https://bugs.webkit.org/show_bug.cgi?id=80797)。据我所知,为什么在V8中抛出RangeError:
V8在程序集中实现Function.prototype.apply。在调用函数之前,它应该放置所有函数调用参数,例如在调用javascript函数之前,thisArg和第二个arg数组的所有成员逐个进入堆栈。但是堆栈的容量有限,如果达到极限,就会得到RangeError。
这是我在V8源代码中发现的(IA-32程序集,builtins-ia32.cc):
void Builtins::Generate_FunctionApply(MacroAssembler* masm) {
static const int kArgumentsOffset = 2 * kPointerSize;
static const int kReceiverOffset = 3 * kPointerSize;
static const int kFunctionOffset = 4 * kPointerSize;
{
FrameScope frame_scope(masm, StackFrame::INTERNAL);
__ push(Operand(ebp, kFunctionOffset)); // push this
__ push(Operand(ebp, kArgumentsOffset)); // push arguments
__ InvokeBuiltin(Builtins::APPLY_PREPARE, CALL_FUNCTION);
// Check the stack for overflow. We are not trying to catch
// interruptions (e.g. debug break and preemption) here, so the "real stack
// limit" is checked.
Label okay;
ExternalReference real_stack_limit =
ExternalReference::address_of_real_stack_limit(masm->isolate());
__ mov(edi, Operand::StaticVariable(real_stack_limit));
// Make ecx the space we have left. The stack might already be overflowed
// here which will cause ecx to become negative.
// !! ADDED COMMENT: IA-32 stack grows downwards, if address to its current top is 0 then it cannot be placed any more elements into. esp is the pointer to stack top.
__ mov(ecx, esp);
// !! ADDED COMMENT: edi holds the "real_stack_limit", which holds the minimum address that stack should not grow beyond. If we subtract edi from ecx (=esp, or, in other words, "how much space is left on the stack"), we may get a negative value, and the comment above says that
__ sub(ecx, edi);
// Make edx the space we need for the array when it is unrolled onto the
// stack.
// !! ADDED COMMENT: eax holds the number of arguments for this apply call, where every member of the 2nd argument array counts as separate argument
__ mov(edx, eax);
// !! ADDED COMMENT: kPointerSizeLog2 - kSmiTagSize is the base-2-logarithm of how much space would 1 argument take. By shl we in fact get 2^(kPointerSizeLog2 - kSmiTagSize) * arguments_count, i.e. how much space do actual arguments occupy
__ shl(edx, kPointerSizeLog2 - kSmiTagSize);
// Check if the arguments will overflow the stack.
// !! ADDED COMMENT: we compare ecx which is how much data we can put onto stack with edx which now means how much data we need to put onto stack
__ cmp(ecx, edx);
__ j(greater, &okay); // Signed comparison.
// Out of stack space.
__ push(Operand(ebp, 4 * kPointerSize)); // push this
__ push(eax);
__ InvokeBuiltin(Builtins::APPLY_OVERFLOW, CALL_FUNCTION);
请检查!!添加评论以解释我的理解。
这是APPLY_OVERFLOW函数,用JS编写(再次,V8源代码,runtime.js):
function APPLY_OVERFLOW(length) {
throw %MakeRangeError('stack_overflow', []);
}
编辑:在您的情况下,我会像:
var max = -Infinity;
for(var i = 0; i < arr.length; i++ ) if (arr[i] > max) max = arr[i];
答案 1 :(得分:1)
您已达到功能参数大小限制。而且确定。 函数应该只接受几个参数,否则代码气味。
如果你有一堆物品? - 使用数组。您正在使用通过的.apply()
参数如:fun(1,2,3,4,5,6....)
并达到极限。 这是不好的做法。
问题是 - Math.max()
只能这样工作,所以最好的选择是迭代搜索功能。但这是另一个主题,因为性能和算法可能会有所不同,例如,如果您首先对数组进行排序。
答案 2 :(得分:0)
对我而言,错误不应该来自对Math.min / max的调用,它看起来像使用递归的结果,我无法相信Chrome将用于实现这些功能。
它们是否嵌入递归代码中?
您可以轻松滚动自己的最小/最大代码,以避免Chrome中的问题。
答案 3 :(得分:0)
var a=[];
for(var i=0;i<1125011;i++){
a[i] = i;
}
function maxIterate(arr){
var max = arr[0];
for(var i = 1;i< arr.length; i++){
(max < arr[i]) && (max = arr[i])
}
return max;
}
console.log(maxIterate(a));
Math.max
可以使用递归方法来获取最大值,只需重写一个迭代函数来获得最大值。这样可以避免RangeError。