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

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

Когда же наши потребности начали превосходить возможности датацентров, мы вошли в эпоху образов Docker и теперь развертываем эти образы поверх виртуальных машин, развертываемых поверх голого железа.

WebAssembly представляет очередной виток эволюции в системе развертывания. Мы прошли путь от кастомных образов жестких дисков через создание продуманных бандлов для голого железа до виртуальных машин и далее до образов Docker. Теперь же у нас есть еще более компактная, быстрая и при этом портативная единица развертывания – модуль WebAssembly.

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

Так что же это за зверь — WebAssembly?

WebAssembly – это ни web, ни assembly


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

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

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

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

Двоичные файлы WebAssembly (.wasm) также очень компактны и в зависимости от реализуемого проекта в полностью функциональном виде занимают килобайты, а не мега- или гигабайты.

Примитивность


WebAssembly примитивен, но в хорошем смысле. Мне всегда нравились языки, которые делают немногое: языки, чьи разработчики старательно уделили внимание исключению всего лишнего. Значительный потенциал Wasm – о чем я буду говорить в течение статьи – в первую очередь определяется его возможностями.

Единственный нюанс, на который зачастую обращают внимание разработчики (и которого побаиваются) – это присутствие в языке только численного типа данных. Функции могут принимать и возвращать значения в виде целых чисел либо чисел с плавающей запятой, причем допускаются только 32- и 64-битные значения.

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

Давайте посмотрим, как именно выглядит этот язык изнутри. Предположим, у нас есть часть файла .wasm, содержащая следующие байты:

0x41 0x09 0x41 0xA0 0x6A

Этот порядок может не соответствовать спецификации, но в качестве примера сгодится (двоичный формат имеет достаточно сложные требования к кодировке, которые на данном этапе нас только запутают):

  • 0x41 — константа i32
  • 0x09 — значение 9
  • 0x41 — константа i32
  • 0xA0 — значение 160
  • 0x6A – сложение i32

Первая инструкция – это i32.const (напомню, что Wasm поддерживает только числа i32, f32, i64 и f64). Она помещает в стек константу 9. Следующая инструкция помещает в стек значение 160. Третья инструкция – это i32.add, которая извлекает из стека два значения, складывает их и помещает сумму обратно в стек. Итак, когда виртуальная машина завершает предварительную обработку байткодов и параметров, в стек помещается значение 169.

Портативность


Я уже упомянул, что никакая инструкция Wasm не ограничивается ни операционной системой, ни архитектурой ЦПУ. Это означает, что при соответствии среды выполнения (например, браузера или кастомного инструмента встраивания (embedder)) спецификации, один и тот же файл Wasm можно интерпретировать на любой машине, независимо от ее ОС или архитектуры ЦПУ.

Это имеет огромное значение, так как в итоге все, что выражается через байткод Wasm, можно один раз скомпилировать и развертывать на разных целевых устройствах без изменений. Большинство из нас слышали мантру «Напиши раз, развертывай везде», которую популяризовали сторонники байткода Java, но Wasm не является просто разновидностью JVM или .NET CLR.

Во-первых, байткод Java по факту не портативен, и разные JVM могут выполнять его совершенно по-своему, исключая возможность переноса. Фреймворк .NET тоже заявляет о портативности, хотя и JVM, и .NET CLR, на мой взгляд, слишком перегружены инструкциями, чтобы уверенно обеспечивать такую возможность. При этом многие из их инструкций буквально нарушают необходимые для портативности условия. К примеру, и JVM, и .NET предоставляют доступ к операционной системе, что уже создает проблемы с переносом, а также вносит риски для безопасности, о чем я расскажу ниже.

Быстродействие


Задача среды выполнения проста – считывать коды операций, управлять стеком и памятью с линейной адресацией, а также выполнять диктуемую операционным кодом задачу. Именно эта простота позволяет невероятно быстро обрабатывать файл WebAssembly. Несмотря на доступность только JIT-компиляторов, преобразующих модуль Wasm в нативный код, многие среды выполнения (например, ваш браузер) могут очень быстро работать путем одной только интерпретации.

Потоковость


Еще одна особенность, положительно влияющая на быстродействие Wasm – это поддержка потокового выполнения, которое становится возможным благодаря особой реализации инструкций и грамотной организации кода в файлах .wasm.

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

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

Компактность


Wasm очень компактен. Даже язык, который обычно производит самые крупные бинарники WebAssembly (речь о Rust), все равно создает их на порядок меньшего размера, чем образы Docker и даже отдельные специфичные для ОС и ЦПУ исполняемые файлы, создаваемые такими языками, как Go и тот же Rust.

