Это шестой пост из серии, посвященной изучению JavaScript и его компонентов. В процессе идентификации и описания основных элементов мы также делимся некоторыми практическими правилами, которые мы используем при создании SessionStack, легкого приложения JavaScript, которое должно быть надежным и высокопроизводительным, чтобы помочь пользователям увидеть и воспроизвести реальные дефекты веб-приложений. -время.
Если вы пропустили предыдущие главы, вы можете найти их здесь:
- Обзор движка, среды выполнения и стека вызовов
- Внутри движка Google V8 + 5 советов по написанию оптимизированного кода
- Управление памятью + как справиться с 4 распространенными утечками памяти
- Цикл событий и рост асинхронного программирования + 5 способов улучшить кодирование с помощью async / await
- Глубокое погружение в WebSockets и HTTP / 2 с SSE + как выбрать правильный путь
На этот раз мы разберем WebAssembly, чтобы проанализировать, как он работает, и, что более важно, как он сочетается с JavaScript с точки зрения производительности: время загрузки, скорость выполнения, сборка мусора, использование памяти, доступ к API платформы, отладка, многопоточность и переносимость.
То, как мы создаем веб-приложения, находится на грани революции - это еще только начало, но то, как мы думаем о веб-приложениях, изменится.
Во-первых, давайте посмотрим, что делает WebAssembly.
WebAssembly (также известный как wasm) - это эффективный низкоуровневый байт-код для Интернета.
WASM позволяет вам использовать языки, отличные от JavaScript (например, C, C ++, Rust или другие), писать в нем свою программу, а затем скомпилировать ее (заранее) в WebAssembly.
В результате получается веб-приложение, которое очень быстро загружается и запускается.
Время загрузки
Чтобы загрузить JavaScript, браузер должен загрузить все текстовые файлы .js.
WebAssembly быстрее загружается в браузере, потому что через Интернет нужно транспортировать только уже скомпилированные файлы wasm. Wasm - это низкоуровневый ассемблерный язык с очень компактным двоичным форматом.
Исполнение
Сегодня Wasm работает всего на 20% медленнее , чем выполнение нативного кода. Безусловно, это поразительный результат. Это формат, который скомпилирован в среду песочницы и работает с целым рядом ограничений, чтобы убедиться, что в нем нет уязвимостей безопасности или он защищен от них. Замедление минимально по сравнению с истинно нативным кодом. Более того, в будущем это будет еще быстрее.
Более того, он не зависит от браузера - все основные движки добавили поддержку WebAssembly и теперь предлагают схожее время выполнения.
Чтобы понять, насколько быстрее выполняется WebAssembly по сравнению с JavaScript, вам следует сначала прочитать нашу статью о том, как работает движок JavaScript.
Давайте посмотрим, что происходит в V8, в качестве краткого обзора:
Слева у нас есть исходный код JavaScript, содержащий функции JavaScript. Сначала его нужно проанализировать, чтобы он преобразовал все строки в токены и сгенерировал абстрактное синтаксическое дерево (AST). AST - это представление логики вашей программы JavaScript в памяти. Как только это представление сгенерировано, V8 переходит прямо к машинному коду. Вы в основном идете по дереву, генерируете машинный код и вот ваша скомпилированная функция. Нет никаких реальных попыток ускорить это.
Теперь давайте посмотрим, что делает конвейер V8 на следующем этапе:
На этот раз у нас есть TurboFan, один из оптимизирующих компиляторов V8. Пока ваше приложение JavaScript работает, внутри V8 выполняется много кода. TurboFan отслеживает, если что-то работает медленно, есть ли узкие места и горячие точки, чтобы оптимизировать их. Он проталкивает их через этот бэкэнд, который представляет собой оптимизированную JIT, которая создает гораздо более быстрый код для тех функций, которые занимают большую часть вашего процессора.
Это решает проблему, но проблема здесь в том, что процесс анализа кода и принятия решения о том, что оптимизировать, также потребляет ресурсы ЦП. Это, в свою очередь, означает более высокий расход заряда батареи, особенно на мобильных устройствах.
Что ж, wasm не нужно все это - он подключается к рабочему процессу следующим образом:
Wasm уже прошел оптимизацию на этапе компиляции. Сверху тоже не нужен парсинг. У вас есть оптимизированный двоичный файл, который может напрямую подключаться к бэкэнду, который может генерировать машинный код. Все оптимизации были выполнены компилятором во внешнем интерфейсе.
Это делает выполнение wasm намного более эффективным, поскольку многие этапы процесса можно просто пропустить.
Модель памяти
Память программы C ++, например, скомпилированной в WebAssembly, представляет собой непрерывный блок памяти без «дыр» в нем. Одной из особенностей wasm, которая помогает повысить безопасность, является концепция отделения стека выполнения от линейной памяти. В программе на C ++ у вас есть куча, которую вы выделяете из нижней части кучи и наращиваете стек из верхней части кучи. Можно взять указатель, а затем посмотреть в памяти стека, чтобы поиграть с переменными, которых вы не должны касаться.
Это ловушка, которую используют многие вредоносные программы.
WebAssembly использует совершенно другую модель. Стек выполнения отделен от самой программы WebAssembly, поэтому вы не можете вносить изменения внутри него и изменять такие вещи, как переменные. Кроме того, функции используют целочисленные смещения, а не указатели. Функции указывают на таблицу косвенных функций. А затем эти прямые вычисляемые числа переходят в функцию внутри модуля. Он построен таким образом, что вы можете загружать несколько модулей wasm бок о бок, смещать все индексы, и все это работает хорошо.
Более подробную информацию о модели памяти и управлении в JavaScript вы можете найти в нашем очень подробном сообщении по теме.
Вывоз мусора
Вы уже знаете, что управление памятью в JavaScript осуществляется с помощью сборщика мусора.
Случай с WebAssembly немного другой. Он поддерживает языки, которые управляют памятью вручную. Вы можете поставить свой собственный GC с вашими модулями wasm, но это сложная задача.
В настоящее время WebAssembly разработан на основе сценариев использования C ++ и RUST. Поскольку wasm очень низкоуровневый, логично, что языки программирования, которые всего на один шаг выше языка ассемблера, будут легко скомпилированы для него. C может использовать обычный malloc, C ++ может использовать интеллектуальные указатели, Rust использует совершенно другую парадигму (это совершенно другая тема). Эти языки не используют сборщики мусора, поэтому им не нужны все сложные средства выполнения для отслеживания памяти. WebAssembly им подходит.
Кроме того, эти языки не на 100% предназначены для вызова сложных вещей JavaScript, таких как изменение DOM. Нет смысла писать целое HTML-приложение на C ++, потому что C ++ не предназначен для этого. В большинстве случаев, когда инженеры пишут C ++ или Rust, они нацелены на WebGL или высокооптимизированные библиотеки (например, тяжелые математические вычисления).
Однако в будущем WebAssembly будет поддерживать языки, которые не поставляются с GC.
Доступ к платформе API
В зависимости от среды выполнения, в которой выполняется JavaScript, предоставляется доступ к API-интерфейсам, зависящим от платформы, которые могут быть доступны напрямую через ваше приложение JavaScript. Например, если вы используете JavaScript в браузере, у вас есть набор веб-API, которые веб-приложение может вызывать для управления функциями веб-браузера / устройства и доступа к таким вещам, как DOM, CSSOM, WebGL. , IndexedDB, API веб-аудио и др.
Что ж, у модулей WebAssembly нет доступа к каким-либо API платформы. Все опосредовано JavaScript. Если вы хотите получить доступ к некоторым API-интерфейсам, зависящим от платформы, внутри вашего модуля WebAssembly, вы должны вызвать его через JavaScript.
Например, если вы хотите console.log, вы должны вызвать его через JavaScript, а не свой код C ++. И за эти вызовы JavaScript взимается штраф.
Так будет не всегда. Спецификация предоставит платформенные API для wasm в будущем, и вы сможете отправлять свои приложения без JavaScript.
Исходные карты
Когда вы минимизируете свой код JavaScript, вам нужен способ его правильной отладки. Здесь на помощь приходят Карты источников.
По сути, Source Maps - это способ сопоставить объединенный / минимизированный файл обратно в нестроенное состояние. Когда вы создаете для производства, наряду с уменьшением и объединением файлов JavaScript, вы создаете исходную карту, которая содержит информацию об исходных файлах. Когда вы запрашиваете номер определенной строки и столбца в сгенерированном JavaScript, вы можете выполнить поиск на исходной карте, который возвращает исходное местоположение.
WebAssembly в настоящее время не поддерживает исходные карты, потому что нет спецификации, но со временем (вероятно, довольно скоро) она будет.
Когда вы устанавливаете точку останова в коде C ++, вы увидите код C ++ вместо WebAssembly. По крайней мере, это цель.
Многопоточность
JavaScript работает в одном потоке. Есть способы использовать цикл событий и асинхронное программирование, как это подробно описано в нашей статье по этой теме.
В JavaScript также используются веб-воркеры, но у них есть очень специфический вариант использования - в основном, любые интенсивные вычисления ЦП, которые блокируют основной поток пользовательского интерфейса, могут выиграть, если будут выгружены на веб-воркера. Однако веб-воркеры не имеют доступа к DOM.
WebAssembly в настоящее время не поддерживает многопоточность. Однако это, вероятно, следующее. Wasm собирается приблизиться к собственным потокам (например, потокам в стиле C ++). Наличие «настоящих» потоков откроет в браузере много новых возможностей. И, конечно же, это откроет новые возможности для злоупотреблений.
Портативность
В настоящее время JavaScript может работать практически где угодно, от браузера до серверной части и даже во встроенных системах.
WebAssembly разработан, чтобы быть безопасным и портативным. Прямо как JavaScript. Он будет работать в любой среде, поддерживающей wasm (например, в каждом браузере).
WebAssembly преследует ту же цель переносимости, которую пыталась достичь Java в первые дни с помощью апплетов.
Где лучше использовать WebAssembly вместо JavaScript?
В первых версиях WebAssembly основное внимание уделялось тяжелым вычислениям, связанным с процессором (например, математике). Самое распространенное использование, которое приходит на ум, - это игры - там есть масса манипуляций с пикселями. Вы можете написать свое приложение на C ++ / Rust, используя привязки OpenGL, к которым вы привыкли, и скомпилировать его в wasm. И он будет работать в браузере.
Взгляните на это (запустите в Firefox) - http://s3.amazonaws.com/mozilla-games/tmp/2017-02-21-SunTemple/SunTemple.html. Это работает на движке Unreal Engine.
Другой случай, когда имеет смысл использовать WebAssembly (с точки зрения производительности), - это реализация некоторой библиотеки, которая выполняет очень интенсивную работу с ЦП. Например, некоторые манипуляции с изображениями.
Как упоминалось ранее, wasm может значительно снизить расход заряда батареи на мобильных устройствах (в зависимости от движка), поскольку большинство этапов обработки выполняются заранее во время компиляции.
В будущем вы сможете использовать двоичные файлы WASM, даже если на самом деле вы не пишете код, который компилируется в него. В NPM можно найти проекты, которые начинают использовать этот подход.
Для манипуляций с DOM и интенсивного использования API платформы определенно имеет смысл остаться с JavaScript, поскольку он не добавляет дополнительных накладных расходов и имеет встроенные API.
В SessionStack мы постоянно расширяем границы производительности JavaScript, чтобы писать высокооптимизированный и эффективный код. Наше решение должно обеспечивать невероятно высокую производительность, поскольку мы не можем себе позволить снижать производительность приложений наших клиентов. Как только вы интегрируете SessionStack в свое производственное веб-приложение или веб-сайт, он начинает записывать все: все изменения DOM, взаимодействия с пользователем, исключения JavaScript, трассировки стека, неудачные сетевые запросы и данные отладки. И все это происходит в вашей производственной среде, не влияя ни на UX, ни на производительность вашего продукта. Нам нужно сильно оптимизировать наш код и сделать его максимально асинхронным.
И не только в библиотеке! Когда вы воспроизводите сеанс пользователя в SessionStack, мы должны отобразить все, что произошло в браузере вашего пользователя в момент возникновения проблемы, и мы должны восстановить все состояние, позволяя вам перемещаться вперед и назад по временной шкале сеанса. Чтобы сделать это возможным, мы активно используем возможности асинхронизации, которые предоставляет JavaScript из-за отсутствия лучшей альтернативы.
С помощью WebAssembly мы сможем перенести некоторые из наиболее сложных операций обработки и рендеринга на язык, который лучше подходит для этой работы, а сбор данных и манипуляции с DOM оставим JavaScript.
Если вы хотите попробовать SessionStack, вы можете начать работу бесплатно. Есть бесплатный план, который предусматривает 1000 сеансов в месяц.

Ресурсы: