Як створювати більш потужні вебзастосунки на WebAssembly

Як створювати більш потужні вебзастосунки на WebAssembly

  • 28 листопада, 2023
  • читати 10 хв
Володимир Шайтан
Володимир Шайтан Lead Front-end Developer у One Source, Викладач Комп'ютерної школи 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

Спочатку розробники пишуть код на таких мовах, як C або Rust, оскільки JavaScript безпосередньо не перетворюється на WebAssembly.

Потім цей код перетворюють у спеціальний формат (.wasm), який може читати і виконувати веббраузер.

Коли ви відкриваєте вебсторінку, браузер завантажує цей файл .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 емулятора, написаного на 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 вже відкриває двері до нової ери вебзастосунків, що мають небувалу потужність та гнучкість.