#javascript #garbage-collection
#javascript #сбор мусора
Вопрос:
Я тщательно профилировал код, пока не обнаружил, что следующий код выделяет более 1 ГБ оперативной памяти в последней версии Chrome в приватном режиме, когда размер «массива» составляет около 33 МБ, размер на самом деле не имеет значения, это всего лишь файл такого размера, с которым я запускал свойтесты. Я не знаю, как сгенерировать такой большой Uint8Array в коде для вашего теста, поэтому приведенный ниже код не может быть запущен как есть, но, возможно, вы все равно сможете это понять и помочь мне с этим.
const bytesToString = function (array) {
let uint8Array = new Uint8Array(array);
let length = uint8Array.byteLength;
let stringToEncode = "";
for (let i = 0; i < length; i ) {
stringToEncode = String.fromCharCode(uint8Array[i]);
}
return stringToEncode;
}
При раскомментировании «цикла for» потребление оперативной памяти остается на том же уровне во время выполнения моего кода, как только «цикл for» активен, потребление увеличивается до более чем 1 ГБ. В какой-то момент это, конечно, приводит к GC, но у меня общая проблема с памятью, когда браузер в конечном итоге выйдет из строя из-за чрезмерного потребления памяти, и я пытаюсь выяснить, является ли эта функция проблемой.
С помощью анализатора производительности из Chrome я мог видеть, что GC вызывается много раз, я не знаю, как работает GC из Chrome, потому что вы можете прочитать много «второстепенных GC» и в какой-то момент в конце «Major GC», и мне было интересно, действительно ли «Minor GC» не работает.означает, что ОЗУ освобождается, а скорее «собирается», и только на более позднем этапе «основной сборщик данных» действительно освобождает ОЗУ. Если это так, я полагаю, что между вызовом этой функции и «Основным GC» мой код запускает что-то, что также требует больше оперативной памяти, чем обычно, а затем происходит сбой браузера. Если это так, возникает вопрос, есть ли лучшая реализация для моей функции или я могу манипулировать GC? Насколько я мог прочитать, я не могу.
Комментарии:
1. «могу ли я манипулировать GC?» вам лучше написать более производительный код…
2. @Jonas Wilms Да, конечно, вопрос больше для любопытства и для целей тестирования.
Ответ №1:
Строки в JS неизменяемы, поэтому каждый раз, когда вы добавляете символ, он создает новую строку, которая на 1 символ длиннее предыдущей. GC не будет запускаться, пока все не будет сделано, поэтому вы застряли с множеством строк различной длины.
Вам нужны другие способы объединения строк. В этом случае вся ваша функция может быть записана как String.fromCharCode(...array)
(хотя, если вы действительно хотите создать строку из двоичных данных, вам следует рассмотреть возможность использования TextDecoder
вместо этого, который поддерживает различные кодировки, с оговоркой, что он недоступен в таких средах, как Node.js ).
Обновление: String.fromCharCode
похоже, не работает для очень больших массивов (существует ограничение на количество параметров для любой функции), поэтому вместо этого вы можете попытаться сопоставить массив с 1-символьными строками, а затем объединить их вместе:
Array.prototype.map.call(uint8Array, c => String.fromCharCode(c)).join("")
(Обратите внимание на использование Array.prototype.map
вместо uint8Array.map
, поскольку последнее приведет к усечению ваших результатов до Uint8)
Комментарии:
1. Я уже пытался работать с TextDecoder в прошлом, но в конце это не сработало из-за того, как я тогда использовал результат, проблемы с кодированием и т. Д.
2. Я протестировал его на лету, и String.fromCharCode(…array) кажется действительно лучше, чем с циклом, с 44 МБ массива ~ 740 МБ оперативной памяти вместо 1,4 ГБ с циклом.
3. Спасибо за обновление, так как несколько минут назад у меня были большие глаза, когда я увидел, что fromCharCode(…array) все-таки возвращал «». Не могли бы вы, пожалуйста, уточнить свое обновление, поскольку я все еще не JS Pro, я не могу полностью прочитать это решение, а также что такое «c»? Обновленное решение также не работает в IE11.
4. И я протестировал это сейчас, и это на самом деле хуже, чем мое решение с точки зрения потребления памяти. Я полагаю, это похоже на выполнение следующего, что я уже пробовал, на самом деле требуется больше оперативной памяти, чем в моем исходном сообщении: let length = Uint8Array.byteLength; let strings = []; strings . length = длина; для (пусть i = 0; i < длина; i ) { strings[i] = String.fromCharCode(Uint8Array[i]);} пусть результат=strings.join(«);
Ответ №2:
Я думаю TextDecoder
, что это, вероятно, правильное решение. Но если вы настаиваете, вы также можете попробовать создать большой двоичный объект, а затем прочитать его.
let blob = new Blob([arrayBuffer], {type: 'application/octet-stream'});
let reader = new FileReader();
reader.onload = function (event) {
console.log(event.target.result);
};
// Use if you want the UTF-8 encoded version
reader.readAsText(blob);
// Use if you for example need to use the result with "window.btoa" as it was in my case.
reader.readAsBinaryString(blob);
Комментарии:
1. Я отредактировал решение, чтобы отразить мое решение, основанное на вашем предложении, я думаю, что в настоящее время это лучшее, что я мог найти.
2. Единственная проблема заключается в том, что «readAsBinaryString» на самом деле устарел developer.mozilla.org/en-US/docs/Web/API/FileReader /…