Конкатенация JS String взрывает потребление памяти - PullRequest
1 голос
/ 19 апреля 2019

Я активно профилировал код, пока не обнаружил, что следующий код выделяет более 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, потому что вы можете прочитать много "Minor GC" и в какой-то момент в конце "Major GC" иМне было интересно, если «Minor GC» на самом деле не означает, что оперативная память освобождается, а скорее «собирается», и только на более позднем этапе «Major GC» действительно освобождает оперативную память.Если это так, то я предполагаю, что между вызовом этой функции и «Major GC» мой код запускает что-то, что также требует больше оперативной памяти, чем обычно, и затем происходит сбой браузера.Если это так, то вопрос в том, есть ли лучшая реализация для моей функции или я могу манипулировать GC?Насколько я мог читать, я не могу.

Ответы [ 2 ]

1 голос
/ 19 апреля 2019

Строки в 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)

0 голосов
/ 19 апреля 2019

Я думаю 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);
...