Существует два типа языков программирования: языки программирования со сборкой мусора и языки программирования, требующие ручного управления памятью. Примерами первых, среди многих других, являются Kotlin, PHP или Java. Примерами вторых являются C, C++ или Rust. Как правило, языки программирования более высокого уровня, скорее всего, имеют сборку мусора в качестве стандартной функции. В этой записи блога основное внимание уделяется таким языкам программирования со сборкой мусора и тому, как их можно скомпилировать в WebAssembly (Wasm). Но что такое сборка мусора (часто называемая GC) для начала?
Browser Support
Сбор мусора
Упрощенно, идея сборки мусора заключается в попытке вернуть память, которая была выделена программой, но на которую больше нет ссылок. Такая память называется мусором. Существует множество стратегий реализации сборки мусора. Одна из них — подсчет ссылок, цель которой — подсчитать количество ссылок на объекты в памяти. Когда ссылок на объект больше нет, его можно пометить как больше не используемый и, таким образом, готовый к сборке мусора. Сборщик мусора PHP использует подсчет ссылок , а использование функции xdebug_debug_zval()
расширения Xdebug позволяет вам заглянуть под его капот. Рассмотрим следующую программу PHP.
<?php
$a= (string) rand();
$c = $b = $a;
$b = 42;
unset($c);
$a = null;
?>
Программа присваивает случайное число, преобразованное в строку, новой переменной с именем a
. Затем она создает две новые переменные, b
и c
, и присваивает им значение a
. После этого она переназначает b
число 42
, а затем отменяет установку c
. Наконец, она устанавливает значение a
в null
. Аннотируя каждый шаг программы с помощью xdebug_debug_zval()
, вы можете увидеть счетчик ссылок сборщика мусора в работе.
<?php
$a= (string) rand();
$c = $b = $a;
xdebug_debug_zval('a');
$b = 42;
xdebug_debug_zval('a');
unset($c);
xdebug_debug_zval('a');
$a = null;
xdebug_debug_zval('a');
?>
Приведенный выше пример выведет следующие логи, в которых вы увидите, как количество ссылок на значение переменной a
уменьшается после каждого шага, что имеет смысл, учитывая последовательность кода. (Ваше случайное число, конечно, будет другим.)
a:
(refcount=3, is_ref=0)string '419796578' (length=9)
a:
(refcount=2, is_ref=0)string '419796578' (length=9)
a:
(refcount=1, is_ref=0)string '419796578' (length=9)
a:
(refcount=0, is_ref=0)null
Существуют и другие проблемы со сборкой мусора, такие как обнаружение циклов , но для этой статьи достаточно иметь базовый уровень понимания подсчета ссылок.
Языки программирования реализованы на других языках программирования
Это может показаться зарождением, но языки программирования реализованы в других языках программирования. Например, среда выполнения PHP в основном реализована на языке C. Вы можете проверить исходный код PHP на GitHub . Код сборки мусора PHP в основном находится в файле zend_gc.c
. Большинство разработчиков устанавливают PHP через менеджер пакетов своей операционной системы. Но разработчики также могут собрать PHP из исходного кода . Например, в среде Linux шаги ./buildconf && ./configure && make
соберут PHP для среды выполнения Linux. Но это также означает, что среда выполнения PHP может быть скомпилирована для других сред выполнения, например, как вы уже догадались, Wasm.
Традиционные методы портирования языков в среду выполнения Wasm
Независимо от платформы, на которой работает PHP, скрипты PHP компилируются в тот же байт-код и запускаются Zend Engine . Zend Engine — это компилятор и среда выполнения для языка сценариев PHP. Он состоит из виртуальной машины Zend (VM), которая состоит из компилятора Zend и исполнителя Zend. Такие языки, как PHP, реализованные в других языках высокого уровня, таких как C, обычно имеют оптимизации, нацеленные на определенные архитектуры, такие как Intel или ARM, и требуют разного бэкэнда для каждой архитектуры. В этом контексте Wasm представляет новую архитектуру. Если у виртуальной машины есть код, специфичный для архитектуры, такой как компиляция just-in-time (JIT) или ahead-of-time (AOT), то разработчик также реализует бэкэнд для JIT/AOT для новой архитектуры. Такой подход имеет большой смысл, поскольку часто основную часть кодовой базы можно просто перекомпилировать для каждой новой архитектуры.
Учитывая, насколько низкоуровневый Wasm, естественно попробовать тот же подход там: перекомпилировать основной код VM с его парсером, поддержкой библиотек, сборщиком мусора и оптимизатором в Wasm и реализовать JIT или AOT бэкенд для Wasm, если необходимо. Это стало возможным с момента выхода Wasm MVP, и это хорошо работает на практике во многих случаях. Фактически, PHP, скомпилированный в Wasm, — это то, что поддерживает WordPress Playground . Узнайте больше о проекте в статье Build in-browser WordPress experiences with WordPress Playground and WebAssembly .
Однако PHP Wasm работает в браузере в контексте языка-хоста JavaScript. В Chrome JavaScript и Wasm работают в V8 , движке JavaScript с открытым исходным кодом от Google, который реализует ECMAScript, как указано в ECMA-262 . И в V8 уже есть сборщик мусора . Это означает, что разработчики, использующие, например, PHP, скомпилированный в Wasm, в конечном итоге отправляют реализацию сборщика мусора портированного языка (PHP) в браузер, в котором уже есть сборщик мусора, что так же расточительно, как и звучит. Вот где вступает в дело WasmGC.
Другая проблема старого подхода, когда модули Wasm строили свой собственный GC поверх линейной памяти Wasm, заключается в том, что тогда нет взаимодействия между собственным сборщиком мусора Wasm и сборщиком мусора, построенным поверх языка, скомпилированного в Wasm, что имеет тенденцию вызывать такие проблемы, как утечки памяти и неэффективные попытки сбора. Разрешение модулям Wasm повторно использовать существующий встроенный GC позволяет избежать этих проблем.
Перенос языков программирования в новые среды выполнения с помощью WasmGC
WasmGC — это предложение WebAssembly Community Group . Текущая реализация Wasm MVP способна работать только с числами, то есть целыми числами и числами с плавающей точкой, в линейной памяти, а с предложением о ссылочных типах Wasm может дополнительно удерживать внешние ссылки. WasmGC теперь добавляет типы кучи структур и массивов, что означает поддержку нелинейного выделения памяти. Каждый объект WasmGC имеет фиксированный тип и структуру, что позволяет виртуальным машинам легко генерировать эффективный код для доступа к своим полям без риска деоптимизации, который есть в динамических языках, таких как JavaScript. Таким образом, это предложение добавляет эффективную поддержку высокоуровневых управляемых языков в WebAssembly через типы кучи структур и массивов, которые позволяют языковым компиляторам, ориентированным на Wasm, интегрироваться со сборщиком мусора в хостовой виртуальной машине. Проще говоря, это означает, что с WasmGC портирование языка программирования на Wasm означает, что сборщик мусора языка программирования больше не должен быть частью порта, а вместо этого можно использовать существующий сборщик мусора.
Чтобы проверить реальное влияние этого улучшения, команда Wasm из Chrome скомпилировала версии бенчмарка Fannkuch (который выделяет структуры данных по мере работы) из C , Rust и Java . Двоичные файлы C и Rust могут быть размером от 6,1 К до 9,6 К в зависимости от различных флагов компилятора, в то время как версия Java намного меньше — всего 2,3 К ! C и Rust не включают сборщик мусора, но они по-прежнему объединяют malloc/free
для управления памятью, и причина, по которой Java здесь меньше, заключается в том, что ей вообще не нужно объединять какой-либо код управления памятью. Это всего лишь один конкретный пример, но он показывает, что двоичные файлы WasmGC могут быть очень маленькими, и это даже до какой-либо значительной работы по оптимизации размера.
Видим портированный на WasmGC язык программирования в действии
Котлин Васм
Одним из первых языков программирования, который был портирован на Wasm благодаря WasmGC, является Kotlin в форме Kotlin/Wasm . Демо с исходным кодом , предоставленным командой Kotlin, показано в следующем листинге.
import kotlinx.browser.document
import kotlinx.dom.appendText
import org.w3c.dom.HTMLDivElement
fun main() {
(document.getElementById("warning") as HTMLDivElement).style.display = "none"
document.body?.appendText("Hello, ${greet()}!")
}
fun greet() = "world"
Теперь вы можете задаться вопросом, в чем смысл, поскольку приведенный выше код Kotlin в основном состоит из API JavaScript OM, преобразованных в Kotlin . Он начинает приобретать больше смысла в сочетании с Compose Multiplatform , который позволяет разработчикам создавать UI, который они, возможно, уже создали для своих приложений Android Kotlin. Ознакомьтесь с ранним исследованием этого с демонстрацией средства просмотра изображений Kotlin/Wasm и изучите его исходный код , также любезно предоставленный командой Kotlin.
Дарт и Флаттер
Команды Dart и Flutter в Google также готовят поддержку WasmGC. Работа по компиляции Dart-to-Wasm почти завершена, и команда работает над инструментальной поддержкой для доставки веб-приложений Flutter, скомпилированных в WebAssembly. Вы можете прочитать о текущем состоянии работы в документации Flutter . Следующая демонстрация — это предварительный просмотр Flutter WasmGC .
Узнайте больше о WasmGC
Этот пост в блоге едва затронул поверхность и в основном предоставил общий обзор WasmGC. Чтобы узнать больше об этой функции, ознакомьтесь с этими ссылками:
- Новый способ эффективного внедрения языков программирования со сборкой мусора в WebAssembly
- Обзор WasmGC
- WasmGC MVP
- WasmGC пост-MVP
Благодарности
Эту статью рецензировали Маттиас Лидтке , Адам Кляйн , Джошуа Белл , Алон Закай , Якоб Куммеров , Клеменс Бэкес , Эмануэль Циглер и Рэйчел Эндрю .