Оба авторе: Лин Кларк — разработчик в группе Mozilla Developer Relations. Занимается JavaScript, WebAssembly, Rust и Servo, а также рисует комиксы о коде.

Люди называют WebAssembly фактором, меняющим правила игры, потому что эта технология ускоряет выполнение кода в вебе. Некоторые из ускорений уже реализованы, а другие появятся позже.

Одна из техник — потоковая компиляция, когда браузер компилирует код во время его загрузки. До настоящего времени эта технология рассматривалась лишь как потенциальный вариант ускорения. Но с выпуском Firefox 58 она станет реальностью.

Firefox 58 также включает в себя двухуровневый компилятор. Новый базовый компилятор компилирует код в 10–15 раз быстрее, чем оптимизирующий компилятор.

Вместе эти два изменения означают, что мы компилируем код быстрее, чем он поступает из сети.



На десктопе мы компилируем 30-60 МБ кода WebAssembly в секунду. Это быстрее, чем сеть доставляет пакеты.

Если у вас Firefox Nightly или Beta, то можете опробовать технологию на собственном устройстве. Даже на средненьком мобильном устройстве компиляция осуществляется на 8 МБ/с — это быстрее, чем скорость скачивания в большинстве мобильных сетей.

Другими словами, выполнение кода начинается сразу по окончании скачивания.

Почему это важно?


Адвокаты веб-производительности критически относятся к большому количеству JavaScript на сайтах, потому что это сильно замедляет загрузку веб-страниц.

Одна из главных причин такого замедления — время разбора и компиляции. Как заметил Стив Саудерс, раньше бутылочным горлышком веб-производительности была cеть, а теперь — CPU, а именно основной поток выполнения.



Так что мы хотим вынести как можно больше работы из основного потока. Мы также хотим как можно раньше обеспечить интерактивность страницы, поэтому используем всё время CPU. А лучше вообще уменьшить нагрузку на CPU.

С помощью JavaScript достижимы некоторые из этих целей. Можно разбирать файлы вне основного потока после их получения. Но их по-прежнему приходится разбирать, а это немалая работа. И нужно дождаться окончания разбора перед началом компиляции. А для компиляции вы возвращаетесь в основной поток, потому что обычно происходит ленивая компиляция JS на лету.



При использовании WebAssembly с самого начала меньше работы. Декодирование WebAssembly гораздо проще и быстрее, чем разбор JavaScript. И эти декодирование и компиляцию можно разбивать по нескольким потокам.

Это означает, что несколько потоков будут осуществлять базовую компиляцию, заметно ускоряя её. По окончании процесса предварительно скомпилированный код начинает исполняться в основном потоке. Не нужно делать остановку на ожидание компиляции, как в случае JS.



Хотя предварительно скомпилированный код запускается в основном потоке, другие потоки в этом время работают над оптимизированной версией. Когда готова оптимизированная версия, она заменяет предварительную — и код выполняется ещё быстрее.

Это делает загрузку WebAssembly более похожей на декодирование изображения, чем на загрузку JavaScript. Подумайте об этом… адвокаты производительности в штыки встречают скрипты более 150 КБ, но изображения такого размера не вызывает нареканий.



Это потому что загрузка изображений осуществляется гораздо быстрее, как объяснял Эдди Османи в статье «Цена JavaScript». И декодирование изображения не блокирует основной поток, как объяснял Алекс Расселл в статье «Вы можете себе это позволить? Бюджет веб-производительности в реальном мире».

Это не означает, что файлы WebAssembly будут такими же большими, как изображения. Хотя первые версии инструментов WebAssembly действительно создают большие файлы, но это потому что туда приходится включать значительную часть среды выполнения. Сейчас идёт активная работа, чтобы уменьшить их размер. Например, в Emscripten действует «инициатива по ужиманию». В Rust вы и сейчас можете получить довольно маленькие файлы, применив цель wasm32-unknown-unknown. И есть инструменты вроде wasm-gc и wasm-snip для ещё большей оптимизации.

Значит, файлы WebAssembly будут загружаться гораздо быстрее, чем эквивалентный JavaScript.

Это очень важно. Как заметил Иегуда Кац, это фактор, действительно меняющий правила игры.

Так давайте посмотрим, как работает новый компилятор.

