Как создавать более мощные веб-приложения на WebAssembly

Как создавать более мощные веб-приложения на WebAssembly

  • 28 ноября, 2023
  • читать 10 мин
Владимир Шайтан
Владимир Шайтан Technical Lead в Zoot, Преподаватель Компьютерной школы Hillel.

О чем этот материал?

Как вы поняли из заголовка этого материала, разговор пойдет о веб-приложениях, оптимизация и скорость работы которых должны быть на высоком уровне, выше, чем может предоставить нам самый оптимизированный и самый минимифицированный JavaScript код при равных условиях разработки (имеется в виду опыт и скилл разработчика/ков).

Высокопроизводительные веб-приложения

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

Например: графические приложения, игры, видео-обработка или научные вычисления

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

На это есть несколько причин:

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

  • Однопоточность: Если не брать во внимание WebWorkers, то JavaScript в браузере однопоточный, что ограничивает возможность параллельной обработки данных и использования потенциала многоядерных процессоров, которые фактически присутствуют почти на любом устройстве, если говорить о современных системах.
  • Динамическая типизация в JavaScript, безусловно, замедляет выполнение кода, так как добавляет дополнительный объем работы для вычисления типов данных.
  • Garbage Collector или, другими словами, сборщик мусора, в JavaScript является автоматическим. Управление памятью JavaScript происходит автоматически, что может приводить к задержкам в выполнении кода, когда будет активен процесс очистки оперативной памяти, которой занимается Garbage Collector.
  • Ограничение ресурсов. Браузер вводит определенные ограничения на использование аппаратных ресурсов при работе для обеспечения безопасности и стабильности работы.

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

Где обычно эти проблемы появляются?

Вернемся к примерам наших высокопроизводительных приложений, которым нужна скорость и ничего кроме скорости, и обсудим их подробнее:

Возьмем, например, Photoshop. Все мы знаем, что Photoshop — это графический редактор, подобных инструментов достаточно много.

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

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

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

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

Делаем браузерное окружение продуктивным

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

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

Что такое WebAssembly?

WebAssembly позволяет нам создавать более мощные приложения, более быстрые и безопасные, чем на JavaScript.

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

Код WebAssembly может быть напрямую скомпилирован в машинный код, который выполняется на аппаратном уровне, что обеспечивает высокую скорость выполнения. В случае JavaScript интерпретация и компиляция just-in-time добавляют дополнительные слои, которые замедляют выполнение.

Для общего понимания, какие слои добавляются при компиляции JavaScript:

  1. Интерпретация кода: сначала код JavaScript интерпретируется движком браузера
  2. Анализ «Горячих путей»: определяются части кода, которые используются чаще всего
  3. Компиляция «Горячих Путей»: эти частые части кода компилируются в оптимизированный машинный код
  4. Повышение производительности: выполнение оптимизированного кода ускоряется
  5. Адаптация к изменениям: Just-in-time компилятор адаптируется к изменениям в использовании кода, перекомпилируя новые горячие пути при необходимости

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

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

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

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

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

Возьмем в качестве примера одни из самых популярных движков для создания 3D видеоигр: Unity и Unreal Engine. Я не буду перечислять игры, которые были созданы с помощью этих движков, поверьте, их очень много.

Инструменты Unity и Unreal Engine поддерживают экспорт проектов в формат WebGL, который использует WebAssembly для выполнения кода в браузере. Когда проект Unity или Unreal Engine экспортируется в WebGL, большинство кода C# или C++ компилируется в WebAssembly. Это обеспечивает большую производительность по сравнению с JavaScript, что особенно важно для игр.

WebGL — это технология, которая позволяет отображать 3D-графику прямо в веб-браузере, без необходимости загружать дополнительные программы или плагины. Она использует возможности вашего компьютера для создания детализированных изображений и анимаций, как в видеоиграх, но работает непосредственно в веб-браузере.

То есть таким образом мы можем обойти ограничения браузера, это означает, что код WebAssembly выполняется на аппаратном уровне и работает продуктивнее.

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

Как использовать WebAssembly

  1. Сначала разработчики пишут код на таких языках, как C или Rust, поскольку JavaScript напрямую не преобразуется в WebAssembly.
  2. Затем этот код преобразуется в специальный формат (.wasm), который может быть прочитан и выполнен веб-браузером.
  3. Когда вы открываете веб-страницу, браузер загружает этот файл .wasm и выполняет его, используя JavaScript для управления процессом и взаимодействия с веб-страницей.

В качестве примера я создам простой калькулятор на C++ и подключу его к веб-странице с помощью WebAssembly.

Шаг 1: Код C++ для калькулятора

Напишем C++ код, который содержит четыре функции:

extern "C" {
    int add(int a, int b) {
        return a + b;
    }

    int subtract(int a, int b) {
        return a - b;
    }

    int multiply(int a, int b) {
        return a * b;
    }

    int divide(int a, int b) {
        if (b == 0) {
            return 0; // Проста обробка ділення на нуль
        }
        return a / b;
    }
}

Шаг 2: Компиляция в WebAssembly

Используя Emscripten, компилируем этот код в .wasm файл:

emcc calculator.cpp -s WASM=1 -o calculator.wasm

Эта команда создаст файл calculator.wasm.

