Бенчмаркинг операций Web Crypto API

Бенчмаркинг операций Web Crypto API требует учета асинхронной природы API, влияния окружения выполнения (браузер, версия движка, аппаратное ускорение), а также особенностей работы с бинарными данными в JavaScript. Любые измерения должны опираться не только на время выполнения отдельных вызовов, но и на устойчивые метрики пропускной способности, стабильность результатов и поведение под нагрузкой.

Основной инструмент измерения в браузере — performance.now(), обеспечивающий высокоточное время в миллисекундах с дробной частью. При этом важно учитывать, что единичные измерения почти всегда нерепрезентативны из-за JIT-компиляции, кэширования и фоновых процессов.


Web Crypto API построен вокруг SubtleCrypto, и все операции выполняются асинхронно через промисы:

  • crypto.subtle.digest
  • crypto.subtle.encrypt
  • crypto.subtle.decrypt
  • crypto.subtle.sign
  • crypto.subtle.verify
  • crypto.subtle.generateKey
  • crypto.subtle.importKey
  • crypto.subtle.exportKey

Измерение должно учитывать:

  • прогрев (warm-up) перед тестами
  • многократные итерации
  • исключение первого вызова из статистики
  • фиксированный размер входных данных
  • отсутствие влияния логирования и DOM-операций

Базовая схема бенчмарка

Типовой шаблон измерения операции выглядит следующим образом:

async function benchmark(fn, iterations = 1000) {
  // прогрев
  for (let i = 0; i < 50; i++) {
    await fn();
  }

  const start = performance.now();

  for (let i = 0; i < iterations; i++) {
    await fn();
  }

  const end = performance.now();

  return {
    iterations,
    totalTime: end - start,
    opsPerSecond: (iterations / (end - start)) * 1000
  };
}

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


Бенчмаркинг хеширования (digest)

Операция crypto.subtle.digest часто используется как базовый ориентир производительности Web Crypto API.

Пример SHA-256:

const data = new TextEncoder().encode("benchmark data".repeat(1000));

async function hash() {
  return crypto.subtle.digest("SHA-256", data);
}

Особенности измерения:

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

При увеличении размера входа наблюдается почти линейное масштабирование времени, что удобно для оценки пропускной способности в MB/s.


Бенчмаркинг симметричного шифрования

Для AES-GCM:

const key = await crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 },
  true,
  ["encrypt", "decrypt"]
);

const iv = crypto.getRandomValues(new Uint8Array(12));
const data = crypto.getRandomValues(new Uint8Array(1024 * 1024));

async function encryptOp() {
  return crypto.subtle.encrypt(
    { name: "AES-GCM", iv },
    key,
    data
  );
}

При измерении важно разделять:

  • генерацию ключа (дорогая операция)
  • шифрование
  • дешифрование

Отдельное измерение каждого этапа позволяет выявить узкие места.


Бенчмаркинг асимметричных операций

RSA и ECDSA значительно дороже по вычислениям, поэтому количество итераций обычно меньше.

Пример ECDSA подписи:

const keyPair = await crypto.subtle.generateKey(
  {
    name: "ECDSA",
    namedCurve: "P-256"
  },
  true,
  ["sign", "verify"]
);

const data = new TextEncoder().encode("message");

async function signOp() {
  return crypto.subtle.sign(
    { name: "ECDSA", hash: "SHA-256" },
    keyPair.privateKey,
    data
  );
}

При бенчмаркинге асимметричных алгоритмов важно учитывать:

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

Влияние размера данных

Практически все операции Web Crypto API масштабируются по-разному:

  • SHA-256: почти линейный рост
  • AES-GCM: линейный рост с низким коэффициентом наклона
  • RSA: слабая зависимость от размера данных, но сильная зависимость от ключа
  • ECDSA: стабильная стоимость на операцию

Для корректного анализа используется серия тестов:

const sizes = [16, 256, 4096, 65536, 1048576];

for (const size of sizes) {
  const data = crypto.getRandomValues(new Uint8Array(size));
  // измерение операции
}

Изоляция накладных расходов JavaScript

Критическая ошибка в бенчмарках — включение лишних затрат:

  • создание Uint8Array внутри теста
  • вызов TextEncoder в каждой итерации
  • логирование в консоль
  • аллокации объектов в цикле

Правильный подход:

  • заранее подготовить данные
  • использовать повторно одни и те же буферы
  • минимизировать GC-активность

Влияние асинхронности

Хотя Web Crypto API выглядит как обычный Promise-based API, фактическое выполнение часто происходит вне основного потока (иногда с использованием нативных ускорений).

Это приводит к эффектам:

  • высокая латентность первого вызова
  • стабилизация после прогрева
  • зависимость от event loop

Поэтому измерение должно учитывать “плато производительности”, а не отдельные вызовы.


Worker-based бенчмаркинг

Для более точных результатов используется Web Worker:

// worker.js
self.onmess age = async (e) => {
  const { data } = e.data;
  const start = performance.now();

  for (let i = 0; i < 1000; i++) {
    await crypto.subtle.digest("SHA-256", data);
  }

  const end = performance.now();

  self.postMessage({ time: end - start });
};

Причины использования воркеров:

  • отсутствие блокировки UI
  • уменьшение влияния рендеринга
  • более стабильные результаты

Метрики производительности

При анализе Web Crypto API используются несколько ключевых метрик:

  • Latency (задержка одной операции)
  • Throughput (операций в секунду)
  • MB/s (пропускная способность для хешей и шифрования)
  • Warm-up stability (разница между первыми и последующими измерениями)
  • Variance (разброс значений)

Аппаратное ускорение

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

  • CPU (AES-NI, ARM Crypto Extensions)
  • браузера (V8, SpiderMonkey, JavaScriptCore)
  • операционной системы
  • включенного аппаратного ускорения криптографии

Одни и те же тесты могут отличаться в разы на разных устройствах.


Частые ошибки в бенчмарках

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

Сравнение алгоритмов

Корректный бенчмарк всегда строится как набор независимых тестов:

  • SHA-256 vs SHA-512
  • AES-GCM vs AES-CBC
  • ECDSA vs RSA-PSS

Сравнение должно учитывать не только скорость, но и масштабируемость при увеличении объема данных.


Интерпретация результатов

Полученные значения нельзя рассматривать изолированно. Важны:

  • стабильность между прогоном
  • повторяемость результатов
  • зависимость от размера данных
  • поведение при параллельных вызовах

Резкие колебания обычно указывают на влияние внешних факторов: JIT-компиляции, фоновых задач или особенностей планировщика потоков браузера.