Czyszczenie pamięci WebAssembly (WasmGC) jest teraz domyślnie włączone w Chrome

Istnieją 2 rodzaje języków programowania: języki z zbieraniem pamięci i języki, które wymagają ręcznego zarządzania pamięcią. Przykładami takich języków są m.in. Kotlin, PHP i Java. Przykładami takich języków są C, C++ i Rust. Zasadniczo języki programowania wyższego poziomu mają funkcję zbierania elementów usuniętych jako standardową funkcję. W tym poście na blogu skupiamy się na takich językach programowania z zbieraniem pamięci, które można skompilować do WebAssembly (Wasm). Ale czym jest zbieranie odpadów (często nazywane GC)?

Obsługa przeglądarek

  • Chrome: 119.
  • Edge: 119.
  • Firefox: 120.
  • Safari: nieobsługiwane.

Czyszczenie pamięci

Upraszczając, idea zbierania elementów zbędących polega na próbie odzyskania pamięci, która została przydzielona przez program, ale do której nie odwołuje się już program. Takie dane są nazywane śmieciami. Istnieje wiele strategii implementacji zbierania elementów. Jednym z nich jest liczenie odwołań, którego celem jest zliczanie odwołań do obiektów w pamięci. Gdy nie ma już żadnych odwołań do obiektu, można go oznaczyć jako nieużywany i tym samym gotowy do usunięcia. Zbiórnik śmieci PHP korzysta z liczenia odwołań, a funkcja xdebug_debug_zval() w rozszerzeniu Xdebug pozwala zajrzeć pod maskę. Rozważ ten program PHP.

<?php
  $a= (string) rand();
  $c = $b = $a;
  $b = 42;
  unset($c);
  $a = null;
?>

Program przypisuje losową liczbę rzutowaną na ciąg znaków nowej zmiennej o nazwie a. Następnie tworzy 2 nowe zmienne, bc, i przypisuje im wartość a. Następnie przypisuje b do numeru 42, a potem anuluje c. Na koniec ustawia wartość parametru a na null. Dzięki adnotacji każdego kroku programu za pomocą xdebug_debug_zval() możesz obserwować działanie licznika odwołań.

<?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');
?>

Powyższy przykład wygeneruje następujące dzienniki, w których widać, jak po każdym kroku maleje liczba odwołań do wartości zmiennej a. Jest to logiczne, biorąc pod uwagę sekwencję kodu. (Twoja liczba losowa będzie oczywiście inna).

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

Zbiórka śmieci wiąże się z innymi wyzwaniami, takimi jak wykrywanie cykli, ale w tym artykule wystarczy podstawowa wiedza o liczeniu odwołań.

Języki programowania są implementowane w innych językach programowania

Może to wyglądać na początek, ale języki programowania są implementowane w innych językach programowania. Na przykład środowisko uruchomieniowe PHP jest głównie implementowane w języku C. Kod źródłowy PHP znajdziesz na GitHubie. Kod PHP do czyszczenia pamięci znajduje się głównie w pliku zend_gc.c. Większość deweloperów instaluje PHP za pomocą menedżera pakietów w systemie operacyjnym. Deweloperzy mogą też tworzyć PHP na podstawie kodu źródłowego. Na przykład w środowisku Linux kroki ./buildconf && ./configure && make skompilują PHP na potrzeby środowiska uruchomieniowego Linuxa. Oznacza to też, że środowisko wykonawcze PHP można skompilować na potrzeby innych środowisk wykonawczych, takich jak Wasm.

Tradycyjne metody przenoszenia języków na środowisko wykonawcze Wasm

Niezależnie od platformy, na której działa PHP, skrypty PHP są kompilowane do tego samego kodu bajtowego i uruchamiane przez Zend Engine. Zend Engine to kompilator i środowisko uruchomieniowe dla języka skryptowego PHP. Składa się z maszyny wirtualnej Zend (VM), która składa się z kompilatora Zend i wykonawcy Zend. Języki takie jak PHP, które są implementowane w innych językach wysokiego poziomu, np. C, często mają optymalizacje przeznaczone dla konkretnych architektur, np. Intel lub ARM, i wymagają innego backendu dla każdej architektury. W tym kontekście Wasm oznacza nową architekturę. Jeśli maszyna wirtualna ma kod specyficzny dla architektury, np. kompilację just-in-time (JIT) lub kompilację z wyprzedzeniem (AOT), deweloper wdraża też backend dla JIT/AOT dla nowej architektury. Takie podejście ma wiele sensu, ponieważ często główną część kodu można po prostu ponownie skompilować pod każdą nową architekturę.

Z uwagi na to, że Wasm jest na bardzo niskim poziomie, naturalne jest, że w tym przypadku zastosujesz to samo podejście: skompilujesz główny kod VM z jego parsowaniem, obsługą biblioteki, zbieraniem śmieci i optymalizacją na potrzeby Wasm, a w razie potrzeby wdrożysz w nim backend JIT lub AOT. Jest to możliwe od czasu wydania Wasm MVP i w wielu przypadkach sprawdza się w praktyce. W fakcie PHP skompilowane do Wasm obsługuje WordPress Playground. Więcej informacji o projekcie znajdziesz w artykule Tworzenie aplikacji WordPress w przeglądarce za pomocą WordPress Playground i WebAssembly.