Emscripten — это открытый инструментарий (open-source toolkit), который позволяет компилировать C и C++ код в WebAssembly и JavaScript. Это дает возможность запускать программы, написанные на этих языках, непосредственно в веб-браузере.

Шаг 3: Использование калькулятора на веб-странице

Далее интегрируем этот модуль WebAssembly в HTML с использованием JavaScript:

<!DOCTYPE html>
<html>
<body>
  <h3>Simple Calculator</h3>
  <p id="result"></p>
  <script>
    async function loadWasm(fileName) {
      const response = await fetch(fileName);
      const buffer = await response.arrayBuffer();
      const wasmModule = await WebAssembly.instantiate(buffer, {});
      return wasmModule.instance.exports;
    }

    async function performCalculations() {
      const calculator = await loadWasm('calculator.wasm');
      let result = `Add 5 + 2 = ${calculator.add(5, 2)}<br>`;
      result += `Subtract 5 - 2 = ${calculator.subtract(5, 2)}<br>`;
      result += `Multiply 5 * 2 = ${calculator.multiply(5, 2)}<br>`;
      result += `Divide 5 / 2 = ${calculator.divide(5, 2)}<br>`;
      document.getElementById('result').innerHTML = result;
    }

    performCalculations();
  </script>
</body>
</html>

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

Вот основные из них, которые используются в примере калькулятора:

  • `fetch()` — это JavaScript API для асинхронного осуществления HTTP запросов. В контексте WebAssembly, `fetch()` используется для загрузки `.wasm` файла с сервера.
  • `arrayBuffer()` — это метод ответа `fetch()`, который используется для получения данных в формате ArrayBuffer. ArrayBuffer — это тип данных в JavaScript, который представляет собой низкоуровневый, фиксированный буфер бинарных данных.
  • `WebAssembly.instantiate()` — это функция, которая используется для создания экземпляра модуля WebAssembly. Эта функция принимает бинарные данные (ArrayBuffer) и опционально объект импорта.

После вызова `WebAssembly.instantiate()`, вы получите объект, который содержит экземпляр WebAssembly модуля. Этот экземпляр содержит экспортированные функции WebAssembly, которые можно вызвать из JavaScript.

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

Языки программирования, которые могут быть скомпилированы в WebAssembly: C, C++, Rust, AssemblyScript, Go (Golang), Blazor (C#)

Из приведенных выше материалов становится понятно, что для того, чтобы использовать WebAssembly, нужно знать один из вышеперечисленных языков программирования и JavaScript. Но это не будет для вас проблемой, если вы знакомы только с языком JavaScript, я уверен, что вы слышали или пользовались TypeScript.

Рекомендуем курс по теме

Дело в том, что язык AssemblyScript — это язык программирования, который очень похож на TypeScript, но предназначен для компиляции в WebAssembly. Он позволяет разработчикам, которые уже знакомы с TypeScript или JavaScript, легче входить в мир WebAssembly.

Интересное

Сравнение производительности WebAssembly и JavaScript:

  • WebAssembly почти вдвое быстрее JavaScript в некоторых тестах производительности, особенно при выполнении вычислений высокой сложности, таких как вычисление факториалов. Однако JavaScript может показывать лучшие результаты в менее требовательных задачах.
  • Анализ реального использования Game Boy эмулятора Game Boy, написанного на AssemblyScript, показал, что WebAssembly имеет преимущество с точки зрения производительности над ES6 JavaScript.
  • WebAssembly компилируется на этапе сборки, поэтому браузеру не нужно компилировать его на лету, что ускоряет выполнение.
  • Сравнение также показало, что WebAssembly может улучшить энергоэффективность в среднем на 30%.

Реальные примеры применения WebAssembly:

  • WebAssembly используется во многих приложениях с высокой производительностью, включая peer-to-peer приложения (игры, совместное редактирование), приложения для музыки (стриминг, кэширование), распознавание изображений, виртуальную и дополненную реальность, CAD-приложения, научную визуализацию и симуляцию.
  • Существует выставка приложений и проектов, которые используют WebAssembly, что указывает на его растущее принятие в практических, пользовательских приложениях.
  • WebAssembly также используется для вычислений в облаке и устройствах IoT, демонстрируя его универсальность за пределами браузерной среды.

Преимущества безопасности WebAssembly по сравнению с JavaScript:

  • WebAssembly считается более безопасным, чем JavaScript, поскольку код WebAssembly компилируется в двоичный код, что затрудняет его реверсивную инженерию и эксплуатацию.
  • Модель безопасности WebAssembly разработана таким образом, чтобы защитить от неисправных или вредоносных модулей и предоставить разработчикам полезные примитивы и митигации для создания безопасных приложений с упором на изолированное выполнение модулей в безопасной среде.
  • Использование WebAssembly может также повысить безопасность кода JavaScript, поскольку WebAssembly действует как мощный компилятор, который может быть использован для развертывания безопасных приложений JavaScript.

Итог

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

Благодаря его способности к быстрому выполнению, глубокому контролю за системными ресурсами и принципам, заточенным на безопасность, WebAssembly эффективно повышает стандарты того, что мы считаем возможным в вебе. Он не стремится заменить JavaScript, а скорее дополняет его, позволяя разработчикам воплощать самые смелые идеи, ранее казавшиеся недостижимыми из-за ограничений браузеров.

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