Размер стека вызовов в JavaScript
Я хочу проверить наличие больших стеков вызовов. В частности, я хочу, чтобы консольное предупреждение, когда длина стека вызовов достигает 1000. Это обычно означает, что я сделал что-то глупое и может привести к тонким ошибкам.
Можно ли вычислить длину стека вызовов в JavaScript?
Ответы
Ответ 1
Здесь функция, которая будет работать во всех основных браузерах, хотя она не будет работать в строгом режиме ECMAScript 5, потому что arguments.callee
и caller
были удалены в строгом режиме.
function getCallStackSize() {
var count = 0, fn = arguments.callee;
while ( (fn = fn.caller) ) {
count++;
}
return count;
}
Пример:
function f() { g(); }
function g() { h(); }
function h() { alert(getCallStackSize()); }
f(); // Alerts 3
ОБНОВЛЕНИЕ 1 ноября 2011
В строгом режиме ES5 просто не удается перейти в стек вызовов. Единственный вариант - это проанализировать строку, возвращаемую new Error().stack
, которая не является стандартной, не поддерживается повсеместно и явно проблематична, и даже этот может быть невозможен навсегда.
ОБНОВЛЕНИЕ 13 августа 2013 г.
Этот метод также ограничен тем фактом, что функция, которая вызывается более одного раза в одном стеке вызовов (например, через рекурсию), бросает getCallStackSize()
в бесконечный цикл (как указано в комментариях @Randomblue), Ниже приведена улучшенная версия getCallStackSize()
: она отслеживает функции, которые она видела раньше, чтобы избежать перехода в бесконечный цикл. Однако возвращаемое значение - это количество различных объектов функции в стоп-кадре перед встречей с повторением, а не с истинным размером полного стека вызовов. Это лучшее, что вы можете сделать, к сожалению.
var arrayContains = Array.prototype.indexOf ?
function(arr, val) {
return arr.indexOf(val) > -1;
} :
function(arr, val) {
for (var i = 0, len = arr.length; i < len; ++i) {
if (arr[i] === val) {
return true;
}
}
return false;
};
function getCallStackSize() {
var count = 0, fn = arguments.callee, functionsSeen = [fn];
while ( (fn = fn.caller) && !arrayContains(functionsSeen, fn) ) {
functionsSeen.push(fn);
count++;
}
return count;
}
Ответ 2
Вы можете использовать этот модуль:
https://github.com/stacktracejs/stacktrace.js
Вызов printStackTrace возвращает трассировку стека внутри массива, тогда вы можете проверить ее длину:
var trace = printStackTrace();
console.log(trace.length());
Ответ 3
Другой подход - это измерение доступного размера в стеке в кадре верхнего уровня, а затем определение используемого пространства в стеке путем наблюдения за тем, сколько свободного места доступно. В коде:
function getRemainingStackSize()
{
var i = 0;
function stackSizeExplorer() {
i++;
stackSizeExplorer();
}
try {
stackSizeExplorer();
} catch (e) {
return i;
}
}
var baselineRemStackSize = getRemainingStackSize();
var largestSeenStackSize = 0;
function getStackSize()
{
var sz = baselineRemStackSize - getRemainingStackSize();
if (largestSeenStackSize < sz)
largestSeenStackSize = sz;
return sz;
}
Например:
function ackermann(m, n)
{
if (m == 0) {
console.log("Stack Size: " + getStackSize());
return n + 1;
}
if (n == 0)
return ackermann(m - 1, 1);
return ackermann(m - 1, ackermann(m, n-1));
}
function main()
{
var m, n;
for (var m = 0; m < 4; m++)
for (var n = 0; n < 5; n++)
console.log("A(" + m + ", " + n + ") = " + ackermann(m, n));
console.log("Deepest recursion: " + largestSeenStackSize + " (" +
(baselineRemStackSize-largestSeenStackSize) + " left)");
}
main();
Конечно, есть два основных недостатка этого подхода:
(1) определение использованного пространства стека является потенциально дорогостоящей операцией, когда виртуальная машина имеет большой размер стека и
(2) числа, о которых сообщалось, не обязательно являются числом рекурсий, а вместо этого являются измерением фактического пространства, используемого в стеке (конечно, это также может быть преимуществом). Я видел автоматически сгенерированный код, который содержит функции, которые используют одно и то же пространство в стеке для каждой рекурсии как 2000 рекурсий функции stackSizeExplorer
выше.
Примечание. Я тестировал только код выше с помощью node.js. Но я предполагаю, что он будет работать со всеми виртуальными машинами, использующими размер статического стека.