Es gibt zwei Arten von Programmiersprachen: Programmiersprachen mit Garbage Collection und Programmiersprachen, die eine manuelle Speicherverwaltung erfordern. Beispiele für Ersteres sind unter anderem Kotlin, PHP oder Java. Beispiele für Letzteres sind C, C++ oder Rust. In der Regel haben Programmiersprachen der höheren Ebenen die automatische Speicherbereinigung als Standardfunktion. In diesem Blogpost liegt der Fokus auf solchen Programmiersprachen mit automatischer Speicherbereinigung und wie sie in WebAssembly (Wasm) kompiliert werden können. Aber was ist die Garbage Collection (oft als GC bezeichnet) überhaupt?
Unterstützte Browser
Automatische Speicherbereinigung
Vereinfacht ausgedrückt besteht die Idee der Garbage Collection darin, Speicherplatz zurückzugewinnen, der vom Programm zugewiesen wurde, aber nicht mehr referenziert wird. Dieser Speicher wird als „Garbage“ bezeichnet. Es gibt viele Strategien zur Implementierung der Garbage Collection. Eine davon ist die Referenzzählung, bei der die Anzahl der Verweise 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 freigegeben werden. Der PHP-Speicherbereiniger verwendet das Referenzzählen. Mit der Funktion xdebug_debug_zval()
der Erweiterung Xdebug können Sie sich die Funktionsweise genauer ansehen. 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 namens 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 a
zugewiesen. Danach wird b
der Zahl 42
neu zugewiesen und c
wird zurückgesetzt. Abschließend wird der Wert von a
auf null
festgelegt. Wenn Sie jeden Schritt des Programms mit xdebug_debug_zval()
annotieren, sehen Sie den Referenzzähler des Garbage Collectors in Aktion.
<?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 Protokolle ausgegeben. Sie sehen, dass die Anzahl der Verweise auf den Wert der Variablen a
nach jedem Schritt abnimmt, was angesichts der Codesequenz 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 Erkkennen von Zyklen. Für diesen Artikel reicht es jedoch aus, wenn Sie die grundlegenden Konzepte der Referenzzählung kennen.
Programmiersprachen werden in anderen Programmiersprachen implementiert
Es mag sich anfühlen, als wäre es der Anfang, aber Programmiersprachen werden in anderen Programmiersprachen implementiert. Die PHP-Laufzeit wird beispielsweise hauptsächlich in C implementiert. Den PHP-Quellcode finden Sie auf GitHub. Der Code für die Garbage Collection 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.
Traditionelle Methoden zum Portieren von Sprachen in die Wasm-Laufzeit
Unabhängig von der Plattform, auf der PHP ausgeführt wird, werden PHP-Scripts 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. Es besteht aus der Zend Virtual Machine (VM), die aus dem Zend-Compiler und dem Zend-Executor besteht. Sprachen wie PHP, die in anderen Hochsprachen wie C implementiert sind, haben häufig Optimierungen, die auf bestimmte Architekturen wie Intel oder ARM ausgerichtet sind, und erfordern für jede Architektur ein anderes Backend. In diesem Zusammenhang stellt Wasm eine neue Architektur dar. Wenn die VM architekturspezifischen Code wie Just-In-Time- (JIT-) oder Ahead-Of-Time- (AOT-) Kompilierung enthält, implementiert der Entwickler auch ein JIT-/AOT-Back-End für die neue Architektur. Dieser Ansatz ist sehr sinnvoll, da der Hauptteil der Codebasis oft für jede neue Architektur einfach neu kompiliert werden kann.
Angesichts der Low-Level-Natur von Wasm ist es naheliegend, dort denselben Ansatz zu versuchen: den Haupt-VM-Code mit seinem Parser, der Bibliotheksunterstützung, der Garbage Collection und dem Optimierer in Wasm neu kompilieren und bei Bedarf ein JIT- oder AOT-Back-End für Wasm implementieren. Das ist seit dem Wasm-MVP möglich und funktioniert in vielen Fällen gut in der Praxis. Tatsächlich wird der WordPress Playground mit in Wasm kompiliertem PHP betrieben. Weitere Informationen zum Projekt finden Sie im Artikel In-Browser-WordPress-Websites mit WordPress Playground und WebAssembly erstellen.
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. Außerdem hat V8 bereits einen Garbage Collector. Das bedeutet, dass Entwickler, die beispielsweise PHP verwenden, das in Wasm kompiliert wurde, eine Garbage-Collection-Implementierung der portierten Sprache (PHP) an den Browser senden, der bereits einen Garbage Collector hat. Das ist so verschwenderisch, wie es klingt. Hier kommt WasmGC ins Spiel.
Das andere Problem des alten Ansatzes, bei dem Wasm-Module ihren eigenen GC auf dem linearen Wasm-Speicher erstellen, ist, dass es dann keine Interaktion zwischen dem eigenen Garbage Collector von Wasm und dem darauf aufbauenden Garbage Collector der in Wasm kompilierten Sprache gibt. Dies führt in der Regel zu Problemen wie Speicherlecks und ineffizienten Sammlungsversuchen. Wenn Wasm-Module die vorhandene integrierte GC wiederverwenden können, werden diese Probleme vermieden.
Programmiersprachen mit WasmGC auf neue Laufzeiten umstellen
WasmGC ist ein Vorschlag der WebAssembly Community Group. Die aktuelle Wasm-MVP-Implementierung kann nur mit Zahlen, also Ganzzahlen und Gleitkommazahlen, im linearen Speicher umgehen. Mit dem Vorschlag für Referenztypen kann Wasm zusätzlich externe Referenzen speichern. WasmGC fügt jetzt Struktur- und Array-Heaptypen hinzu, was die Unterstützung für die nicht lineare Speicherzuweisung bedeutet. Jedes WasmGC-Objekt hat einen festen Typ und eine feste Struktur. Dadurch können VMs effizient Code zum Zugriff auf ihre Felder generieren, ohne das Risiko von deoptimizations, die bei dynamischen Sprachen wie JavaScript auftreten. Mit diesem Vorschlag wird WebAssembly eine effiziente Unterstützung für hochrangige verwaltete Sprachen hinzugefügt. Dazu werden Stack- und Heap-Typen für Strukturen und Arrays verwendet, die es Sprachcompilern ermöglichen, die auf Wasm ausgerichtet sind, in einen Garbage Collector in der Host-VM eingebunden zu werden. Vereinfacht ausgedrückt bedeutet das, dass bei WasmGC der Garbage Collector der Programmiersprache nicht mehr Teil des Ports sein muss, sondern stattdessen 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 während der Ausführung Datenstrukturen zuweist) in C, Rust und Java kompiliert. Die C- und Rust-Binärdateien können je nach den verschiedenen Compiler-Flags zwischen 6,1 K und 9,6 K liegen, während die Java-Version mit nur 2,3 K viel kleiner ist. C und Rust enthalten keinen Garbage Collector, aber sie enthalten malloc/free
zur Verwaltung des Arbeitsspeichers. Java ist hier kleiner, weil es keinen Code zur Speicherverwaltung benötigt. Dies ist nur ein Beispiel, das zeigt, dass WasmGC-Binärdateien sehr klein sein können – und das noch vor einer nennenswerten Optimierung der Größe.
Eine mit WasmGC portierte Programmiersprache in Aktion
Kotlin Wasm
Eine der ersten Programmiersprachen, die dank WasmGC auf Wasm portiert wurde, ist Kotlin in Form von Kotlin/Wasm. Die Demo mit dem Quellcode des Kotlin-Teams ist im folgenden Listing 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"
Jetzt fragen Sie sich vielleicht, was der Sinn ist, da der obige Kotlin-Code im Grunde aus den in Kotlin konvertierten JavaScript OM APIs besteht. In Kombination mit Compose Multiplatform macht es mehr Sinn, da Entwickler so auf der Benutzeroberfläche aufbauen können, die sie möglicherweise bereits für ihre Android-Kotlin-Apps erstellt haben. Eine erste Erkundung dieser Möglichkeiten bietet die Demo Kotlin/Wasm-Bildbetrachter und der Quellcode, ebenfalls vom Kotlin-Team.
Dart und Flutter
Die Dart- und Flutter-Teams von Google arbeiten ebenfalls an der Unterstützung von WasmGC. Die Dart-zu-Wasm-Kompilierung ist fast abgeschlossen und das Team arbeitet an der Toolunterstützung für die Bereitstellung von Flutter-Webanwendungen, die in WebAssembly kompiliert wurden. Aktuelle Informationen zum Stand der Arbeit finden Sie in der Flutter-Dokumentation. Die folgende Demo ist die Flutter WasmGC-Vorabversion.
Weitere Informationen zu WasmGC
In diesem Blogpost haben wir nur einen kleinen Teil der Funktionen von WasmGC behandelt und vor allem einen allgemeinen Überblick gegeben. Weitere Informationen zu dieser Funktion findest du unter den folgenden Links:
- Eine neue Möglichkeit, Garbage-Collected-Programmiersprachen effizient in WebAssembly zu bringen
- 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.