Существует ряд фреймворков и кастомных инструментов встраивания, которые за счет скорости и компактности Wasm способны поддерживать сотни и даже тысячи мелких модулей на одном хосте. Подобной вычислительной плотности нереально добиться при помощи доступных на сегодня языков и фреймворков, выполняя компиляцию для «традиционных» целевых платформ.

Безопасность


WebAssembly безопасен. Безопасность в данном случае определяется как возможностями языка, так и его спецификацией. Первая и главная особенность в том, что модуль Wasm реактивен. Он не может делать что-либо, пока не получит соответствующий запрос от среды выполнения. Во-вторых, модули Wasm не имеют доступа к памяти среды выполнения. Они используют собственную закрытую область памяти с линейной адресацией, которая в итоге сводится к длинному вектору байтов.

В WebAssembly нет встроенных инструкций для обращения к файловой системе, записи в сокеты, управления памятью хоста, доступа к сетевым службам или какого-либо взаимодействия с операционной системой. Даже при том, что стандарты вроде WASI позволяют ограниченный доступ к ОС, он оказывается защищен за счет использования разрешающих токенов, и среда выполнения по-прежнему может просто запретить доступ к вызовам функций на основе WASI.

Модули Wasm зачастую представляют собой чистые вычисления либо совсем без побочных эффектов, либо с такими, которые строго контролируются средой выполнения. К побочным эффектам, допускаемым со стороны среды браузера, относятся доступ к JavaScript API через промежуточные оболочки и возможность управлять DOM (также через промежуточный код, обычно генерируемый специальными инструментами).

Подытожим


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