Потоковая компиляция: раннее начало компиляции


Чем раньше вы начинаете компиляцию кода, тем раньше её закончите. Это то, что делает потоковая компиляция… начиная компиляцию файла .wasm как можно раньше.

Когда вы скачиваете файл, он не приходит одним куском. Вместо этого он поступает серией пакетов.

Раньше нужно было скачать все пакеты файла .wasm, затем сетевой слой браузера помещал его в ArrayBuffer.



Потом этот ArrayBuffer передавался Web VM (aka движок JS). В этот момент компилятор WebAssembly начал бы компиляцию.



Но нет никакой серьёзной причины оставлять компилятор в ожидании. Технически возможно компилировать WebAssembly строчка за строчкой. Значит, процесс можно начать после поступления первого же фрагмента.

Именно так поступает наш компилятор, который использует преимущества потоковых программных интерфейсов WebAssembly.



Если вы передадите объект Response в WebAssembly.instantiateStreaming, новые фрагменты кода будут поступать в движок WebAssembly сразу после скачивания. Затем компилятор может начать работу над первым фрагментом, пока следующий ещё скачивается.



Помимо одновременной загрузки и компиляции кода, есть ещё одно преимущество.

Кодовый раздел модуля .wasm идёт в самом начале, перед остальными данными (которые размещаются в объекте памяти модуля). Так что в случае потоковой компиляции код компилируется в то время, как данные модуля ещё не скачались до конца. Если вашему модулю требуется много данных, то объект памяти может быть размером в несколько мегабайт, и потоковая компиляция даст существенное увеличение производительности.



С потоковой компиляцией процесс компиляции начинается раньше. Но мы можем также сделать его быстрее.

Базовый компилятор 1-го уровня: ускорение компиляции


Если хотите быстрой работы кода, то его нужно оптимизировать. Но выполнение этих оптимизаций во время компиляции занимает время, что замедляет саму компиляцию. Так что это определённый компромисс.

Но если использовать два компилятора, то можно получить преимущества и быстрой компиляции, и оптимизированного кода. Первый быстро компилирует без особых оптимизаций, а второй работает более медленно, но выдаёт более оптимизированный код.

Это называется многоуровневым компилятором. Когда изначально поступает код, он компилируется компилятором Уровня 1 (или базовым компилятором). После того, как скомпилированный базовым компилятором код пошёл на запуск, компилятор Уровня 2 снова обрабатывает код и в фоновом режиме готовит более оптимизированную версию.

По завершении процесса происходит горячая замена базовой версии кода на оптимизированную версию. Это ускоряет выполнение кода.



В движках JavaScript давно используются многоуровневые компиляторы. Однако движки JS используют компилятор Уровня 2 (то есть оптимизирующий) только для «горячего» кода… который часто вызывается на исполнение.

В отличие от них, в WebAssembly компилятор Уровня 2 с охотой выполнит полную перекомпиляцию, оптимизировав весь код модуля. В будущем мы можем добавить больше опций для управления, насколько жадной или ленивой должна быть оптимизация.

Базовый компилятор экономит много времени на загрузке. Он работает в 10–15 раз быстрее, чем оптимизирующий компилятор. И скомпилированный код в нашем случае работает всего в два раза медленнее.

Это означает, что ваш код будет работать достаточно быстро даже в первые моменты, когда работает только базовая неоптимизированная версия.

Параллелизация: ещё большее ускорение


В статье по Firefox Quantum я объясняла варианты грубо- и тонконастроенной параллелизации. Для компиляции WebAssembly мы используем оба типа.

Выше я упоминала, что оптимизирующий компилятор работает в фоновом режиме, освобождая основной поток для исполнения кода. Базовая скомпилированная версия может работать в то время как оптимизирующий компилятор выполняет свою рекомпиляцию.

Но на большинстве компьютеров в таком случае всё равно бoльшая часть ядер останутся незагруженными. Чтобы оптимально использовать все ядра, оба компилятора используют тонконастроенную параллелизацию для разделения работы.

Единицей параллелизации является функция. Каждая функция может быть скомпилирована независимо, на отдельном ядре. На самом деле это настолько тонкая настройка, что в реальности нам приходится группировать эти функции в бoльшие группы функций. Эти группы рассылаются по разным ядрам.

