Chrome:如何在Math.max.apply(Math,array)上解决"超出最大调用堆栈大小"错误

Pan*_*han 12 javascript optimization google-chrome

我必须找到非常大的数组的最大值和最小值.为此,我正在使用

Math.max.apply(Math, my_array);
Math.min.apply(Math, my_array);
Run Code Online (Sandbox Code Playgroud)

它在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);
Run Code Online (Sandbox Code Playgroud)

---> RangeError:超出最大调用堆栈大小

t.O*_*t.O 19

此问题与Math.max和Math.min无关.

Function.prototype.apply只能接收有限长度的数组作为其第二个参数.

在本地,我使用Chrome在Chrome中对其进行了测试:

function limit(l) {
  var x = []; x.length = l;
  (function (){}).apply(null, x);
}
Run Code Online (Sandbox Code Playgroud)

在本地,限制(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.调用该函数之前,应该将所有的函数调用参数,例如thisArg,并且所有第二ARG阵列的成员,一个接一个,入堆栈,调用JavaScript函数之前.但是堆栈的容量有限,如果达到极限,就会得到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);
Run Code Online (Sandbox Code Playgroud)

请检查 !!添加评论以解释我对它的理解.

这是APPLY_OVERFLOW函数,用JS编写(再次,V8源代码,runtime.js):

function APPLY_OVERFLOW(length) {
  throw %MakeRangeError('stack_overflow', []);
}
Run Code Online (Sandbox Code Playgroud)

编辑:在你的情况下,我会像:

var max = -Infinity; 
for(var i = 0; i < arr.length; i++ ) if (arr[i] > max) max = arr[i];
Run Code Online (Sandbox Code Playgroud)


Tom*_*Tom 0

对我来说,错误不应该来自对 Math.min / max 的调用,它看起来像是使用递归的结果,我无法相信 Chrome 会使用递归来实现这些函数。

它们嵌入在递归代码中吗?

您可以简单地滚动自己的最小/最大代码来避免 Chrome 中的问题。