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

Istnieją 2 rodzaje języków programowania: języki z zbieraniem elementów 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 i na tym, jak można je 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 odśmiecający 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 usuwa c. Na koniec ustawia wartość 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 zwróci poniższe logi, gdzie widać, jak po każdym kroku zmniejsza się liczba odwołań do wartości zmiennej a. Ma to sens w przypadku danej sekwencji kodu. (losowy numer będzie oczywiście inny).

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 się wydawać, że to wprowadzenie, ale języki programowania są zaimplementowane 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ć język PHP z 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 do środowiska wykonawczego 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 reprezentuje 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 wersji Wasm MVP i w wielu przypadkach sprawdza się w praktyce. Tak naprawdę WordPress Playground opiera się na PHP skompilowanym na język Wasm. Więcej informacji o projekcie znajdziesz w artykule Tworzenie aplikacji WordPress w przeglądarce za pomocą WordPress Playground i WebAssembly.

Jednak JavaScript Wasm w języku PHP działa w przeglądarce w kontekście języka 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, w ramach którego zezwalanie modułom Wasm na tworzenie własnych GC na podstawie pamięci liniowej Wasm, jest brak interakcji między własnym modułem odśmiecania Wasm a wbudowanym modułem odśmiecania języka skompilowanego w wasm, który zwykle powoduje problemy, takie jak wycieki pamięci i nieefektywne próby zbierania danych. Umożliwienie modułom Wasm ponowne wykorzystanie istniejącego wbudowanego GC pozwala uniknąć tych problemów.

Przenoszenie języków programowania do nowych środowisk wykonawczych 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 wdrożeniem 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 efektywnego kodu umożliwiającego dostęp do pól bez ryzyka deoptymalizacji wykorzystywanych przez języki dynamiczne takie 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. Języki C i Rust nie zawierają modułu do czyszczenia pamięci, ale nadal zawierają pakiet malloc/free do zarządzania pamięcią. Zmniejsza to rozmiar Javy, ponieważ w ogóle nie trzeba w nich pakować żadnego kodu zarządzania pamięcią. To tylko jeden z konkretnych przykładów, który pokazuje, że pliki binarne WasmGC mogą być bardzo małe, a to jeszcze przed rozpoczęciem jakiejkolwiek istotnej pracy nad 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. Przykładkodem źródłowym udostępnionym przez zespół Kotlin, który jest widoczny 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. Nabiera sensu w połączeniu z funkcją Compose Multiplatform, która umożliwia programistom korzystanie z interfejsu, który już utworzyli dla swoich aplikacji na Androida 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ą Dart-to-Wasm są już prawie ukończone, a zespół pracuje nad obsługą narzędzi do dostarczania aplikacji internetowych Flutter skompilowanych do WebAssembly. O bieżącym stanie zadania możesz przeczytać w dokumentacji Flutter. Poniższy pokaz to wersja zapoznawcza Flutter WasmGC.

Więcej informacji o WasmGC

Ten post na blogu to zaledwie zarys ogólnego obrazu, który w głównej mierze dotyczył 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.