JavaScript: заметка о WebAssembly

rhb3d1jrudzlxpk5mearo_5lfjc.jpeg


Привет, друзья!

В 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++, в бинарный (двоичный) формат:


image-loader.svg


Обратите внимание: WA — это не язык программирования. Это технология (инструмент), позволяющая конвертировать код на указанных выше языках в понятный для браузеров машинный код.

Как WA работает?

WA — это веб-ассемблер. Но что такое ассемблер?

Если очень простыми словами, то


  • Каждый процессор имеет определенную архитектуру, например, x86 или ARM. Процессор понимает только машинный код.
  • Писать машинный код, сами понимаете, сложно и утомительно. Для облегчения этого процесса существуют языки ассемблера.
  • Ассемблер конвертирует инструкции на языке ассемблера в машинный код, понятный для процессора.

На изображении ниже представлен процесс выполнения программы на C на компьютере:


image-loader.svg


Пример использования 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.


image-loader.svg


Вставляем код в первую колонку, нажимаем 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('
    
            

© Habrahabr.ru