Существует два типа языков программирования: языки программирования со сборкой мусора и языки программирования, требующие ручного управления памятью. Примерами первых, среди многих других, являются 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 случайное число, преобразованное в строку 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, естественно попробовать тот же подход и здесь: перекомпилировать основной код виртуальной машины с её парсером, поддержкой библиотек, сборщиком мусора и оптимизатором в 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 означает, что сборщик мусора этого языка программирования больше не обязательно должен быть частью порта, вместо этого можно использовать существующий сборщик мусора.
Чтобы проверить реальное влияние этого улучшения, команда Wasm от Chrome скомпилировала версии бенчмарка Fannkuch (который выделяет структуры данных по мере работы) из C , Rust и Java . Двоичные файлы C и Rust могут быть размером от 6,1 КБ до 9,6 КБ в зависимости от различных флагов компилятора, в то время как версия Java намного меньше — всего 2,3 КБ ! В C и Rust нет сборщика мусора, но они по-прежнему объединяют malloc/free
для управления памятью, а причина, по которой Java здесь меньше, заключается в том, что ей вообще не нужно объединять какой-либо код управления памятью. Это всего лишь один конкретный пример, но он показывает, что двоичные файлы WasmGC могут быть очень маленькими, и это даже до какой-либо значительной работы по оптимизации размера.
Видим в действии язык программирования, портированный на WasmGC
Kotlin Wasm
Одним из первых языков программирования, портированных на 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 в Wasm практически завершена, и команда работает над инструментами поддержки для доставки веб-приложений Flutter, скомпилированных в WebAssembly. О текущем состоянии работы можно узнать в документации Flutter . Следующая демонстрация — предварительная версия Flutter WasmGC .
Узнайте больше о WasmGC
Эта запись в блоге лишь поверхностно затронула тему и в основном содержит общий обзор WasmGC. Чтобы узнать больше об этой функции, перейдите по этим ссылкам:
- Новый способ эффективного внедрения языков программирования со сборкой мусора в WebAssembly
- Обзор WasmGC
- WasmGC MVP
- WasmGC пост-MVP
Благодарности
Эту статью рецензировали Маттиас Лидтке , Адам Кляйн , Джошуа Белл , Алон Закай , Якоб Куммеров , Клеменс Бэкес , Эмануэль Циглер и Рэйчел Эндрю .