Оптимизация предобработки: избегание лишних аллокаций

Работа с ONNX Runtime Web (ORT Web) в JavaScript требует внимательного отношения к управлению памятью, особенно на этапе предобработки данных перед подачей в модель. Лишние аллокации массивов и промежуточных объектов не только замедляют выполнение, но и создают дополнительную нагрузку на сборщик мусора, что особенно критично для веб-приложений с высокой частотой запросов к модели.

Использование заранее выделенных буферов

Ключевой принцип оптимизации — минимизация повторного создания массивов. Для этого целесообразно заранее выделять буферы фиксированного размера и переиспользовать их для каждого нового входа.

const inputSize = 224 * 224 * 3; // пример для модели обработки изображений
const inputBuffer = new Float32Array(inputSize);

// при предобработке изображения
function preprocessImage(imageData) {
    // заполняем заранее выделенный буфер
    for (let i = 0; i < inputSize; i++) {
        inputBuffer[i] = imageData[i] / 255.0;
    }
    return inputBuffer;
}

Преимущества:

  • Нет повторного выделения памяти на каждый кадр.
  • Сборщик мусора минимально нагружается.
  • Возможность прямой передачи буфера в Ort.Tensor без дополнительных копирований.

Прямое создание тензора на основе существующего буфера

ONNX Runtime Web позволяет создавать тензоры напрямую из уже существующего массива, что полностью исключает создание промежуточных структур.

const tensor = new ort.Tensor('float32', inputBuffer, [1, 3, 224, 224]);

Особенности:

  • Тип данных и размерность должны строго соответствовать ожиданиям модели.
  • Повторное использование inputBuffer для разных запросов ускоряет предобработку.
  • Изменение данных в буфере не требует пересоздания объекта Tensor.

Минимизация промежуточных копий при нормализации

При стандартной нормализации изображений часто создаются временные массивы для каждого канала. Эффективнее нормализовать данные «на лету» в уже выделенном буфере.

function normalizeChannels(imageData, mean, std) {
    for (let i = 0; i < imageData.length; i += 3) {
        inputBuffer[i]     = (imageData[i]     / 255.0 - mean[0]) / std[0];
        inputBuffer[i + 1] = (imageData[i + 1] / 255.0 - mean[1]) / std[1];
        inputBuffer[i + 2] = (imageData[i + 2] / 255.0 - mean[2]) / std[2];
    }
    return inputBuffer;
}

Эффект:

  • Исключение временных массивов для каждого канала.
  • Обработка выполняется за один проход по исходным данным.
  • Снижение количества операций выделения памяти в цикле рендеринга.

Пакетная обработка для многократного запроса

Для веб-приложений с потоковой подачей изображений или батчами данных важно использовать один общий буфер для пакета запросов. Создание единого большого буфера и заполнение отдельных частей для каждого примера в батче позволяет полностью избежать многократных аллокаций.

const batchSize = 4;
const batchBuffer = new Float32Array(batchSize * inputSize);

for (let b = 0; b < batchSize; b++) {
    const offset = b * inputSize;
    preprocessImageBatch(imageDataArray[b], batchBuffer, offset);
}

function preprocessImageBatch(imageData, buffer, offset) {
    for (let i = 0; i < inputSize; i++) {
        buffer[offset + i] = imageData[i] / 255.0;
    }
}

Результат:

  • Одно выделение памяти на весь батч.
  • Снижение нагрузки на сборщик мусора.
  • Возможность передачи всего батча в модель одним вызовом session.run().

Использование TypedArray вместо обычных массивов

JavaScript TypedArray (Float32Array, Int32Array и др.) обеспечивает плотное размещение данных и прямую совместимость с ORT Web. Это позволяет передавать массивы напрямую в Ort.Tensor без копирования, в отличие от обычных массивов Array, которые требуют конвертации.

Рекомендации:

  • Всегда использовать Float32Array для моделей с типом float32.
  • Избегать промежуточных массивов Array при любой арифметической обработке.
  • При необходимости временных массивов — использовать заранее выделенные буферы, а не создавать новые объекты.

Выводы по оптимизации предобработки

  • Повторное использование выделенной памяти критически важно для высокой производительности веб-приложений с ONNX Runtime Web.
  • Прямое создание тензоров из TypedArray исключает лишние копирования.
  • Нормализация и другие операции должны выполняться в одном проходе по исходным данным.
  • Пакетная обработка с общим буфером значительно уменьшает количество аллокаций.

Эти подходы позволяют сократить время предобработки и снизить нагрузку на сборщик мусора, что особенно важно для приложений с высокой частотой обработки изображений или потоковых данных.