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, które można skompilować do WebAssembly (Wasm). Ale czym jest zbieranie odpadów (często nazywane GC)?

Browser Support

  • Chrome: 119.
  • Edge: 119.
  • Firefox: 120.
  • Safari: 18.2.

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

W tym przykładzie zostaną wygenerowane następujące logi, 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órkę odpadów utrudniają też inne problemy, takie 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ć jak w filmie Inception, 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ć pod kątem 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 wdrapiesz backend JIT lub AOT dla Wasm. Jest to możliwe od czasu wydania wersji 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 kompilowanego w Wasm kodu PHP 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 przypadku przydaje 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żywanie 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 benchmarku 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ą 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 WebAssembly. Informacje o bieżącym stanie prac znajdziesz w dokumentacji Fluttera. Poniższy pokaz to wersja podglądu Flutter WasmGC.

Więcej informacji o WasmGC

Ten post na blogu to zaledwie wierzchołek góry lodowej. Przedstawia on głównie ogólny zarys WasmGC. Więcej informacji o tej funkcji znajdziesz pod tymi linkami:

Podziękowania

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