Перейти к содержанию

Как сравнивать JavaScript и WebAssembly

«Мера» умеет выбрать Wasm или JavaScript и выполняет вычисление через Worker. Теперь можно поставить вопрос о скорости. Ответ требует измерения: язык реализации, режим компиляции и название технологии сами по себе не показывают, сколько времени ждёт человек от выбора файла до готового результата.

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

Три разных вопроса

Первый вопрос — сколько занимает чистый расчёт на уже готовой строке в прогретом модуле. Второй — сколько стоит первая загрузка движка. Третий — сколько длится вся пользовательская операция с чтением файла, декодированием, передачей в Worker и отображением. У этих вопросов разные границы, поэтому нельзя объединять их в одно число без пояснения.

Например, Wasm-функция может обработать строку быстро, но первый посетитель ещё должен получить бинарный ресурс. Если маленький файл анализируется один раз, эта стоимость может определять весь результат. При десятках повторных операций загрузка распределяется иначе. Выбор зависит от настоящего сценария приложения.

Для «Меры» основной пользовательский интервал начинается после отправки формы и заканчивается перед показом готовых чисел. Отдельно будем записывать этапы. Так можно понять, следует ли улучшать алгоритм, загрузку пакета или передачу данных между потоками.

Интервал Начало Конец
Чтение и декодирование До readTextFile Готовая строка
Операция движка До вызова клиента Получен ответ Worker
Отображение До обновления DOM Обновлены значения
Полное действие Отправка формы Готов результат интерфейса

Интервал движка в такой таблице включает сообщения и возможную инициализацию. Называть его «только Rust» неправильно. Для чистого расчёта понадобится отметка непосредственно внутри соответствующего контекста выполнения.

Монотонное время в одном контексте

Для коротких промежутков используем performance.now(). Его смысл описан в документации Performance API: это время относительно начала контекста с монотонной шкалой. Разность двух отметок в одном контексте удобна для локального интервала.

Следующий фрагмент относится к обработчику формы. client.analyze здесь ожидает ответ Worker, а остальные функции уже определены в проекте:

const started = performance.now();
const text = await readTextFile(file);
const decoded = performance.now();
const response = await client.analyze(text);
const computed = performance.now();
showResult(response.result);
const rendered = performance.now();

const observation = {
  readAndDecodeMs: decoded - started,
  engineRoundTripMs: computed - decoded,
  domUpdateMs: rendered - computed,
  totalMs: rendered - started,
  engine: response.engine,
  inputBytes: file.size
};

Последняя отметка означает завершение наших DOM-операций, а не доказательство, что пиксели уже нарисованы на экране. Для исследования визуального появления понадобится другой договор наблюдения. Не следует переименовывать domUpdateMs в «время первого отображения» только потому, что оно находится в конце обработчика.

У Worker собственный контекст времени. Не вычитайте бездумно его performance.now() из отметки страницы. Проще измерять локальный расчёт внутри Worker и возвращать длительность, а round trip — двумя отметками главного потока. Так каждое значение имеет одну понятную систему отсчёта.

Холодный и прогретый сценарии

Холодный сценарий рассматривает первый вызов после создания нового потока. Прогретый — дальнейшие вызовы с тем же экземпляром. Запишите, какие ресурсы уже находятся в браузерном кэше. Перезагрузка вкладки не всегда означает повторную сетевую передачу бинарника, поэтому «холодная сеть» и «новый экземпляр» — разные условия.

Не исключайте стоимость загрузки из итогового вывода о пользовательском сценарии. Можно показать её отдельным столбцом, но человек всё равно ждёт готовность. Для короткой задачи иногда разумнее JavaScript, который уже входит в страницу. Для тяжёлого повторного расчёта соотношение может быть иным.

Если запасной движок сработал после неудачной попытки Wasm, такой запуск не является чистым сравнением JavaScript. В него входит время отказа загрузки. Для будущего эксперимента предусмотрим явный режим выбора: только JavaScript либо только Wasm. Автоматический выбор оставим для обычного использования.

Сопоставимые исходные данные

Подготовьте набор текстов разных объёмов с известными правилами строк: небольшой пример, несколько сотен KiB и значение близко к лимиту. Укажите, сколько байт и какие типы символов входят в каждый. Генерация файла из повторяющейся строки удобна для контроля, но не обязана представлять реальную библиотеку.

Перед временем сравните результат. Все поля и корзины должны совпасть. Если одна реализация считает UTF-16, а другая Unicode-скалярные значения, нельзя продолжать и объявлять одну быстрее. Сначала исправляется договор. То же относится к завершающему LF и правилам пробельных строк.

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

Несколько наблюдений вместо одного числа

Одиночный запуск может совпасть с фоновой задачей браузера, сборкой мусора или изменением частоты процессора. Записывайте серию одинаковых условий и весь набор результатов. Медиана и разброс помогают увидеть стабильность, но не превращают плохое сравнение в хорошее автоматически.

Порядок движков тоже влияет на условия. Полезно чередовать варианты и отдельно отмечать прогрев. Не выполняйте подробное логирование внутри горячего цикла, которое присутствует только у одной реализации. Объём возвращаемого результата и порядок вызовов должны быть одинаковыми.

Отчёт указывает браузер, устройство, версии кода, режим Worker, состояние кэша и размеры артефакта. Без этих сведений фраза «Wasm быстрее в пять раз» мало что объясняет. В этой серии такого вывода нет: мы подготовили механизм и оставили измерения следующему этапу работы.

Вывод зависит от наблюдаемой задачи

Если выигрыш появился только на самом большом подготовленном файле, не распространяйте его на все страницы сайта. Анализатор может включаться редко, а его пакет увеличивает первую загрузку. Итоговое решение учитывает частоту использования, типичный объём и приемлемую сложность сопровождения. Технический отчёт полезен тогда, когда помогает выбрать конкретное изменение, а не только публикует впечатляющее отношение двух чисел.

После будущего сравнения решение может быть простым: сохранить JavaScript, объединить экспорты Wasm или перенести чтение в Worker. Цель измерения — выбрать изменение для конкретного приложения. Финальный урок сформирует договор передачи модуля, чтобы его результат и ограничения не потерялись при включении в другой веб-проект.

Оглавление серии