Er zijn twee soorten programmeertalen: programmeertalen met garbage collection en programmeertalen die handmatig geheugenbeheer vereisen. Voorbeelden van de eerste, naast vele andere, zijn Kotlin, PHP en Java. Voorbeelden van de laatste zijn C, C++ en Rust. Over het algemeen hebben hogere programmeertalen garbage collection vaker als standaardfunctie. In deze blogpost ligt de focus op dergelijke programmeertalen met garbage collection en hoe ze gecompileerd kunnen worden naar WebAssembly (Wasm). Maar wat is garbage collection (vaak aangeduid als GC) eigenlijk?
Browser Support
Afvalinzameling
Simpel gezegd is het idee van garbage collection de poging om geheugen terug te winnen dat door het programma is toegewezen, maar waarnaar niet langer wordt verwezen. Zulk geheugen wordt garbage collection genoemd. Er zijn veel strategieën voor de implementatie van garbage collection. Een daarvan is referentietelling, waarbij het doel is om het aantal verwijzingen naar objecten in het geheugen te tellen. Wanneer er geen verwijzingen meer naar een object zijn, kan het worden gemarkeerd als niet langer gebruikt en dus klaar voor garbage collection. De garbage collector van PHP maakt gebruik van referentietelling , en met behulp van de functie xdebug_debug_zval()
van de Xdebug- extensie kunt u een kijkje nemen onder de motorkap. Beschouw het volgende PHP-programma.
<?php
$a= (string) rand();
$c = $b = $a;
$b = 42;
unset($c);
$a = null;
?>
Het programma wijst een willekeurig getal, gecast naar een string, toe aan een nieuwe variabele genaamd a
. Vervolgens maakt het twee nieuwe variabelen aan, b
en c
, en wijst deze de waarde a
toe. Daarna wijst het b
opnieuw toe aan het getal 42
en verwijdert c
. Ten slotte stelt het de waarde van a
in op null
. Door elke stap van het programma te annoteren met xdebug_debug_zval()
, kunt u de referentieteller van de garbage collector aan het werk zien.
<?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');
?>
Het bovenstaande voorbeeld genereert de volgende logs, waarin u ziet hoe het aantal verwijzingen naar de waarde van de variabele a
na elke stap afneemt, wat logisch is gezien de codevolgorde. (Uw willekeurige nummer zal uiteraard anders zijn.)
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
Er zijn nog andere uitdagingen bij het ophalen van afval, zoals het detecteren van cycli , maar voor dit artikel is een basiskennis van referentietelling voldoende.
Programmeertalen worden geïmplementeerd in andere programmeertalen
Het voelt misschien als een begin, maar programmeertalen worden in andere programmeertalen geïmplementeerd. De PHP-runtime is bijvoorbeeld voornamelijk in C geïmplementeerd. Je kunt de PHP-broncode bekijken op GitHub . De garbage collection-code van PHP bevindt zich voornamelijk in het bestand zend_gc.c
. De meeste ontwikkelaars installeren PHP via de pakketbeheerder van hun besturingssysteem. Maar ontwikkelaars kunnen PHP ook vanuit de broncode bouwen . In een Linux-omgeving bijvoorbeeld, bouwen de stappen ./buildconf && ./configure && make
PHP voor de Linux-runtime. Maar dit betekent ook dat de PHP-runtime kan worden gecompileerd voor andere runtimes, zoals, je raadt het al, Wasm.
Traditionele methoden voor het porteren van talen naar de Wasm-runtime
Onafhankelijk van het platform waarop PHP draait, worden PHP-scripts gecompileerd naar dezelfde bytecode en uitgevoerd door de Zend Engine . De Zend Engine is een compiler en runtime-omgeving voor de PHP-scripttaal. Deze bestaat uit de Zend Virtual Machine (VM), die is samengesteld uit de Zend Compiler en de Zend Executor. Talen zoals PHP die geïmplementeerd zijn in andere hogere programmeertalen zoals C, hebben vaak optimalisaties die gericht zijn op specifieke architecturen, zoals Intel of ARM, en vereisen een andere backend voor elke architectuur. In deze context vertegenwoordigt Wasm een nieuwe architectuur. Als de VM architectuurspecifieke code heeft, zoals just-in-time (JIT) of ahead-of-time (AOT) compilatie, dan implementeert de ontwikkelaar ook een backend voor JIT/AOT voor de nieuwe architectuur. Deze aanpak is zeer zinvol, omdat het grootste deel van de codebase vaak gewoon opnieuw kan worden gecompileerd voor elke nieuwe architectuur.
Gezien het lage niveau van Wasm, is het logisch om dezelfde aanpak daar te proberen: hercompileer de hoofd-VM-code met parser, bibliotheekondersteuning, garbage collection en optimizer naar Wasm, en implementeer indien nodig een JIT- of AOT-backend voor Wasm. Dit is mogelijk sinds de Wasm MVP en werkt in de praktijk in veel gevallen goed. Sterker nog, PHP gecompileerd naar Wasm is de drijvende kracht achter WordPress Playground . Lees meer over het project in het artikel Build in-browser WordPress experiences with WordPress Playground and WebAssembly .
PHP Wasm draait echter in de browser in de context van de hosttaal JavaScript. In Chrome worden JavaScript en Wasm uitgevoerd in V8 , Googles open-source JavaScript-engine die ECMAScript implementeert zoals gespecificeerd in ECMA-262 . Bovendien heeft V8 al een garbage collector . Dit betekent dat ontwikkelaars die bijvoorbeeld PHP gebruiken dat naar Wasm is gecompileerd, uiteindelijk een garbage collector-implementatie van de geporteerde taal (PHP) naar de browser sturen die al een garbage collector heeft, wat net zo verspillend is als het klinkt. Dit is waar WasmGC om de hoek komt kijken.
Het andere probleem van de oude aanpak, waarbij Wasm-modules hun eigen GC bovenop Wasms lineaire geheugen bouwen, is dat er dan geen interactie is tussen Wasms eigen garbage collector en de ingebouwde garbage collector van de naar Wasm gecompileerde taal. Dit leidt vaak tot problemen zoals geheugenlekken en inefficiënte verzamelpogingen. Door Wasm-modules de bestaande ingebouwde GC te laten hergebruiken, worden deze problemen vermeden.
Programmeertalen naar nieuwe runtimes porteren met WasmGC
WasmGC is een voorstel van de WebAssembly Community Group . De huidige Wasm MVP-implementatie kan alleen getallen verwerken, dat wil zeggen gehele getallen en floats, in lineair geheugen. Met het voorgestelde referentietype kan Wasm bovendien externe referenties opslaan. WasmGC voegt nu struct- en array-heaptypen toe, wat ondersteuning biedt voor niet-lineaire geheugentoewijzing. Elk WasmGC-object heeft een vast type en een vaste structuur, waardoor virtuele machines eenvoudig efficiënte code kunnen genereren om toegang te krijgen tot hun velden, zonder het risico op deoptimalisaties die dynamische talen zoals JavaScript met zich meebrengen. Dit voorstel voegt daarmee efficiënte ondersteuning toe voor high-level managed languages aan WebAssembly, via struct- en array-heaptypen waarmee taalcompilers die Wasm targeten, kunnen integreren met een garbage collector in de host-VM. Simpel gezegd betekent dit dat het porten van een programmeertaal naar Wasm met WasmGC betekent dat de garbage collector van de programmeertaal niet langer deel hoeft uit te maken van de port, maar dat in plaats daarvan de bestaande garbage collector kan worden gebruikt.
Om de impact van deze verbetering in de praktijk te verifiëren, heeft het Wasm-team van Chrome versies van de Fannkuch-benchmark (die datastructuren toewijst terwijl het werkt) gecompileerd uit C , Rust en Java . De binaire bestanden van C en Rust kunnen variëren van 6,1 K tot 9,6 K, afhankelijk van de verschillende compiler-vlaggen, terwijl de Java-versie veel kleiner is met slechts 2,3 K ! C en Rust bevatten geen garbage collector, maar bundelen wel malloc/free
om geheugen te beheren. De reden dat Java hier kleiner is, is omdat het helemaal geen code voor geheugenbeheer hoeft te bundelen. Dit is slechts één specifiek voorbeeld, maar het laat zien dat de binaire bestanden van WasmGC het potentieel hebben om erg klein te zijn, en dit zelfs vóór enig significant werk aan optimalisatie voor grootte.
Een WasmGC-geporteerde programmeertaal in actie zien
Kotlin Wasm
Een van de eerste programmeertalen die dankzij WasmGC naar Wasm is geporteerd, is Kotlin in de vorm van Kotlin/Wasm . De demo , met broncode van het Kotlin-team, is te zien in de volgende lijst.
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"
Je vraagt je misschien af wat het nut hiervan is, aangezien de bovenstaande Kotlin-code in feite bestaat uit de JavaScript OM API's die naar Kotlin zijn geconverteerd . Het begint logischer te worden in combinatie met Compose Multiplatform , waarmee ontwikkelaars kunnen voortbouwen op de gebruikersinterface die ze mogelijk al voor hun Android Kotlin-apps hebben gemaakt. Bekijk een vroege verkenning hiervan met de Kotlin/Wasm image viewer , eveneens met dank aan het Kotlin-team.
Dart en Flutter
De Dart- en Flutter-teams bij Google bereiden ook ondersteuning voor WasmGC voor. De Dart-naar-Wasm-compilatie is bijna voltooid en het team werkt aan tooling voor ondersteuning bij het leveren van Flutter-webapplicaties die gecompileerd zijn naar WebAssembly. U kunt meer lezen over de huidige stand van zaken in de Flutter-documentatie . De volgende demo is de Flutter WasmGC Preview .
Meer informatie over WasmGC
Deze blogpost heeft nog maar het topje van de ijsberg behandeld en biedt vooral een algemeen overzicht van WasmGC. Bekijk deze links voor meer informatie over deze functie:
- Een nieuwe manier om programmeertalen die gebruikmaken van garbage collection efficiënt naar WebAssembly te brengen
- WasmGC-overzicht
- WasmGC MVP
- WasmGC na MVP
Dankbetuigingen
Dit artikel is beoordeeld door Matthias Liedtke , Adam Klein , Joshua Bell , Alon Zakai , Jakob Kummerow , Clemens Backes , Emanuel Ziegler en Rachel Andrew .