【问题标题】:Chrome: How to solve "Maximum call stack size exceeded" errors on Math.max.apply( Math, array )Chrome:如何解决 Math.max.apply( Math, array ) 上的“超出最大调用堆栈大小”错误
【发布时间】:2013-08-20 22:46:18
【问题描述】:

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

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);

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

【问题讨论】:

  • 你可能想看看这个问题:stackoverflow.com/questions/1669190/…
  • 我看到了这个。从那里我复制了我的两行。但是我的问题没有...
  • 这个答案在 FF 中效果很好,但在 chrome 中崩溃了......使用这个时我得到了很多 jQuery 错误!顺便说一句:他们提到你只有在拥有 ~10⁷ 元素时才需要使用它,但我更早遇到了这个问题:/
  • 在 Chrome 上这对我有用:for (var a = [], i = 0; i &lt; 999999; ++i) a[i] = Math.random() + 1; a.min(); 无论如何,问题与 Function.prototype.apply 有关 - 试试这个:for(var a = [],i=0; i&lt;130000; i++)xxx.push(Math.random());(function(){}).apply(null, a);

标签: javascript google-chrome optimization


【解决方案1】:

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

它们是否嵌入在递归代码中?

您可以轻松滚动自己的最小/最大代码以避免 Chrome 中的问题。

【讨论】:

  • 在 Chrome 中调试时,总是停在 'Math.max.apply' 的那一行,我也不敢相信,但这是事实……而且我没有此代码中的递归。这两行在一个只调用一次的 init 函数中。
【解决方案2】:
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。

【讨论】:

  • 什么? (max
  • @lukas.pukenis - 这是操作的顺序。如果 max
【解决方案3】:

您已达到函数参数大小限制。它是好的。 函数应该只接受几个参数,因为 else 是 代码味道

如果你有一堆东西? - 使用数组。您正在使用通过的.apply() 参数如:fun(1,2,3,4,5,6....) 并达到极限。 这是不好的做法

问题是 - Math.max() 只能这样工作,所以你最好的选择是迭代搜索功能。但这是另一个话题,因为性能和算法可能会有所不同,例如,如果您首先对数组进行排序。

【讨论】:

  • 这是一些精英公牛。使用可变数量参数是 JavaScript 灵活性的奇迹之一。没必要不做!在很多情况下都非常方便。你应该给它不好的理由!
  • 我喜欢它并使用它。除了我在答案中只关心边缘情况:)
【解决方案4】:

这个问题与 Math.max 和 Math.min 无关。

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

在本地,我在 Chrome 中使用:

function limit(l) {
  var x = []; x.length = l;
  (function (){}).apply(null, x);
}

在本地,limit(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);

请检查!!添加评论以解释我如何理解它。

这是 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];

【讨论】:

  • 在我的例子中,以下函数的限制是 251100(重新开始):function max(arr) { return Math.max.apply(null, arr); }
猜你喜欢
  • 2017-07-06
  • 1970-01-01
  • 2011-08-31
  • 1970-01-01
  • 1970-01-01
  • 2019-09-04
  • 2019-07-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多