Es gibt zwei Arten von Programmiersprachen: Programmiersprachen mit automatischer Speicherbereinigung und Programmiersprachen, die eine manuelle Speicherverwaltung erfordern. Beispiele für die erstgenannte Kategorie sind unter anderem Kotlin, PHP oder Java. Beispiele für Letzteres sind C, C++ oder Rust. In der Regel ist die automatische Speicherbereinigung eine Standardfunktion von Programmiersprachen höherer Ebene. In diesem Blogbeitrag geht es um solche Programmiersprachen mit automatischer Speicherbereinigung und darum, wie sie in WebAssembly (Wasm) kompiliert werden können. Was ist aber überhaupt die Garbage Collection (oft als GC bezeichnet)?
Browser Support
Automatische Speicherbereinigung
Vereinfacht gesagt, geht es bei der Garbage Collection darum, Speicher zurückzugewinnen, der vom Programm zugewiesen wurde, aber nicht mehr referenziert wird. Dieser Speicher wird als „Müll“ bezeichnet. Es gibt viele Strategien für die Implementierung der Garbage Collection. Eine davon ist die Referenzzählung, bei der die Anzahl der Referenzen auf Objekte im Arbeitsspeicher gezählt wird. Wenn es keine Verweise mehr auf ein Objekt gibt, kann es als nicht mehr verwendet markiert und somit für die Garbage Collection vorbereitet werden. Die Speicherbereinigung von PHP verwendet die Referenzzählung. Mit der Funktion xdebug_debug_zval()
der Erweiterung Xdebug können Sie sich ansehen, wie sie funktioniert. Betrachten Sie das folgende PHP-Programm.
<?php
$a= (string) rand();
$c = $b = $a;
$b = 42;
unset($c);
$a = null;
?>
Das Programm weist einer neuen Variablen mit dem Namen a
eine zufällige Zahl zu, die in einen String umgewandelt wird. Anschließend werden zwei neue Variablen, b
und c
, erstellt und ihnen der Wert von a
zugewiesen. Anschließend wird b
der Zahl 42
zugewiesen und c
wird zurückgesetzt. Schließlich wird der Wert von a
auf null
festgelegt. Wenn Sie jeden Schritt des Programms mit xdebug_debug_zval()
kommentieren, können Sie sehen, wie der Referenzzähler des Garbage Collectors funktioniert.
<?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');
?>
Im obigen Beispiel werden die folgenden Logs ausgegeben. Sie sehen, wie die Anzahl der Verweise auf den Wert der Variablen a
nach jedem Schritt abnimmt, was angesichts der Codefolge sinnvoll ist. Ihre Zufallszahl wird natürlich anders sein.
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
Es gibt noch weitere Herausforderungen bei der Garbage Collection, z. B. das Erkennen von Zyklen. Für diesen Artikel reicht jedoch ein grundlegendes Verständnis der Referenzzählung aus.
Programmiersprachen werden in anderen Programmiersprachen implementiert
Es mag sich wie eine Endlosschleife anfühlen, aber Programmiersprachen werden in anderen Programmiersprachen implementiert. Die PHP-Laufzeit ist beispielsweise hauptsächlich in C implementiert. Sie können sich den PHP-Quellcode auf GitHub ansehen. Der Garbage Collection-Code von PHP befindet sich hauptsächlich in der Datei zend_gc.c
. Die meisten Entwickler installieren PHP über den Paketmanager ihres Betriebssystems. Entwickler können PHP aber auch aus dem Quellcode erstellen. In einer Linux-Umgebung würden mit den Schritten ./buildconf && ./configure && make
beispielsweise PHP für die Linux-Laufzeit erstellt. Das bedeutet aber auch, dass die PHP-Laufzeit für andere Laufzeiten kompiliert werden kann, z. B. für Wasm.
Herkömmliche Methoden zum Portieren von Sprachen in die Wasm-Laufzeit
Unabhängig von der Plattform, auf der PHP ausgeführt wird, werden PHP-Skripts in denselben Bytecode kompiliert und von der Zend Engine ausgeführt. Die Zend Engine ist ein Compiler und eine Laufzeitumgebung für die PHP-Scriptsprache. Sie besteht aus der Zend Virtual Machine (VM), die sich aus dem Zend Compiler und dem Zend Executor zusammensetzt. Sprachen wie PHP, die in anderen Hochsprachen wie C implementiert sind, haben in der Regel Optimierungen, die auf bestimmte Architekturen wie Intel oder ARM ausgerichtet sind, und erfordern für jede Architektur ein anderes Backend. In diesem Kontext stellt Wasm eine neue Architektur dar. Wenn die VM architekturabhängigen Code hat, z. B. JIT- oder AOT-Kompilierung, implementiert der Entwickler auch ein Backend für JIT/AOT für die neue Architektur. Dieser Ansatz ist sehr sinnvoll, da der Hauptteil des Quellcodes oft einfach für jede neue Architektur neu kompiliert werden kann.
Da Wasm so Low-Level ist, liegt es nahe, denselben Ansatz zu verfolgen: Kompilieren Sie den Haupt-VM-Code mit seinem Parser, der Bibliotheksunterstützung, der Garbage Collection und dem Optimierer in Wasm und implementieren Sie bei Bedarf ein JIT- oder AOT-Backend für Wasm. Das ist seit dem Wasm-MVP möglich und funktioniert in der Praxis in vielen Fällen gut. Tatsächlich wird PHP, das in Wasm kompiliert wurde, für den WordPress Playground verwendet. Weitere Informationen zum Projekt finden Sie im Artikel Build in-browser WordPress experiences with WordPress Playground and WebAssembly.
PHP Wasm wird jedoch im Browser im Kontext der Hostsprache JavaScript ausgeführt. In Chrome werden JavaScript und Wasm in V8 ausgeführt, der Open-Source-JavaScript-Engine von Google, die ECMAScript gemäß ECMA-262 implementiert. Und V8 hat bereits eine Garbage Collection. Das bedeutet, dass Entwickler, die beispielsweise in Wasm kompilierte PHP verwenden, eine Implementierung der automatischen Speicherbereinigung der portierten Sprache (PHP) an den Browser senden, der bereits eine automatische Speicherbereinigung hat. Das ist so ineffizient, wie es sich anhört. Hier kommt WasmGC ins Spiel.
Ein weiteres Problem des alten Ansatzes, bei dem Wasm-Module ihren eigenen Garbage Collector auf dem linearen Speicher von Wasm aufbauen, besteht darin, dass es dann keine Interaktion zwischen dem Garbage Collector von Wasm und dem Garbage Collector der in Wasm kompilierten Sprache gibt. Dies führt in der Regel zu Problemen wie Speicherlecks und ineffizienten Versuchen, Speicher freizugeben. Wenn Wasm-Module den vorhandenen integrierten GC wiederverwenden, werden diese Probleme vermieden.
Programmiersprachen mit WasmGC auf neue Laufzeiten portieren
WasmGC ist ein Vorschlag der WebAssembly Community Group. Die aktuelle MVP-Implementierung von Wasm kann nur Zahlen (Ganzzahlen und Gleitkommazahlen) im linearen Speicher verarbeiten. Mit der Einführung des Vorschlags für Referenztypen kann Wasm zusätzlich externe Referenzen speichern. Mit WasmGC werden jetzt Heap-Typen für Structs und Arrays hinzugefügt, was die Unterstützung für nicht lineare Speicherzuweisung bedeutet. Jedes WasmGC-Objekt hat einen festen Typ und eine feste Struktur. Dadurch können VMs effizienten Code für den Zugriff auf die Felder generieren, ohne dass das Risiko von Deoptimierungen besteht, die bei dynamischen Sprachen wie JavaScript auftreten. Dieser Vorschlag fügt WebAssembly effiziente Unterstützung für verwaltete Sprachen auf hoher Ebene hinzu. Dies geschieht über Heap-Typen für Strukturen und Arrays, die es Sprachcompilern ermöglichen, die auf Wasm ausgerichtet sind, sich in einen Garbage Collector in der Host-VM zu integrieren. Vereinfacht ausgedrückt bedeutet das, dass beim Portieren einer Programmiersprache zu Wasm mit WasmGC der Garbage Collector der Programmiersprache nicht mehr Teil des Ports sein muss, sondern der vorhandene Garbage Collector verwendet werden kann.
Um die Auswirkungen dieser Verbesserung in der Praxis zu überprüfen, hat das Wasm-Team von Chrome Versionen des Fannkuch-Benchmarks (der Datenstrukturen während der Ausführung zuweist) aus C, Rust und Java zusammengestellt. Die C- und Rust-Binärdateien können je nach den verschiedenen Compiler-Flags zwischen 6,1 K und 9,6 K groß sein, während die Java-Version mit nur 2,3 K viel kleiner ist. C und Rust enthalten keinen Garbage Collector, aber sie bündeln trotzdem malloc/free
zur Speicherverwaltung. Der Grund, warum Java hier kleiner ist, liegt darin, dass es überhaupt keinen Code zur Speicherverwaltung bündeln muss. Dies ist nur ein konkretes Beispiel, aber es zeigt, dass WasmGC-Binärdateien sehr klein sein können. Und das, bevor überhaupt nennenswerte Optimierungen in Bezug auf die Größe vorgenommen wurden.
WasmGC-portierte Programmiersprache in Aktion
Kotlin/Wasm
Eine der ersten Programmiersprachen, die dank WasmGC zu Wasm portiert wurde, ist Kotlin in Form von Kotlin/Wasm. Die Demo mit Quellcode des Kotlin-Teams ist in der folgenden Auflistung zu sehen.
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"
Sie fragen sich jetzt vielleicht, was der Sinn der Sache ist, da der Kotlin-Code oben im Grunde aus den in Kotlin konvertierten JavaScript-OM-APIs besteht. In Kombination mit Compose Multiplatform wird es noch interessanter, da Entwickler die Benutzeroberfläche, die sie möglicherweise bereits für ihre Android-Kotlin-Apps erstellt haben, weiterentwickeln können. In der Kotlin/Wasm-Bildbetrachter-Demo können Sie sich eine frühe Version ansehen und den Quellcode erkunden, der ebenfalls vom Kotlin-Team stammt.
Dart und Flutter
Die Dart- und Flutter-Teams bei Google bereiten ebenfalls die Unterstützung für WasmGC vor. Die Kompilierung von Dart zu Wasm ist fast abgeschlossen und das Team arbeitet an der Unterstützung von Tools für die Bereitstellung von Flutter-Webanwendungen, die in WebAssembly kompiliert wurden. Informationen zum aktuellen Stand der Arbeiten finden Sie in der Flutter-Dokumentation. Die folgende Demo ist die Flutter WasmGC Preview.
Weitere Informationen zu WasmGC
Dieser Blogpost hat das Thema nur angerissen und bietet hauptsächlich einen allgemeinen Überblick über WasmGC. Weitere Informationen zu dieser Funktion finden Sie unter den folgenden Links:
- Eine neue Möglichkeit, Garbage-Collection-Programmiersprachen effizient in WebAssembly zu nutzen
- WasmGC – Übersicht
- WasmGC MVP
- WasmGC nach MVP
Danksagungen
Dieser Artikel wurde von Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler und Rachel Andrew geprüft.