[Перевод] Что это за зверь — WebAssembly?
По мнению многих специалистов 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
— значение 90x41
— константаi32
0xA0
— значение 1600x6A
— сложение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. Так что, если вы еще не занялись освоением этой новой технологии, то рекомендую надолго не откладывать, потому что вскоре она наверняка будет использоваться разработчиками повсеместно.