Существует два типа языков программирования: языки программирования со сборкой мусора и языки программирования, требующие ручного управления памятью. Примерами первых, среди многих других, являются Kotlin, PHP или Java. Примерами последних являются C, C++ или Rust. Как правило, языки программирования более высокого уровня, скорее всего, будут иметь сбор мусора в качестве стандартной функции. В этом сообщении блога основное внимание уделяется таким языкам программирования со сборкой мусора и тому, как их можно скомпилировать в WebAssembly (Wasm). Но что такое сбор мусора (часто называемый GC)?
Поддержка браузера
Сбор мусора
Проще говоря, идея сборки мусора — это попытка освободить память, которая была выделена программой, но на которую больше не ссылаются. Такая память называется мусором. Существует множество стратегий реализации сборки мусора. Одним из них является подсчет ссылок , целью которого является подсчет количества ссылок на объекты в памяти. Когда ссылок на объект больше нет, его можно пометить как неиспользуемый и, таким образом, готовый к сборке мусора. Сборщик мусора 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 представляет собой новую архитектуру. Если виртуальная машина имеет код, специфичный для архитектуры, например JIT-компиляция или компиляция с опережением времени (AOT), то разработчик также реализует серверную часть для JIT/AOT для новой архитектуры. Этот подход имеет большой смысл, поскольку зачастую основную часть кодовой базы можно просто перекомпилировать для каждой новой архитектуры.
Учитывая, насколько низкоуровневым является Wasm, вполне естественно попробовать тот же подход: перекомпилировать основной код виртуальной машины с его синтаксическим анализатором, поддержкой библиотек, сборкой мусора и оптимизатором в Wasm и при необходимости реализовать JIT или AOT-бэкэнд для Wasm. Это стало возможным со времен Wasm MVP и во многих случаях хорошо работает на практике. Фактически, PHP, скомпилированный в Wasm, является основой WordPress Playground . Узнайте больше о проекте в статье Создание возможностей WordPress в браузере с помощью WordPress Playground и WebAssembly .
Однако PHP Wasm работает в браузере в контексте основного языка JavaScript. В Chrome JavaScript и Wasm запускаются в V8 , движке JavaScript с открытым исходным кодом Google, который реализует ECMAScript, как указано в ECMA-262 . А в V8 уже есть сборщик мусора . Это означает, что разработчики, использующие, например, PHP, скомпилированный в Wasm, в конечном итоге отправляют реализацию сборщика мусора портированного языка (PHP) в браузер, в котором уже есть сборщик мусора, что настолько же расточительно, насколько и звучит. Здесь на помощь приходит WasmGC.
Другая проблема старого подхода, позволяющего модулям Wasm создавать собственный сборщик мусора поверх линейной памяти Wasm, заключается в том, что тогда нет взаимодействия между собственным сборщиком мусора Wasm и встроенным сборщиком мусора языка, скомпилированного в Wasm. что имеет тенденцию вызывать такие проблемы, как утечки памяти и неэффективные попытки сбора. Разрешение модулям Wasm повторно использовать существующий встроенный сборщик мусора позволяет избежать этих проблем.
Перенос языков программирования в новые среды выполнения с помощью WasmGC
WasmGC — предложение группы сообщества WebAssembly . Текущая реализация Wasm MVP способна работать только с числами, то есть целыми числами и числами с плавающей запятой, в линейной памяти, а с учетом предложения ссылочных типов Wasm может дополнительно удерживать внешние ссылки. WasmGC теперь добавляет типы кучи структур и массивов, что означает поддержку нелинейного распределения памяти. Каждый объект WasmGC имеет фиксированный тип и структуру, что позволяет виртуальным машинам легко генерировать эффективный код для доступа к своим полям без риска деоптимизации , которая свойственна динамическим языкам, таким как JavaScript. Таким образом, это предложение добавляет в WebAssembly эффективную поддержку управляемых языков высокого уровня через типы кучи структур и массивов, которые позволяют компиляторам языков, ориентированным на Wasm, интегрироваться со сборщиком мусора в виртуальной машине хоста. Проще говоря, это означает, что с помощью WasmGC портирование языка программирования на Wasm означает, что сборщик мусора языка программирования больше не должен быть частью порта, а вместо этого можно использовать существующий сборщик мусора.
Чтобы проверить реальное влияние этого улучшения, команда Chrome Wasm скомпилировала версии теста 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 , который позволяет разработчикам использовать пользовательский интерфейс, который они, возможно, уже создали для своих приложений Android Kotlin. Ознакомьтесь с ранним исследованием этого с помощью демо-версии средства просмотра изображений Kotlin/Wasm и изучите его исходный код , также любезно предоставленный командой Kotlin.
Дарт и Флаттер
Команды Dart и Flutter в Google также готовят поддержку WasmGC. Работа по компиляции Dart-to-Wasm почти завершена, и команда работает над поддержкой инструментов для доставки веб-приложений Flutter, скомпилированных в WebAssembly. О текущем состоянии работ можно прочитать в документации Flutter . Следующая демонстрация — Flutter WasmGC Preview .
Узнайте больше о WasmGC
Эта запись в блоге лишь коснулась поверхности и в основном предоставила общий обзор WasmGC. Чтобы узнать больше об этой функции, перейдите по этим ссылкам:
- Новый способ эффективного внедрения языков программирования со сборкой мусора в WebAssembly.
- Обзор WasmGC
- Самый ценный игрок WasmGC
- WasmGC после MVP
Благодарности
Эту статью рецензировали Маттиас Лидтке , Адам Кляйн , Джошуа Белл , Алон Закай , Якоб Куммеров , Клеменс Бакес , Эмануэль Зиглер и Рэйчел Эндрю .