Jednak PHP Wasm działa w przeglądarce w kontekście języka hosta JavaScript. W Chrome JavaScript i Wasm są wykonywane w V8, czyli mechanizmie JavaScriptu o otwartym kodzie źródłowym od Google, który implementuje ECMAScript zgodnie ze specyfikacją ECMA-262. V8 ma już też własny system zarządzania pamięcią. Oznacza to, że deweloperzy korzystający np. z PHP skompilowanego na Wasm wysyłają do przeglądarki implementację kolektora pamięci dla przeniesionego języka (PHP), który już ma takiego kolektora. Jest to niepotrzebne obciążenie. Właśnie w tym miejscu pojawia się WasmGC.

Innym problemem związanym ze starym podejściem polegającym na umożliwieniu modułom Wasm tworzenia własnego GC na podstawie liniowej pamięci Wasm jest to, że nie ma wtedy interakcji między własnym zbieraczem śmieci Wasm a zbieraczem śmieci zbudowanym na podstawie kompilowanego języka Wasm, co może powodować problemy, takie jak wycieki pamięci i nieefektywne próby zbierania. Umożliwienie modułom Wasm ponowne użycie istniejącego wbudowanego GC pozwala uniknąć tych problemów.

Przenoszenie języków programowania na nowe środowisko uruchomieniowe za pomocą WasmGC

WasmGC to propozycja grupy Community WebAssembly. Obecna implementacja Wasm MVP może obsługiwać tylko liczby, czyli liczby całkowite i zmiennoprzecinkowe, w pamięci liniowej. Wraz z wprowadzeniem propozycji dotyczącej typów odwołań Wasm może dodatkowo przechowywać odwołania zewnętrzne. WasmGC obsługuje teraz typy stosu struktury i tablicy, co oznacza obsługę nieliniowej alokacji pamięci. Każdy obiekt WasmGC ma stały typ i strukturę, co ułatwia maszynom wirtualnym generowanie wydajnego kodu umożliwiającego dostęp do ich pól bez ryzyka deoptymalizacji, która występuje w przypadku języków dynamicznych, takich jak JavaScript. W ramach tej propozycji dodamy do WebAssembly obsługę języków zarządzanych wysokiego poziomu za pomocą typów struktury i tablicy stosu, które umożliwiają kompilatorom języków docelowych Wasm integrację z zbieraczem pamięci usuwającym śmieci w gospodarczej maszynie wirtualnej. Upraszczając, oznacza to, że w przypadku WasmGC przenoszenie języka programowania na Wasm oznacza, że nie trzeba już uwzględniać w nim zbieracza pamięci, ale można użyć istniejącego zbieracza.

Aby sprawdzić, jaki wpływ na działanie przeglądarki ma to ulepszenie, zespół Wasm Chrome skompilował wersje benchmarka Fankuch (który przypisuje struktury danych podczas działania) z użyciem C, RustJava. Pliki binarne w językach C i Rust mogą mieć rozmiar od 6,1 K do 9,6 K w zależności od różnych flag kompilatora, podczas gdy wersja w języku Java jest znacznie mniejsza i ma tylko 2,3 K. C i Rust nie zawierają zbieracza pamięci, ale nadal zawierają pakiet malloc/free do zarządzania pamięcią. Java jest tu mniejsza, ponieważ nie musi w ogóle zawierać kodu do zarządzania pamięcią. To tylko jeden przykład, ale pokazuje, że pliki binarne WasmGC mogą być bardzo małe, nawet bez znaczących działań związanych z optymalizacją pod kątem rozmiaru.

Jak działa język programowania przeniesiony do WasmGC

Kotlin Wasm

Jednym z pierwszych języków programowania, który został przeniesiony do Wasm dzięki WasmGC, jest Kotlin w postaci Kotlin/Wasm. Demokodem źródłowym udostępnionym przez zespół Kotlin, które jest widoczne w poniższym wykazie.

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"

Możesz się zastanawiać, jaki jest sens tego, skoro kod Kotlina składa się głównie z interfejsów OM JavaScripta przekonwertowanych na Kotlina. W połączeniu z Compose Multiplatform ma to sens, ponieważ pozwala deweloperom tworzyć interfejs użytkownika na podstawie tego, który został już utworzony w przypadku aplikacji na Androida w języku Kotlin. Zapoznaj się z wczesną wersją tej funkcji za pomocą prezentacji Kotlin/Wasm image viewer i zapoznaj się z jej kodem źródłowym, który również udostępnił zespół Kotlin.

Dart i Flutter

Zespoły Dart i Flutter w Google przygotowują też obsługę WasmGC. Prace nad kompilacją Darta na Wasm są prawie ukończone, a zespół pracuje nad narzędziami do tworzenia aplikacji internetowych Fluttera skompilowanych na Wasm. Informacje o bieżącym stanie prac znajdziesz w dokumentacji Fluttera. Poniższy pokaz to wersja zapoznawcza Flutter WasmGC.

Więcej informacji o WasmGC

Ten post na blogu to zaledwie zarys ogólnych informacji o WasmGC. Więcej informacji o tej funkcji znajdziesz w tych artykułach:

Podziękowania

Ten artykuł został sprawdzony przez Matthiasa Liedtke, Adama Kleina, Joshuę Bella, Alona Zakai, Jakoba Kummerowa, Clemensa Backesa, Emanuela ZiegleraRachel Andrew.