- PVSM.RU - https://www.pvsm.ru -
Если вы – из тех программистов, которые в новогоднюю ночь пообещали себе писать более быстрый код, сегодня у вас есть шанс это обещание выполнить. Мы поговорим о том, как ускорить работу веб-решений с использованием технологии WebAssembly (сокращённо её называют wasm). Технология это очень молодая, сейчас – пора её становления, однако, она вполне может оказать серьёзное влияние на будущее разработки для интернета.
Здесь я расскажу о том, как создавать модули WebAssembly, как с ними работать, как вызывать их из клиентского кода в браузере так, будто это модули, написанные на JS. Мы рассмотрим два набора реализаций алгоритма [1] поиска чисел Фибоначчи. Один из них представлен обычными JavaScript-функциями, второй – написан на C и преобразован в модуль WebAssembly. Это позволит сравнить производительность wasm и JS при решении схожих задач.
Мы будем исследовать три подхода к поиску чисел Фибоначчи. Первый использует цикл. Второй задействует рекурсию. Третий основан на технике мемоизации. Все они реализованы на JavaScript и на C.
Вот [2] JS-код:
function fiboJs(num){
var a = 1, b = 0, temp;
while (num >= 0){
temp = a;
a = a + b;
b = temp;
num--;
}
return b;
}
const fiboJsRec = (num) => {
if (num <= 1) return 1;
return fiboJsRec(num - 1) + fiboJsRec(num - 2);
}
const fiboJsMemo = (num, memo) => {
memo = memo || {};
if (memo[num]) return memo[num];
if (num <= 1) return 1;
return memo[num] = fiboJsMemo(num - 1, memo) + fiboJsMemo(num - 2, memo);
}
module.exports = {fiboJs, fiboJsRec, fiboJsMemo};
Вот [3] – то же самое, написанное на C:
int fibonacci(int n) {
int a = 1;
int b = 1;
while (n-- > 1) {
int t = a;
a = b;
b += t;
}
return b;
}
int fibonacciRec(int num) {
if (num <= 1) return 1;
return fibonacciRec(num - 1) + fibonacciRec(num - 2);
}
int memo[10000];
int fibonacciMemo(int n) {
if (memo[n] != -1) return memo[n];
if (n == 1 || n == 2) {
return 1;
} else {
return memo[n] = fibonacciMemo(n - 1) + fibonacciMemo(n - 2);
}
}
Тонкости реализации обсуждать здесь не будем, всё же, наша основная цель в другом. Если хотите, здесь [4] можете почитать о числах Фибоначчи, вот [5] – интересное обсуждение рекурсивного подхода к поиску этих чисел, а вот [6] – материал про мемоизацию. Прежде чем переходить к практическому примеру, остановимся ненадолго на особенностях технологий, имеющих отношение к нашему разговору.
Технология WebAssembly – это инициатива, направленная на создание безопасного, переносимого и быстрого для загрузки и исполнения формата кода, подходящего для Web. WebAssembly – это не язык программирования. Это – цель компиляции, у которой имеются спецификации текстового и бинарного форматов. Это означает, что другие низкоуровневые языки, такие, как C/C++, Rust, Swift, и так далее, можно скомпилировать в WebAssembly. WebAssembly даёт доступ к тем же API, что и браузерный JavaScript, органично встраивается в существующий стек технологий. Это отличает wasm от чего-то вроде Java-апплетов [7]. Архитектура [8] WebAssembly – это результат коллективной работы сообщества, в котором имеются представители разработчиков всех ведущих веб-браузеров. Для компиляции кода в формат WebAssembly используется Emscripten.
Emscripten – это компилятор из байт-кода LLVM в JavaScript. То есть, с его помощью можно скомпилировать в JavaScript программы, написанные на C/C++ или на любых других языках, код на которых можно преобразовать в формат LLVM. Emscripten предоставляет набор API для портирования кода в формат, подходящий для веб. Этому проекту уже много лет, в основном его используют для преобразования игр в их браузерные варианты. Emscripten позволяет достичь высокой производительности благодаря тому, что он генерирует код, соответствующий стандартам Asm.js, о котором ниже, но недавно его успешно оснастили поддержкой WebAssembly.
Asm.js – это низкоуровневое оптимизированное подмножество JavaScript, обеспечивающее линейный доступ к памяти с помощью типизированных массивов и поддерживающее аннотации с информацией о типах данных. Asm.js позволяет повысить производительность решений. Это – тоже не новый язык программирования, поэтому, если браузер его не поддерживает, Asm.js-код будет выполняться как обычный JavaScript, то есть, от его использования не удастся получить прироста производительности.
WebAssembly, по состоянию на 10.01.2017, поддерживается в Chrome Canary [9] и Firefox [10]. Для того, чтобы wasm-код заработал, нужно активировать соответствующую возможность в настройках. В Safari [11] поддержка WebAssembly пока в стадии разработки. В V8 wasm включён [12] по умолчанию.
Вот [13] интересное видео о движке V8, о текущем состоянии поддержки JavaScript и WebAssembly c Chrome Dev Summit 2016 [14].
Займёмся преобразованием программы, написанной на C, в формат wasm [15]. Для того, чтобы это сделать, я решил воспользоваться возможностью создания автономн [16]ых модулей WebAssembly. При таком подходе на выходе компилятора мы получаем только файл с кодом WebAssembly, без дополнительных вспомогательных .js-файлов.
Такой подход основан на концепции дополнительных модулей [17] (side module) Emscripten. Здесь имеет смысл использовать подобные модули, так как они, в сущности, очень похожи на динамические библиотеки. Например, системные библиотеки не подключаются к ним автоматически, они представляют собой некие самодостаточные блоки кода, выдаваемого компилятором.
$ emcc fibonacci.c -Os -s WASM=1 -s SIDE_MODULE=1 -o fibonacci.wasm
После получения бинарного файла нам нужно лишь загрузить его в браузер. Для того, чтобы это сделать, API WebAssembly [18] предоставляет объект верхнего уровня WebAssembly, который содержит методы, нужные для того, чтобы скомпилировать [19] и создать [20] экземпляр модуля. Вот [21] простой метод, основанный на gist [22] Алона Закаи, который работает как универсальный загрузчик.
module.exports = (filename) => {
return fetch(filename)
.then(response => response.arrayBuffer())
.then(buffer => WebAssembly.compile(buffer))
.then(module => {
const imports = {
env: {
memoryBase: 0,
tableBase: 0,
memory: new WebAssembly.Memory({
initial: 256
}),
table: new WebAssembly.Table({
initial: 0,
element: 'anyfunc'
})
}
};
return new WebAssembly.Instance(module, imports);
});
}
Самое приятное здесь то, что всё происходит асинхронно. Сначала мы берём содержимое файла и конвертируем его в структуру данных формата ArrayBuffer [23]. Буфер содержит исходные двоичные данные фиксированной длины. Напрямую исполнять их мы не можем, именно поэтому на следующем шаге буфер передают методу WebAssembly.compile, который возвращает WebAssembly.Module [24], экземпляр которого, в итоге, можно создать с помощью WebAssembly.Instance.
Почитайте описание двоичного формат [25]а, который использует WebAssembly, если хотите глубже во всём этом разобраться.
Пришло время взглянуть на то, как использовать wasm-модуль, как протестировать его производительность и сравнить её со скоростью работы JavaScript. На вход исследуемых функций будем подавать число 40. Вот [26] код тестов:
const Benchmark = require('benchmark');
const loadModule = require('./loader');
const {fiboJs, fiboJsRec, fiboJsMemo} = require('./fibo.js');
const suite = new Benchmark.Suite;
const numToFibo = 40;
window.Benchmark = Benchmark; //Benchmark.js uses the global object internally
console.info('Benchmark started');
loadModule('fibonacci.wasm').then(instance => {
const fiboNative = instance.exports._fibonacci;
const fiboNativeRec = instance.exports._fibonacciRec;
const fiboNativeMemo = instance.exports._fibonacciMemo;
suite
.add('Js', () => fiboJs(numToFibo))
.add('Js recursive', () => fiboJsRec(numToFibo))
.add('Js memoization', () => fiboJsMemo(numToFibo))
.add('Native', () => fiboNative(numToFibo))
.add('Native recursive', () => fiboNativeRec(numToFibo))
.add('Native memoization', () => fiboNativeMemo(numToFibo))
.on('cycle', (event) => console.log(String(event.target)))
.on('complete', function() {
console.log('Fastest: ' + this.filter('fastest').map('name'));
console.log('Slowest: ' + this.filter('slowest').map('name'));
console.info('Benchmark finished');
})
.run({ 'async': true });
});
А вот – результаты. На этой странице [27], кстати, вы можете попробовать всё сами.
JS loop x 8,605,838 ops/sec ±1.17% (55 runs sampled)
JS recursive x 0.65 ops/sec ±1.09% (6 runs sampled)
JS memoization x 407,714 ops/sec ±0.95% (59 runs sampled)
Native loop x 11,166,298 ops/sec ±1.18% (54 runs sampled)
Native recursive x 2.20 ops/sec ±1.58% (10 runs sampled)
Native memoization x 30,886,062 ops/sec ±1.64% (56 runs sampled)
Fastest: Native memoization
Slowest: JS recursive
Хорошо заметно, что wasm-код, полученный из программы на C (в выводе теста он обозначен как «Native») быстрее чем аналогичный код, написанный на обычном JavaScript («JS» в выводе теста). При этом самой быстрой реализацией оказалась wasm-функция поиска чисел Фибоначчи, применяющая технику мемоизации, а самой медленной – рекурсивная функция на JavaScript.
Если посидеть над полученными результатами с калькулятором, можно выяснить следующее:
Надеюсь, вам понравился мой краткий рассказ о возможностях WebAssembly, и о том, чего можно достичь с помощью этой технологии уже сегодня. За рамками данного материала осталось немало тем, среди которых – wasm-модули при компиляции которых создаются и вспомогательные файлы, различные способы взаимодействия между скомпилированным кодом на C и кодом на JS, динамическое связывание. Вполне возможно, что мы с вами их когда-нибудь обсудим. Теперь же у вас есть всё необходимое для начала экспериментов с WebAssembly. Кстати, можете ещё взглянуть на официальное руководство для разработчиков [28] WebAssembly.
Для того, чтобы быть в курсе последних событий в области wasm, добавьте в закладки эту страницу [29] со сведениями о достижениях и планах развития проекта. Полезно будет заглядывать и в журнал изменений [30] Emscripten.
Кстати, а вы уже думали о том, как воспользоваться возможностями WebAssembly в своих проектах?
Автор: RUVDS.com
Источник [31]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/234797
Ссылки в тексте:
[1] алгоритма: https://medium.com/developers-writing/fibonacci-sequence-algorithm-in-javascript-b253dc7e320e
[2] Вот: https://gist.github.com/zzarcon/f25863252278b22e79f2cebaf11bb9da#file-fibo-js
[3] Вот: https://gist.github.com/zzarcon/4b8cd1c3b686f81e56c554b96dfc9600#file-fibonacci-c
[4] здесь: https://en.wikipedia.org/wiki/Fibonacci_number
[5] вот: http://stackoverflow.com/questions/8965006/java-recursive-fibonacci-sequence
[6] вот: https://en.wikipedia.org/wiki/Memoization
[7] Java-апплетов: https://en.wikipedia.org/wiki/Java_applet
[8] Архитектура: https://github.com/WebAssembly/design
[9] Chrome Canary: https://www.chromestatus.com/features/5453022515691520
[10] Firefox: https://hacks.mozilla.org/2016/03/a-webassembly-milestone/
[11] Safari: https://webkit.org/status/#specification-webassembly
[12] включён: https://chromium.googlesource.com/v8/v8/+/34b63f050b1a247bb64ddc91c967501ce04e011f
[13] Вот: https://www.youtube.com/watch?v=PvZdTZ1Nl5o
[14] Chrome Dev Summit 2016: https://www.youtube.com/playlist?list=PLNYkxOF6rcIBTs2KPy1E6tIYaWoFcG3uj
[15] wasm: http://webassembly.org/docs/semantics/
[16] автономн: https://github.com/kripken/emscripten/wiki/WebAssembly-Standalone
[17] дополнительных модулей: https://github.com/kripken/emscripten/wiki/Linking
[18] API WebAssembly: https://github.com/WebAssembly/design/blob/master/JS.md
[19] скомпилировать: https://github.com/WebAssembly/design/blob/master/JS.md#webassemblycompile
[20] создать: https://github.com/WebAssembly/design/blob/master/JS.md#webassemblyinstance-constructor
[21] Вот: https://gist.github.com/zzarcon/f41cb1fddaff4097c216bf38339a296b#file-loader-js
[22] gist: https://gist.github.com/kripken/59c67556dc03bb6d57052fedef1e61ab
[23] ArrayBuffer: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
[24] WebAssembly.Module: https://github.com/WebAssembly/design/blob/master/JS.md#webassemblymodule-constructor
[25] двоичного формат: https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md
[26] Вот: https://gist.github.com/zzarcon/e0deeff77a365ea216294db0090632fe#file-benchmark-js
[27] этой странице: https://zzarcon.github.io/WebAssembly-demo/
[28] руководство для разработчиков: http://webassembly.org/getting-started/developers-guide/
[29] эту страницу: http://webassembly.org/roadmap/
[30] журнал изменений: https://github.com/kripken/emscripten/blob/master/ChangeLog.markdown
[31] Источник: https://habrahabr.ru/post/319834/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.