#javascript #node.js #memory-leaks
#javascript #node.js #утечки памяти
Вопрос:
Просто для развлечения и чтобы опробовать nodejs, я написал очень, очень простую программу, которая проверяет гипотезу Коллатца на абсурдное количество чисел. Теоретически это должно быть нормально. Проблема, с которой я сталкиваюсь, заключается в том, что этот супер простой код имеет утечку памяти, и я не могу определить, почему.
var step;
var numberOfSteps;
for (var i = 0; i < 100000000000000; i ) {
step = i;
numberOfSteps = 0;
while (step !== 1) {
if (step%2 === 0)
step /= 2;
else
step = 3 * step 1;
numberOfSteps ;
}
console.log("" i ": " numberOfSteps " steps.");
}
Я пробовал переменные как в цикле, так и вне его. Я пытался обнулить их в конце цикла. Ничто не меняет утечку памяти.
Комментарии:
1. Где утечка? Я попробовал это на своем компьютере, и моя память увеличилась менее чем на 0,01G
2. утечки нет … но цикл while бесконечен для шага === 0 … хотя, если вы исправите эту проблему, узел, похоже, продолжает медленно поглощать память, не так ли
3. Это то
console.log
, что вызывает это — это почти так, как если бы GC не мог работать для очистки мусора, оставленного консолью. вызов журнала4. Я не смог запустить его в своем случае, после 2-3 минут запуска он сломался и разбился:
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory Aborted (core dumped)
(дамп ядра больше). Однако я проверял использование памяти, оно увеличивалось примерно на 800 МБ, а затем оставалось стабильным, продолжало работать, и произошел сбой с дампом ядра выше.5. Это должно начинаться с i = 1, я просто вставил туда число, на котором остановился. Выше единицы оно никогда не должно опускаться ниже 1.
Ответ №1:
Немного исследую мой дамп ядра:
<--- Last few GCs --->
131690 ms: Scavenge 1398.1 (1458.1) -> 1398.1 (1458.1) MB, 1.3 / 0 ms ( 2.8 ms in 1 steps since last GC) [allocation failure] [incremental marking delaying mark-sweep].
132935 ms: Mark-sweep 1398.1 (1458.1) -> 1398.1 (1458.1) MB, 1245.0 / 0 ms ( 3.7 ms in 2 steps since start of marking, biggest step 2.8 ms) [last resort gc].
134169 ms: Mark-sweep 1398.1 (1458.1) -> 1398.1 (1458.1) MB, 1234.5 / 0 ms [last resort gc].
<--- JS stacktrace --->
==== JS stack trace =========================================
Security context: 0x33083d8e3ac1 <JS Object>
1: /* anonymous */ [/user/projects/test.js:~1] [pc=0x557d307b271] (this=0x2a4a669d8341 <an Object with map 0xf8593408359>,exports=0x33083d804189 <undefined>,require=0x33083d804189 <undefined>,module=0x33083d804189 <undefined>,__filename=0x33083d804189 <undefined>,__dirname=0x33083d804189 <undefined>)
3: _compile [module.js:413] [pc=0x557d304d03c] (this=0x2a4a669d8431...
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory
Aborted (core dumped)
Похоже, что это известная проблема с console.log, согласно этой проблеме на github https://github.com/nodejs/node/issues/3171
Это известная «проблема», поскольку запись в стандартный вывод в случае tty / console выполняется асинхронно. Таким образом, очень быстрая регистрация большого количества данных вполне может привести к буферизации большого количества записей в памяти, если tty / console не может идти в ногу.
Ответ №2:
Вот код, который, кажется, достигает максимума примерно в 50 МБ для меня
Это выполняет функцию партиями по 10000 — с setImmediate для обработки следующего пакета
function collatz(n) {
var step,numberOfSteps, i;
for(i = 0; i < 10000; i , n ) {
step = n;
numberOfSteps = 0;
while (step !== 1) {
if (step%2 === 0)
step /= 2;
else
step = 3 * step 1;
numberOfSteps ;
}
console.log("" n ": " numberOfSteps " steps.");
}
if (n < 100000000000000) {
setImmediate(collatz, n);
}
}
collatz(1);
Обратите внимание, что в этом случае цикл for может начинаться с 0, потому что n начнется с 1: p
Я не пробовал более высокие значения цикла for
Я провел некоторый сравнительный анализ с исходным кодом — выполнение 100 за раз (в цикле for) дает ту же производительность, что и 10000, и неотличимо по производительности от исходного кода. Даже при 10 за раз я бы не стал говорить, что этот метод тоже работает медленнее. Только по 1 за раз это последовательно на 5-8% медленнее, чем исходный код
Обратите внимание, что изначально я думал, что проблема заключается в сборке мусора (или его отсутствии) из-за того, что узкий цикл не дает узлу времени на ведение домашнего хозяйства, но пока я публиковал ответ, @Svabel опубликовал то, что, по-видимому, является известной проблемой при попадании в консоль.жесткий журнал.
Я могу только предположить, что использование
setImmediate
позволяет вести какое-то домашнее хозяйство в отношении буферов tty, что в противном случае невозможно.
Комментарии:
1. Можете ли вы объяснить, почему асинхронность решает проблему с памятью?
2. не совсем, я думал, что это может быть проблема с GC, но, прочитав ответ @Svabel, это может быть как-то связано с «известной проблемой», касающейся console.log — я не хотел включать это в свой ответ, поскольку это казалось бы неуместным
3. интересно, я вижу в этой «проблеме», что использование
setImmediate
было предложенным решением — один setImmediate на итерацию — 10000 на итерацию является излишним, даже 100 дает аналогичную (на самом деле в течение нескольких тестов, я не могу сказать, что это вообще хуже) «производительность» по сравнению с исходным кодом — тогда как1 на итерацию влияет на скорость примерно на 8%