Характеристики, которые всегда рассматривались как некий священный Грааль в вычислительной среде – малый размер, портативность, безопасность и быстродействие – все они обеспечиваются WebAssembly. Так что, если вы еще не занялись освоением этой новой технологии, то рекомендую надолго не откладывать, потому что вскоре она наверняка будет использоваться разработчиками повсеместно.

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


  1. n0isy
    23.08.2021 16:31

    Очень познавательно. Я не знал, что WASM это ФОРТ. Он строился на стеке и даже математические выражения записываются соответствующей нотацией: "2 2 + ." выведет на экран "4".


    1. static_cast
      23.08.2021 17:06
      +3

      Не каждая стеково-регистровая машина становится ФОРТом. Для этого нужна, как минимум, встроенная возможность определять новые произвольные слова. Для чего нужна уже абстракция более высокого уровня, чем простая трансляция ограниченного набора инструкций байткода. Разновидность обычного ассемблера, скорее.


      1. juray
        24.08.2021 23:23

        Такой, по сути ассемблер, был в программируемых калькуляторах — Б3-34, МК-54 и т.п., каковые стеково-регистровыми машинами и являлись, собственно (даже в режиме ручных вычислений).

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


  1. none7
    23.08.2021 18:18
    +6

    По ссылке доказывающей, что WA это не очередная JVM встретил утверждение, что WA исполняется почти также быстро как машинный код. Снижение производительности в 2 раза назвать незначительным довольно трудно. С такими источниками информации всю статью можно не глядя отправлять в корзину. Производительность .NET всего в 1,5 раза ниже чистого Си. Ничего удивительного в том, что облачные хостинги пытаются продать WA, ведь они торгуют временем работы на своём железе. Чем дольше работает код, тем лучше.
    Так же хотелось бы увидеть доказательство того как разные JVM исполняют один и тот же код по разному. Естественно, что речь не идёт про функции устройств, которые не входят в стандарт Java. Если Вы будете использовать в WA функции доступные только браузеру, то никакое WASI не спасёт вас от различающегося поведения кода. Не сможет консолька рисовать canvas, а браузер не сможет получить доступ к raw-сокетам.


    1. Zibx
      25.08.2021 13:49

      У облачных хостингов есть php и python, им незачем изобретать такой велосипед.


      1. none7
        25.08.2021 17:18

        Они бы и рады заставить всех использовать только python, но их ведь сразу пошлют куда подальше. А тут смотри как много надёжности и безопасности. И почти так же быстро как машинный код.


  1. gochaorg
    23.08.2021 18:58
    +5

    Не вводите людей в заблуждение,

    WASM это обычная виртуальная машина со стеклом и памятью, она не быстрее и не медленнее остальных (Jvm, net)

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

    Плюс ещё скажите лучше про ограничение в 4гб оперативны, и ещё не ясно как в ней решен вопрос синхронизации поток (cpu)

    Хотяв целом я рад ее появлению, но далеко не все языки включили поддержку

    Ну и про jvm - тоже заблуждение, jvm вполе из коробки безопасна и ограничить доступ к ОС делается на раз


    1. none7
      24.08.2021 08:39
      +1

      и не медленнее остальных
      Весьма спорное утверждение. Ценой внешней похожести на настоящий ассемблер стало затирание информации о типах. И если JVM откомпилировав метод может быть уверена, что при вызове метода, this != null && VALID_PTR(this) && VALID_PTR(this+sizeof(myclass)) и можно не проверять данные в процессе выполнения. То в случае же WASM всё это проверять необходимо. У всё тоже самое для каждого указателя, независимо от того, как долго функция будет с ним работать. Для аппаратного ускорения таких проверок, нужно писать полноценную виртуальную машину работающую в ядре ОС. А браузерописатели к этому совсем не готовы.


      1. Zibx
        25.08.2021 13:52

        Браузерописатели ко всему готовы, на V8 гугл изначально нанял чуваков с 20-30 лет опыта написания интерпретаторов и виртуальных машин. Многие разработчики JVM были сханчены.


      1. AnthonyMikh
        25.08.2021 15:16
        +1

        И если JVM откомпилировав метод может быть уверена, что при вызове метода, this != null && VALID_PTR(this) && VALID_PTR(this+sizeof(myclass)) и можно не проверять данные в процессе выполнения. То в случае же WASM всё это проверять необходимо

        А что мешает эти проверки провести на этапе компиляции в WASM?


        1. none7
          25.08.2021 18:30
          -1

          Мешает то, что предвычислить результат выполнения функций, которые будут вызывать ныне компилируемую, практически невозможно. Это задача того же порядка сложности, что и оптимизация стандартного хелло-ворда с printf до нескольких машинных инструкций и одного вызова ядра. Только вместо хелло-ворда у нас обычно огромное приложение, что повышает сложность в миллионы раз.


          1. freecoder_xx
            29.08.2021 15:50
            +1

            Я не понял ваш ответ. Допустим, у меня есть программа на Rust, и компилятор мне проверил, что нигде нет null. Я эту программу компилирую в WASM. Зачем мне там теперь нужны динамические проверки?


            1. none7
              29.08.2021 16:11

              А компилятор WASM в машинный код должен слепо доверять вашему компилятору Rust? А, чтобы компилятор WASM мог совершить те же проверки, ему во первых нужна подробная информация о типах, а во вторых уйма времени.


              1. freecoder_xx
                29.08.2021 17:11

                Зачем ему вообще что-то проверять, если вся программа работает в своей собственной линейной памяти и за ее пределы выйти не может?


                1. none7
                  29.08.2021 17:14

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


                  1. freecoder_xx
                    29.08.2021 17:53

                    Насколько я знаю, под каждый wasm-модуль выделяется 16 Гб виртуальной памяти, 8 Гб до начала памяти модуля, и 8 Гб на саму память (guard pages). Поэтому максимальный адрес, который может быть использован в WASM 32, не выходит за данный диапазон (он рассчитывается как 0xffffffff + 0xffffffff = 0x1fffffffe), и проверка условия не требуется.


                    1. none7
                      29.08.2021 18:16

                      Во первых, пруф? Раньше V8 резервировал только 1Гб для возможности расширения кучи без копирования памяти, не больше. Во вторых wasm64 на подходе. И в третьих, есть тип i64.


                      1. freecoder_xx
                        29.08.2021 18:59
                        +1

                        Ну вы можете оперировать числами типа i64, но указатель имеет тип i32 (в wasm 32).


    1. Druu
      25.08.2021 09:46

      Безопасность там не лучше других, эта виртуальная машина не может определять чистая функция или нет

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


    1. SadOcean
      25.08.2021 20:38

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

      Но согласен, в чем отличие от остальных VM, которые тоже нормально контролируют свою память и свое внешнее api - непонятно


  1. kovserg
    23.08.2021 18:58

    Сплошной маркетинговый маркетинг. Никогда общее решение не будет быстрее специализированного решения, особенно сейчас когда уже производительность на ядро уперлось в потолок и мы видим увеличение длинны векторных инструкций появления специализированных вычислителей под разные особенности вычислительных конвейеров (gpgpu, ai accelerator, datapath accelerator ...)

    Этот ваш WA это очередная тупиковая ветвь развития (по производительности быстрее java и собратьев не будет ни конда). WA нужен только что бы фортрановские коды не переписывать на javascript ибо не каждый осилит (очень дорого), а полезного много.
    Что код был быстрым приходится учитывать особенности железа (особенности векторизации, кэши, количество каналов памяти, длину конвейера спекулятивного исполнения и топологию процессоров). Как это всё учитывается в WA?

    Более того почти всегда используются уже готовые высоко оптимизированные библиотеки для типовых вычислений которые постановляют производители железа (intel, nvidia, amd ...) и самому не заморачиваться, а тут ничего нет только выход в javascript (который это может скормить часть вычислений в ускоритель через webgl и т.о. не факт)

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


    1. Alexufo
      26.08.2021 17:48
      +1

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

      Вы не хотите покритиковать всю архитектуру x86? Это не повод от нее отказываться.
      Лучше васм чем его отсутствие.

      Этот ваш WA это очередная тупиковая ветвь развития (по производительности быстрее java и собратьев не будет ни конда).

      эволюционная ветвь, уже 5 лет и пока все мысли о развитии. Не похоже на тупик в ближайшей перспективе.


  1. NBSoyu
    24.08.2021 04:13

    Поживем увидим конечно, но на данный момент как и говорится нам остается только ждать чуда описанного в посте, т.к пока что он остается "ЯП низкого уровня" но не менее полезным чем его конкуренты, он переносимый как на Web Так и на такие языки как C++, C#. Да и лет не особо много прошло с момента его появления и утверждать что скоро он станет повсеместно используемым это как тыкать пальцем в небо.


  1. abar
    24.08.2021 15:29
    +1

    Большинство из нас слышали мантру «Напиши раз, развертывай везде», которую популяризовали сторонники байткода Java, но Wasm не является просто разновидностью JVM или .NET CLR.

    Во-первых, байткод Java по факту не портативен, и разные JVM могут выполнять его совершенно по-своему, исключая возможность переноса. Фреймворк .NET тоже заявляет о портативности, хотя и JVM, и .NET CLR, на мой взгляд, слишком перегружены инструкциями, чтобы уверенно обеспечивать такую возможность. При этом многие из их инструкций буквально нарушают необходимые для портативности условия. К примеру, и JVM, и .NET предоставляют доступ к операционной системе, что уже создает проблемы с переносом, а также вносит риски для безопасности, о чем я расскажу ниже.

    Про то, что «JVM могут выполнять по разному» — это ложь, до тех пор, пока автор не предоставит доказательств обратного. Я по памяти могу вспомнить только пример, где Оракловская JVM корректно работает с виндоусовским керберосом, а опенсорсная — нет, но сомневаюсь что такое будет проблемой для большинства пользователей.

    Вообще, смешно — убили java applet'ы в браузере, вырезали JVM-ку, «потому что ненадёжно!», что бы в итоге к тому же самому и вернуться. Ах, да! Сейчас она не перегружена инструкциями и не даёт доступ к системе. Потом, внезапно, окажется что существующих команд недостаточно, начнут добавлять, выкатят масштабное дополнение для более прямого доступа к системным ресурсам, а в итоге, внезапно, окажется, что у нас очередной комбайн, изучать который молодые программисты не захотят и от которого надо будет избавляться. Задолбало…


    1. Alexufo
      26.08.2021 13:56
      +2

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

      У васма совсем другая архитектура. Он даже вызывать себя не умеет без js.


  1. yurec_bond
    24.08.2021 16:37
    +1

    WebAssembly был содан для того что бы покончить с монополией богомерзкого JavaScript в баузерах. И дать возможность писать высоко оптимизированный код на нормальных языках програмирования.


    1. AikoKirino
      24.08.2021 19:39
      +1

      И дать возможность писать высоко оптимизированный код на нормальных языках програмирования.

      При этом дергать DOM через js. Не думаю что об этом мы мечтали, но возможно в будущем что-то предложат взамен.


    1. Alexufo
      26.08.2021 13:52

      Почитайте mdn, не выдавайте свои мысли за описание технологии.


  1. LynXzp
    30.08.2021 01:43

    WebAssembly – это ни web, ни assembly
    При изучении WebAssembly (кратко именуемого Wasm)…
    Но, Wasm это же такой ассемблер. Т.е. WebAssembly это ни web, ни assembly, ни Wasm. Хотя конечно Wasm уже умер и можно переиспользовать имя, но создается ощущение
    pythonscript
    image