…и затем пропуск всей этой работы за счёт полного кэширования (в будущем)


В настоящее время декодирование и компиляция выполняются повторно каждый раз, когда вы перезагружаете страницу. Но если у вас тот же файл .wasm, то он должен скомпилироваться в тот же машинный код.

Значит почти всегда эту работу можно пропустить. Именно этим мы займёмся в будущем. Декодирование и компиляция будут выполняться при первой загрузке страницы, а полученный в результате машинный код будет сохранён в кэше HTTP. Затем при запросе этого URL будет выдаваться сразу прекомпилированный машинный код.

Так время загрузки вообще исчезнет при последующих загрузках страницы.



Фундамент для этой функции уже заложен. В версии Firefox 58 мы таким образом кэшируем байткод JavaScript. Нужно только расширить эту функцию для поддержки файлов .wasm.

Комментарии (42)


  1. deril
    22.01.2018 00:26

    Значит ли это, что в скором времени будут разработчики компилировать весь JavaScript в wasm для улучшения производительности? И канут в реку uglifier и подобные?


    1. lekzd
      22.01.2018 10:06

      нет, не значит, wasm быстрый засчет более лёгкой среды исполнения. JS требует слишком много всего для исполнения, что бы его исполнить


  1. Gumanoid
    22.01.2018 00:55

    Интересно как происходит hot-swapping одного работающего кода на другой.


    1. Deosis
      22.01.2018 07:24

      Самый простой вариант: за счет трамплина.
      Первой инструкцией неоптимизированной функции будет переход на следующую инструкцию.
      Для замены на оптимизированную версию достаточно поменять адрес перехода.


      1. Gumanoid
        22.01.2018 18:50

        Но неоптимизированный код за время своей работы изменит регистры и память, а оптимизированный может по-другому распределить регистры и данные в памяти. Видимо нужны какие-то точки, в которые возможно переключение, и ограничения оптимизаций.


        1. Deosis
          23.01.2018 08:18

          Фактически меняется указатель на функцию.
          Если в другом потоке выполняется старая версия, то она продолжит выполняться, нормально завершится и вернет управление тому, кто её вызвал.
          Оптимизированная версия хранится по другому адресу.


  1. radist2s
    22.01.2018 04:30

    А в Chrome есть что-то подобное? И та крутая технология рендеринга, которую добавили в FF недавно — в Chrome что-то предвидится из того же ряда?


    1. domix32
      22.01.2018 11:43

      Пока про инициативу RIIR'нуть Chromium не приходилось слышать. Там все больше на отладке сконцентрировались если не ошибаюсь


  1. ElectroGuard
    22.01.2018 09:43

    Было бы хорошо, что бы со временем была представлена хорошая альтернатива JS. JS и есть главный bottle neck, только об этом не говорят — боятся.


    1. radist2s
      22.01.2018 10:17

      Да в общем-то альтернатива уже есть — AssemblyScript(более строго типизированный TypeScript), который компилируется в WASM.
      Да, совсем с полпинка перекомпилить код действующего TS-проекта в WASM-код не выйдет, но в перспективе это проще, чем, например, переписать все на Swift | C++ | Python | etc.


      1. 3axap4eHko
        22.01.2018 18:06
        +1

        Ну что Вы говорите такое?


        Во-первых, язык сам по себе не может быть bottle neck, т.к. это всего лишь синтаксис и в таком случае bottle neck — это разработчик который не знает языка.


        Во-вторых WASM пока (возможно никогда) не способен заменить JS т.к. он не имеет доступа к API браузера да и вообще ни к чему. Что бы WASM что-то делал кроме сложения чисел в него пробрасывают контекст (объект с набором функций написанных на JS которые взаимодействуют с API брвузера) собственно что и делает unity. Так что WASM — это не альтернатива, а дополнение к JS.


        Ну и в-третьих про "все переписать заново" уже не мало статей написано и граблей истоптано. Не стоит оно того. Лучше приложить усилия для создания чего-то лучшего.


        1. radist2s
          23.01.2018 09:37

          Вы так наехали на меня, будто бы я сказал, что от JS можно будет отказаться. Я имел ввиду, что стоит обратить внимание на TypeScript. Помимо очевидных преимуществ языка есть еще одно — возможность какую-то часть кода скомпилировать в WASM.


    1. Revertis
      22.01.2018 11:35

      Насколько я знаю, главный bottle neck это скорость доступа к DOM.


      1. domix32
        22.01.2018 13:41

        Теперь можно изобретать собственный DOM и рисовать прямиком на canvas. То есть делать это с меньшей болью и большей производительностью, чем раньше.


        1. Revertis
          22.01.2018 14:18

          Ага, мы этим занимались в 2006-2009-х годах на J2ME :)


          1. domix32
            23.01.2018 02:20

            Знаю попервой клоны MS Office тоже рисовали таблицы через канвас. J2ME это же Java-апплеты дл браузера или вы на бэкенде рисовали, а на фронт изменения картинки слали?


            1. asm0dey
              23.01.2018 07:59

              J2ME — джава под мобильные телефоны


              1. domix32
                23.01.2018 23:44

                Браузеры под нокии?


        1. justboris
          22.01.2018 15:39

          Уверены, что сможете сделать это быстрее и лучше, при этом соблюдая все те же требования? То есть: возможность копирования текстовых блоков, относительные размеры, чтобы на больших и маленьких экранах хорошо смотрелось и остальные возможности CSS?


          1. guai
            22.01.2018 18:31

            В том-то и дело, что многим приложениям «все те же требования» не нужны, но более легковесной среды стандарты не предусматривают, всё прибито гвоздями.


            1. justboris
              22.01.2018 19:00

              Тем, кому эти требования действительно не нужны (браузерные игры, например), уже давно переползли на Canvas.


              А как вы будете реализовывать tab-навигацию по полям ввода, нарисованным на сanvas, да при этом чтобы не медленнее, чем на JS, я не представляю.


              1. Falstaff
                22.01.2018 19:19

                Я, конечно, только теоретически себе это представляю — но есть мысль, что можно скомпилировать целый графический фреймворк (Qt/wxWidgets/nuklear/...) в WebAssembly, снабдив его бэкендом для вывода на canvas. Получатся обычные приложения. Не уверен по поводу скорости навигации по полям, но подозреваю что layout и отрисовка во многих фреймворках проще и шустрее чем махина DOM с CSS.


                1. boblenin
                  22.01.2018 19:25

                  А ведь можно построить что-то вообще вроде X server.


                1. justboris
                  22.01.2018 19:25

                  но подозреваю что layout и отрисовка во многих фреймворках проще и шустрее чем махина DOM с CSS.

                  Интересно, чем вызвано такое подозрение? Всё, что не содержит ни грамма JS — автоматически становится быстрее?


                  1. Falstaff
                    22.01.2018 19:42

                    На том что оно, в отличие от HTML, никогда не разрабатывалось для того чтобы форматировать текст, быть может? Должна быть разница между деревом DOM с CSS, где какая-нибудь неосторожная вставка элемента в таблицу будет постоянно вызывать reflow документа, и фреймворком, который консервативно обновит только один виджет. (Я просто напомню, что речь вовсе не о JS, а об отрисовке приложения в canvas или родными средствами браузера. Вы вполне можете использовать emscripten чтобы скомпилировать этот фреймворк в JS, кто вам мешает.)


                    1. justboris
                      22.01.2018 21:31

                      Итак:


                      1. Пишем на canvas, на котором нельзя сказать "передвиньте линию X на N пикселей вниз", только полный repaint
                      2. Фреймворк, быстрый за счет того, что умеет инкрементально обновлять картинку.

                      Не видите в этих друх пунктах противоречия?


                      1. Falstaff
                        22.01.2018 23:10

                        Не вижу. Просто к сведению, рендерер браузера тоже не может обойтись без формирования кадра перед тем как он (обычно аппаратно) будет брошен на экран. Если фреймворк использует WebGL, то, грубо говоря, он использует те же самые примитивы, что и сам браузер, и доступ к оборудованию.


                        Реальные затраты, о которых я говорю, это разница между фреймворком дать виджету команду обновить свой вид — например, кнопке себя перерисовать, когда фреймворк даже не будет заморачиваться layout и обновлением, скажем, дисплей-листов других виджетов — и reflow (не repaint) в браузере, когда вы неосторожно изменяете шрифт на кнопке и в ответ рендерер перелопачивает DOM, заново определяет координаты и границы для всего, прозрачность и прочая, и прочая.


                        Да, вы можете приложить усилия и попытаться минимизировать количество чихов, на которые ваша страница делает reflow. Но я не уверен, что вы, верстая страницу, серьёзно на этом сосредотачиваетесь. Вы постоянно держите в голове, что событие hover (проводка мыши) может вызвать reflow? А простой ввод данных в текстовом поле? А просто запрос ширины элемента? И ещё много-много всего, о чём пишут статьи и руководства по чёрной магии для оптимизации рендеринга сайтов. Не запрашивайте ширину. Остерегайтесь добавлять/удалять элементы DOM из JS. Не ходите к броду на полнолуние… Но если вы всё это держите в голове, и это не вызывает дискомфорта, то я рад.


                        1. justboris
                          23.01.2018 11:09

                          И ещё много-много всего, о чём пишут статьи и руководства по чёрной магии для оптимизации рендеринга сайтов.

                          Не надо утрировать, никакой черной магии здесь нет. Есть очевидное правило: не нужно изменять геометрию существующих блоков, чтобы не вызвать пересчета блоков, их окружающих.


                          Это правило следует из банальной геометрии и логики, которая работает как для декстопных layout-фреймворков, так и для браузерных. Какого-то особого преимущества, позволяющего десктопным решениям делать это быстрее, я не вижу.


                          1. Falstaff
                            23.01.2018 12:10
                            -1

                            Как это правило соотносится с :hover? Как соотносится со вводом текста в поле? Как соотносится с использованием анимаций из CSS3, где каждый кадр вызывает reflow? Как соотносится с чтением (sic!) свойств ширины/высоты элементов? Всё это не изменяет / не обязано изменять геометрию, если что.


                            Преимущество десктопных фреймворков в том, что они проектировались для разработки приложений, а не эволюционировали из языка разметки текста. Вам ниже domix32 правильно написал, DOM/CSS — это бутылочное горлышко веба. Для одних задач он подходит хорошо, для других, таких как приложения — хуже.


                            1. justboris
                              23.01.2018 20:18

                              Как это правило соотносится с :hover? Как соотносится со вводом текста в поле Как соотносится с использованием анимаций из CSS3, где каждый кадр вызывает reflow?

                              До тех пор, пока эти события не вызывают изменения размеров блоков, все происходит быстро, анимации и интерфейс в целом остается плавным.


                              Как соотносится с чтением (sic!) свойств ширины/высоты элементов?

                              Опять же, значения размеров можно читать сколько угодно, если они не меняются по 10 раз в секунду.


                              Преимущество десктопных фреймворков в том, что они проектировались для разработки приложений

                              Декстопные декстопы и прилагательные приложения. А какие-то конкретные ноу-хау есть? Инкрементальная перерисовка не в счет, в это и браузеры умеют. Reflow, которым вы тут пугаете, не так страшен, как вам кажется.


                              1. Falstaff
                                23.01.2018 22:48

                                Извините, но если выбирать между вашими совершенно неконкретными заверениями, что "в целом остаётся плавным" и "не так страшен как вам кажется" и массой статейдаже не могу сказать сколько их вокруг) по борьбе с лишними reflow и обилием нюансов, и конкретными заверениями Мозиллы и Гугла о том что reflow таки страшен (а гугл вдобавок ещё и противоречит вам, говоря что он обычно проводится для всего документа а не инкрементально), то я скорее прислушаюсь к Гуглу и Мозилле.


                                Декстопные декстопы и прилагательные приложения.

                                Вы бы так свою область знали как вы ёрничать умеете. Раз такие пироги, то и спорить больше не о чем — верьте чему хотите, я пас.


                                1. justboris
                                  23.01.2018 23:21

                                  Хорошая подборка источников, там рассказывается как бороться с некоторой проблемой. Но перед тем как лечить что-то, нужно точно поставить диагноз.


                                  Возьмем, например, Хабрахабр, здесь справа есть кнопка обновления комментариев. Если на нее нажать, то произойдет перерисовка страницы, но ничего страшного не случится, система не затормозит, а вентиляторы не взвоют.


                                  Нужно сначала убедиться в наличии проблемы, а потом уже можно искать материалы с руководством по исправлению.


                      1. domix32
                        23.01.2018 02:40

                        Если использовать последню версию GL контекста, то если не ошибаюсь согласно поддерживаемому стандату OpenGL обновление линии будет происходить локально.
                        Интересно было бы посмотреть разницу между JS и WASM версиями quake в этом плане.


              1. domix32
                23.01.2018 02:29

                Ради формочки и табов можно поверх канваса ещё ноду показать. ЕМНИП ещё вроде можно повесить preventDefault() на обработчик и табать внутри канваса как заблагорассудится.
                А вообще изначально шла речь о том что стандартный DOM является bottleneck и соственная реализация через какие-нибудь Scene Graph будут на порядок быстрее. Тем более что часть из обращений можно рассчитать на этапе компиляции.


                1. justboris
                  23.01.2018 11:14

                  реализация через какие-нибудь Scene Graph будут на порядок быстрее.

                  Если бы это действительно было так, то наверное крупные компании типа Facebook или Google уже бы выпустили свои подобные решения?


                  Единственное, что я видел в этом направлении, это PureQML, который рендерит QT-виджеты прямо в DOM.


                  Может быть, DOM не такой уж и медленный, как его принято в этом треде называть?


                  1. guai
                    23.01.2018 12:06

                    DOM удовлетворительно быстр на десктопе, а там, где хорошо бы что-то побыстрее, все в последнее время этим как раз и занимаются — пилят нативные приложения к своим сервисам под мобильные платформы.


                  1. Falstaff
                    23.01.2018 12:33

                    Выпускали и выпускают. Настольные приложения называются. guai упомянул мобильные, но и без ограничений мобильных платформ — если у компании есть приложение, и они хотят его шустрым, то они выпускают обычное настольное. (Да, у веба есть преимущество, это платформа, которая есть везде и которая не требует средств распространения/инсталляции — поэтому некоторые жрут кактус и героически сражаются с DOM, создавая офисные пакеты в вебе, но если вы думаете, что они получаются быстрее настольных...)


                  1. domix32
                    23.01.2018 23:43

                    Тут есть нюансы с поддерживаимость технологии разными системами/браузерами. Асинхронная подгрузка ресурсов туда же. И опять же — работа с собственым вариантом DOM может и быстрее, но есть ряд других проблем. Те же 60 FPS на канвасе сложно добиться при большом количестве графических элементов.


                    1. justboris
                      23.01.2018 23:47
                      +1

                      И все же: зачем свой велосипед, если нативная платформа нормально работает?


                      Давайте серьезно: вот эта самая дискуссия происходит на платформе с использованием обычного DOM, HTML, CSS и всё работает и не тормозит!


                      1. domix32
                        24.01.2018 00:58

                        Запилить собственный DRM, например. Или вовсе игры, в том числе и 3D. Куча нод на экране нехило просаживает память из-за кучи свойств и если очень захочется редактировать гигантские таблицы а ля excel, да ешё и с хитрыми формулами на несколько листов, есть вероятность уронить вкладку. Можно вынести нагрузку на серверную сторону с меньшими потерями памяти и возвращать готовую картинку на фронт, например. Бегать с `querySelector` по DOM и на основе `value` вычислять значения вероятно более затратно нежели скажем в типизированном массиве по трем индексам проскочить.
                        Хотя и на чистом css тоже не так давно шутер сделали. Правда не каждая машина осиливает отрисовку.


                        1. justboris
                          24.01.2018 01:04

                          Или вовсе игры, в том числе и 3D.

                          … и мы возвращаемся к моему изначальному комменту. Да, есть варианты, где рисование на canvas дает преимущество. Там уже есть готовые движки, они конечно же выиграют от WebAssembly.


                          Но мнение, что "WebAssembly придет, все напишут свой DOM с блекджеком, а об обычном DOM все забудут", я не разделяю.


                          1. domix32
                            24.01.2018 01:39

                            Да никто не утверждал такого. Утверждалось что общение с DOM является бутылочным горлышком в производительности страницы и что собственная реализация + canvas могут быть производительнее. Естественно, что любой из имеющихся вариантов нужно применять к месту.