Es gibt zwei Arten von Programmiersprachen: Programmiersprachen mit Garbage Collection und Programmiersprachen, die eine manuelle Speicherverwaltung erfordern. Beispiele dafür sind Kotlin, PHP und Java. Beispiele für Letzteres sind C, C++ oder Rust. In der Regel ist bei höheren Programmiersprachen die Wahrscheinlichkeit der automatischen Speicherbereinigung als Standardfunktion höher. In diesem Blogpost liegt der Fokus auf solchen Programmiersprachen mit automatischer Speicherbereinigung und wie sie in WebAssembly (Wasm) kompiliert werden können. Aber womit beginnt die automatische Speicherbereinigung (GC)?
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. Dabei wird die Anzahl der Verweise auf Objekte im Speicher gezählt. Wenn es keine weiteren Verweise auf ein Objekt gibt, kann es als nicht mehr verwendet markiert werden und somit für die automatische Speicherbereinigung bereit sein. 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. Schließlich wird der Wert von a
auf null
gesetzt. 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 automatischen Speicherbereinigung, wie 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 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 aber auch PHP aus dem Quellcode erstellen. In einer Linux-Umgebung wird 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
PHP-Skripts werden unabhängig von der Plattform, auf der PHP ausgeführt wird, 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. Dies ist seit dem Wasm-MVP möglich und funktioniert in der Praxis in vielen Fällen gut. Der WordPress Playground basiert sogar auf in Wasm kompilierten PHP. 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. Und V8 hat bereits eine automatische Speicherbereinigung. Das bedeutet, dass Entwickler, die z. B. in Wasm kompilierte PHP verwenden, am Ende eine Garbage Collector-Implementierung der portierten Sprache (PHP) an den Browser senden, der bereits einen Garbage Collector hat, was so viel Aufwand verursacht, 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 Deoptimierungen, 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 Binärdateien für C und Rust können je nach Compiler-Flags zwischen 6.1 K und 9.6 K liegen. Die Java-Version ist mit nur 2.3 K viel kleiner. 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 konkretes Beispiel, aber es zeigt, dass WasmGC-Binärdateien das Potenzial haben, sehr klein zu sein, und dies ist noch bevor eine erhebliche Optimierung im Hinblick auf die Größe erfolgt.
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 bei Google bereiten auch Unterstützung für WasmGC vor. Die Dart-to-Wasm-Kompilierung ist fast abgeschlossen und das Team arbeitet an der Unterstützung von Tools für die Bereitstellung von Flutter-Webanwendungen, die für WebAssembly kompiliert wurden. Aktuelle Informationen zum Stand der Arbeit finden Sie in der Flutter-Dokumentation. Die folgende Demo ist die Flutter-WasmGC-Vorschau.
Weitere Informationen zu WasmGC
Dieser Blogpost hat kaum an der Oberfläche gekratzt und bietet größtenteils einen allgemeinen Überblick über WasmGC. Weitere Informationen zu dieser Funktion finden Sie 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.