Node.js нарезка очень большого буфера с нехваткой памяти

#javascript #node.js #performance

#javascript #node.js #Производительность

Вопрос:

У меня есть очень большая строка в кодировке base64, которую необходимо прочитать в массив байтов (Uint8), а затем разделить этот массив байтов на фрагменты указанного размера. base64 кодирует эти фрагменты отдельно. Использование приведенной ниже функции работает, но вызов .slice или .toString увеличивает память в куче при каждом вызове, потому что (я полагаю) он создает копию буфера. Для особенно больших строк, закодированных в base64Encoded, приложению не хватит места в куче. Что можно сделать, чтобы разделить это на указанные размеры и кодировать их в base64 без нехватки памяти?

 const process = function (reallyLargeBase64EncodedString, splitSize){

var theBuffer = Buffer.from(reallyLargeBase64EncodedString, 'base64');

//var tempBuffer = new Buffer(splitSize);
for (var start = 0; start < theBuffer.length; start  = splitSize) {
    //for(var z = 0; z < splitSize; z  ){
        //tempBuffer.writeUInt8( theBuffer[start z],z);
    //}
    //var base64EncodedVal = tempBuffer.toString('base64');
    //var base64EncodedVal = theBuffer.buffer.toString('base64', start, start splitSize);
    var base64EncodedVal = theBuffer.slice(start,start splitSize).toString('base64'); 
    //do stuff with the base64 encoded value
}
  

};

Комментарии:

1. Нет, slice не копирует память . toString делает. Что именно «материал», который вы делаете со строками?

2. Прямо сейчас вставляем их в базу данных. Хммм, проблема в строке toString, возможно, будет лучше изменить эту строку со строки на большой двоичный объект и просто вставить массив байтов напрямую.

3. Является ли вставка базы данных асинхронной? Возможно, вы захотите сделать его последовательным, чтобы не все строки создавались в памяти одновременно.

4. На самом деле я выполнял вставку с этой последней строкой, просто взял код базы данных для примера.

5. Если у вас есть «очень большой буфер» в Node. JS, тогда ты делаешь что-то не так.

Ответ №1:

Я бы рекомендовал использовать потоковый интерфейс узла для работы с чем-то таким большим. Если ваша строка в кодировке base64 поступает из файла или сетевого запроса, вы можете передавать ее непосредственно из входных данных в поток декодирования base64, например base64-stream .

Чтобы разделить данные и перекодировать каждый фрагмент, вам нужно будет написать свой собственный поток преобразования (поток, который проходит между входом и выходом). Это будет выглядеть примерно так

 // NOTE: the following code has been tested in node 6.
// since it relies on the new Buffer api, it must be run in 5.10 
var Transform = require('stream').Transform;

class ChunkEncode extends Transform {
    constructor(options){
        super(options);
        this.splitSize = options.splitSize;
        this.buffer = Buffer.alloc(0);
    }

    _transform(chunk, encoding, cb){
        // chunk is a Buffer;
        this.buffer = Buffer.concat([this.buffer, chunk]);
        while (this.buffer.length > this.splitSize){
            let chunk = this.buffer.slice(0, this.splitSize);
            // Encode and write back to the stream.
            this.push(chunk.toString('base64')) 
            // throw in a newline for visibility.
            this.push('n');
            // chop off `splitSize` from the start of our buffer.
            this.buffer = this.buffer.slice(this.splitSize);
        }
    }
}
  

Тогда вы должны быть в состоянии сделать что-то вроде

  var fs     = require('fs');
 var base64 = require('base64-stream');

 fs.createReadStream('./long-base64-string')
 .pipe(base64.decode())
 .pipe(new ChunkEncode({splitSize : 128}))
 .pipe(process.stdout) 
  

это приведет к выходу из системы, но вы можете так же легко выполнить запись в файл или сетевой поток. Если вам нужно дополнительно манипулировать данными, вы можете создать поток записи, который позволит вам что-то делать с каждым фрагментом данных по мере их поступления.

Комментарии:

1. Это потрясающе, и я действительно хочу отметить это как ответ, но я решил проблему совершенно по-другому, и единственная причина, по которой ваш ответ не был тем, потому что я не опубликовал ВЕСЬ код, и я прошу прощения за это. Настоящая проблема в том, что я n00b, когда дело доходит до асинхронной парадигмы. Этот код был вызван из результата запроса, результаты которого были отправлены для вызова API, а затем кода, который я опубликовал, и вставки для строки b64. Начальный вызов базы данных еще не завершился, и все, что я там делал, было выброшено в кучу, пока оно не могло быть обработано.

2. Не беспокойтесь об этом. Трудно сказать, какие фрагменты кода будут важны в подобном вопросе. Тем более, что причина, по которой вы задали вопрос, заключается в том, что у вас не было ответа 🙂 Я думаю, что процедура в этом случае заключается в том, чтобы написать свой собственный ответ. (в значительной степени этот комментарий выше) и пометьте его как правильный.

3. Я тоже проверил ваш ответ, чтобы посмотреть, сохранит ли он размер кучи, и это произошло.. недостаточно, чтобы остановить переполнение кучи, но цикл for продвинулся более чем в два раза, прежде чем его вырвало.

4. _transform Метод не вызывает обратный вызов и _flush должен быть также реализован для отправки оставшихся элементов в буфер в конце потока.