Профилирование узких мест

В Web Crypto API большинство операций являются асинхронными и часто выполняются вне основного JavaScript-потока, однако это не означает их «бесплатность» с точки зрения производительности. При профилировании важно учитывать не только время криптографического алгоритма, но и накладные расходы на подготовку данных, копирование буферов и переходы между слоями движка браузера и нативной криптографической подсистемы.

Ключевая метрика, которая используется на практике, — общее время выполнения операции от момента вызова crypto.subtle.* до получения результата промиса. Однако этого недостаточно: внутри этого интервала могут скрываться совершенно разные по природе задержки.

Разделение времени на этапы выполнения

Типичная операция Web Crypto API включает несколько фаз:

  • Подготовка входных данных (строки → ArrayBuffer, кодирование)
  • Передача данных в криптографический слой
  • Выполнение нативного алгоритма (AES, RSA, ECDSA и т.д.)
  • Возврат результата в JS-окружение
  • Обработка результата (декодирование, преобразование типов)

Наиболее частые узкие места возникают не в самом криптоалгоритме, а на границах между этими этапами.

Накладные расходы на преобразование данных

Одним из наиболее недооценённых факторов производительности является работа с ArrayBuffer, TypedArray и текстовыми кодировками.

Кодирование строк

Каждый вызов TextEncoder.encode() создаёт новый буфер:

const enc = new TextEncoder();
const data = enc.encode("some large payload");

При массовых операциях (например, потоковое шифрование сообщений) это становится значительным источником аллокаций и давления на сборщик мусора.

Копирование буферов

Web Crypto API почти всегда работает с копиями данных. Даже если передаётся ArrayBuffer, движок может:

  • копировать данные в нативную память
  • выравнивать буферы
  • создавать промежуточные структуры

Это особенно заметно при больших объёмах данных (десятки мегабайт), где стоимость копирования начинает сопоставляться со стоимостью самого алгоритма.

Асинхронность и скрытая стоимость промисов

Хотя crypto.subtle работает асинхронно, это не означает минимальных накладных расходов. Каждый вызов:

await crypto.subtle.encrypt(...)

включает:

  • создание промиса
  • постановку задачи в очередь
  • переключение контекста исполнения
  • возврат результата через микротаски

При массовых операциях (например, тысячи подписей или проверок) именно управление промисами может стать ограничивающим фактором.

Узкие места в массовых криптографических операциях

Генерация ключей

Операции вроде:

crypto.subtle.generateKey(...)

или:

crypto.subtle.importKey(...)

являются значительно более дорогими, чем шифрование или хэширование. Причина — работа с:

  • генерацией случайных чисел (CSPRNG)
  • проверкой параметров
  • построением внутренних структур ключей

При частом создании ключей система начинает упираться в криптографический RNG и системные вызовы.

RSA и ECDSA как источники задержек

Асимметричные алгоритмы обладают высокой вычислительной стоимостью:

  • RSA-OAEP: дорогие операции возведения в степень
  • ECDSA: операции на эллиптических кривых

При профилировании важно учитывать, что даже аппаратное ускорение не устраняет полностью их стоимость, а лишь снижает её.

AES-GCM и влияние режима работы

Симметричные алгоритмы вроде AES-GCM обычно значительно быстрее, но их производительность зависит от:

  • размера блока данных
  • наличия аппаратного ускорения (AES-NI)
  • частоты вызовов

Небольшие частые вызовы часто оказываются медленнее, чем одна большая операция, из-за накладных расходов на вызов API.

GC pressure и временные объекты

При интенсивной работе с Web Crypto API формируется большое количество временных объектов:

  • Uint8Array
  • ArrayBuffer
  • промежуточные буферы
  • Promise wrappers

Это приводит к:

  • частым паузам GC
  • фрагментации памяти
  • росту latency на больших нагрузках

Особенно заметно в сценариях потокового шифрования, где данные разбиваются на мелкие части.

Профилирование через Performance API

Для измерения реальной производительности используется performance.now():

const t0 = performance.now();
await crypto.subtle.encrypt(alg, key, data);
const t1 = performance.now();

Однако более точные измерения требуют:

  • performance.mark()
  • performance.measure()
  • группировки операций в батчи

Пример группового измерения:

performance.mark("start-encrypt");

for (let i = 0; i < 1000; i++) {
  await crypto.subtle.encrypt(alg, key, chunks[i]);
}

performance.mark("end-encrypt");
performance.measure("encrypt-total", "start-encrypt", "end-encrypt");

Разделение CPU и криптографического времени

В браузерной среде важно учитывать, что:

  • JS поток может простаивать во время нативной операции
  • измерения в JS не отражают внутреннюю загрузку CPU полностью
  • DevTools показывает усреднённые значения

Для анализа используются:

  • Chrome Performance Panel
  • flame chart
  • long tasks API

Бутылочные горлышки в потоковой криптографии

При реализации потокового шифрования часто возникают проблемы:

  • слишком мелкие чанки (overhead API)
  • частые вызовы encrypt/decrypt
  • отсутствие буферизации

Оптимальная стратегия — увеличение размера блока до диапазона 64KB–1MB в зависимости от сценария.

Влияние сериализации и форматов данных

Часто криптографические операции сопровождаются преобразованием:

  • base64 ↔︎ ArrayBuffer
  • JSON ↔︎ бинарные данные

Base64 особенно затратен:

  • увеличивает размер данных на ~33%
  • требует CPU на кодирование/декодирование
  • создаёт лишние аллокации

При профилировании часто выясняется, что именно кодирование занимает больше времени, чем crypto.subtle.encrypt.

Параллелизм и Web Workers

Web Crypto API частично освобождает от необходимости ручного параллелизма, но при высоких нагрузках выгодно использовать:

  • Web Workers
  • разделение данных на независимые задачи
  • batch processing

Однако при этом возникает новая проблема: стоимость передачи данных между потоками через structured cloning.

Типичные паттерны узких мест

На практике наиболее частые причины деградации производительности:

  • частые мелкие вызовы subtle
  • избыточное кодирование строк
  • создание ключей внутри горячего цикла
  • base64 как промежуточный формат
  • отсутствие батчинга
  • неэффективное разделение чанков

Скрытые системные зависимости

Производительность Web Crypto API зависит от:

  • операционной системы (Windows Crypto API, Secure Enclave, OpenSSL backend)
  • аппаратного ускорения
  • версии браузера
  • политики sandbox

Одинаковый код может демонстрировать различие в производительности в разы.

Практическая структура профилирования

При анализе узких мест обычно выделяются три слоя:

  1. JS-слой (подготовка данных, циклы, аллокации)
  2. API-слой (вызовы crypto.subtle)
  3. Нативный слой (алгоритмы, системные вызовы)

Точное определение узкого места возможно только при раздельном измерении каждого слоя с использованием комбинации DevTools и ручных замеров.