JavaScript: заметка о WebAssembly
Привет, друзья!
В 2019 году WebAssembly
(далее — WA
или wasm
) стал четвертым «языком» веба. Первые три — это, разумеется, HTML
, CSS
и JavaScript
. Сегодня wasm
поддерживается 94% браузеров. Он, как утверждается, обеспечивает скорость выполнения кода, близкую к нативной (естественной, т.е. максимально возможной для браузера), позволяя портировать в веб десктопные приложения и видеоигры.
Что не так с JS
?
JS
— это интерпретируемый язык программирования с динамической типизацией. Динамическая типизация означает, что тип переменной проверяется (определяется) во время выполнения кода. И что с того? — спросите вы. Вот как определяется переменная в C++
:
int n = 42
Такое определение сообщает компилятору тип переменной n
и ее локацию в памяти. И все это в одной строке. А в случае с определением аналогичной переменной в JS
(const n = 42
), движку сначала приходится определять, что переменная является числом, затем, что число является целым и т.д. при каждом выполнении программы. На определение и (часто) приведение (преобразование) типов каждой инструкции уходит какое-то время.
Процесс выполнения кода в JS
выглядит примерно так:
Разбор (парсинг) -> Компиляция и оптимизация -> Повторная (дополнительная) оптимизация или деоптимизация -> Выполнение -> Сборка мусора
А в WA
так:
Расшифровка (декодирование) -> Компиляция и оптимизация -> Выполнение
Это делает WA
более производительным, чем JS
. В защиту JS
можно сказать, что он разрабатывался для придания «легкой» интерактивности веб-страницам, а не для создания высокопроизводительных приложений, выполняющих сложные вычисления.
Что такое WA
?
Формальное определение гласит, что WA
— это открытый формат байт-кода, позволяющий переносить код, написанный на таких языках как C
, C++
, C#
, Rust
и Go
в низкоуровневые ассемблерные инструкции, выполняемые браузером. По сути, это виртуальный микропроцессор, преобразующий высокоуровневый язык в машинный код.
На изображении ниже представлен процесс преобразования функции для сложения чисел (add
), написанной на C++
, в бинарный (двоичный) формат:
Обратите внимание: WA
— это не язык программирования. Это технология (инструмент), позволяющая конвертировать код на указанных выше языках в понятный для браузеров машинный код.
Как WA
работает?
WA
— это веб-ассемблер. Но что такое ассемблер?
Если очень простыми словами, то
- Каждый процессор имеет определенную архитектуру, например,
x86
илиARM
. Процессор понимает только машинный код. - Писать машинный код, сами понимаете, сложно и утомительно. Для облегчения этого процесса существуют языки ассемблера.
- Ассемблер конвертирует инструкции на языке ассемблера в машинный код, понятный для процессора.
На изображении ниже представлен процесс выполнения программы на C
на компьютере:
Пример использования WA
Код примера с исходниками.
Что нужно сделать, чтобы использовать WA
в браузере (или на сервере в Node.js
)? И действительно ли WA-код
является более производительным, чем JS-код
? Давайте это выясним.
Предположим, что у нас имеется такая функция на C++
:
int fib(int n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
int ... int
означает, что функция принимает целое число и возвращает целое число. Как видите, наша функция вычисляет сумму чисел из последовательности Фибоначчи (далее — фибонача :)).
Сначала эту функцию необходимо конвертировать в wasm-модуль
. Для этого существуют разные способы и инструменты. В нашем случае для этого вполне подойдет WasmExplorer
.
Вставляем код в первую колонку, нажимаем Compile
для компиляции кода в Wat
(текстовое представление двоичного формата wasm
) и Download
для преобразования .wat
в .wasm
и скачивания файла (test.wasm
). Переименуем этот файл в fib.wasm
.
Подготовим проект. Нам потребуется сервер. Зачем? Об этом чуть позже.
# создаем директорию и переходим в нее
mkdir wasm-test
cd wasm-test
# инициализируем Node.js-проект
yarn init -yp
# устанавливаем зависимости для продакшна
yarn add express cors
# и для разработки
yarn add -D nodemon
Структура проекта:
- public
- fib.wasm
- index.html
- script.js
- server.mjs
- ...
Обратите внимание на расширение файла server
.
Добавляем в package.json
команду для запуска сервера для разработки:
"scripts": {
"dev": "nodemon server.mjs"
}
Код сервера (server.mjs
):
import { dirname, resolve } from 'path'
import { fileURLToPath } from 'url'
import express from 'express'
import cors from 'cors'
const __dirname = dirname(fileURLToPath(import.meta.url))
const app = express()
app.use(cors())
app.use(express.static('public'))
app.get('*', (req, res) => {
res.sendFile(resolve(`${__dirname}/${decodeURIComponent(req.url)}`))
})
app.listen(5000, () => {
console.log('