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

Istnieją 2 typy języków programowania: ze śmieciami oraz języki programowania, które wymagają ręcznego zarządzania pamięcią. Jednymi z pierwszych języków są Kotlin, PHP i Java. Przykładami tych ostatnich są C, C++ i Rust. Ogólnie rzecz biorąc, w przypadku języków programowania wyższego poziomu jest większe prawdopodobieństwo, że funkcja czyszczenia pamięci jest dostępna w ramach standardowej funkcji. W tym poście skupiamy się na usuwanych odpadających językach programowania oraz o sposobach ich skompilowania do WebAssembly (Wasm). Od czego zacząć?

Obsługa przeglądarek

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

Usuwanie odpadów

W uproszczeniu idea czyszczenia pamięci to próba odzyskania pamięci, która została przydzielona przez program, ale już się do niej nie odwołuje. Taką pamięć nazywamy „śmieć”. Jest wiele strategii wdrażania funkcji czyszczenia pamięci. Jednym z nich jest zliczanie odwołań, w ramach którego celem jest zliczenie liczby odwołań do obiektów w pamięci. Gdy nie ma więcej odwołań do obiektu, można go oznaczyć jako nieużywany i jest on gotowy do czyszczenia pamięci. Funkcja czyszczenia pamięci PHP korzysta ze liczenia odwołań, a funkcja xdebug_debug_zval() rozszerzenia Xdebug umożliwia wgląd w nie. Rozważ poniższy program w języku PHP.

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

Program przypisuje losową liczbę rzutowaną do ciągu do nowej zmiennej o nazwie a. Następnie tworzy dwie nowe zmienne, b i c, i przypisuje im wartość a. Następnie przypisuje numer b do numeru 42, a następnie anuluje ustawienie c. Na koniec ustawia wartość a na null. Dzięki adnotacjom xdebug_debug_zval() do każdego kroku programu możesz zobaczyć licznik odśmiecania w działaniu.

<?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

Istnieją też inne wyzwania związane z odpadaniem, takie jak wykrywanie cykli, ale w tym artykule wystarczy podstawowa znajomość liczenia plików referencyjnych.

Języki programowania są zaimplementowane 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 wykonawcze PHP jest implementowane głównie w języku C. Kod źródłowy PHP znajdziesz na GitHubie. Kod czyszczenia pamięci PHP znajduje się głównie w pliku zend_gc.c. Większość programistów instaluje język 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 spowodują utworzenie języka PHP dla środowiska wykonawczego Linux. Oznacza to jednak również, że środowisko wykonawcze PHP można skompilować na potrzeby innych środowisk wykonawczych, np. Wasm.

Tradycyjne metody przenoszenia języków do środowiska wykonawczego Wasm

Niezależnie od tego, na której platformie działa język PHP, skrypty PHP są kompilowane do tego samego kodu bajtowego i uruchamiane przez Zend Engine. Zend Engine to kompilator i środowisko wykonawcze dla języka skryptów PHP. Składa się on z maszyny wirtualnej Zend (VM) składającej się z kompilatora Zend i wykonawcy Zend. Języki takie jak PHP, które są zaimplementowane w innych językach ogólnych, takich jak C, zwykle są optymalizowane pod kątem określonych architektur, takich jak Intel czy ARM, i wymagają osobnego backendu dla każdej architektury. W tym kontekście Wasm reprezentuje nową architekturę. Jeśli maszyna wirtualna zawiera kod specyficzny dla architektury, na przykład kompilację „just-in-time” (JIT) lub „z wyprzedzeniem” (AOT), deweloper wdraża również backend dla JIT/AOT dla nowej architektury. Takie podejście ma sens, ponieważ często główną część bazy kodu można po prostu skompilować dla każdej nowej architektury.

Z uwagi na to, że Wasm jest na niskim poziomie, naturalne jest tu wypróbować to samo podejście: ponownie skompilować główny kod maszyny wirtualnej z jego parserem, obsługą bibliotek, usuwaniem pamięci i optymalizatorem do Wasm oraz w razie potrzeby zaimplementować backend JIT lub AOT dla Wasm. Jest to możliwe od wprowadzenia MVP Wasm i w wielu przypadkach sprawdza się to dobrze. Tak naprawdę WordPress Playground opiera się na PHP skompilowanym na język Wasm. Więcej informacji o projekcie znajdziesz w artykule Tworzenie środowiska WordPress w przeglądarce za pomocą narzędzi 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 wam działają w wersji 8, czyli silniku JavaScript typu open source Google, który implementuje ECMAScript zgodnie z opisem w ECMA-262. Wersja 8 ma już funkcję czyszczenia pamięci. Oznacza to, że deweloperzy korzystający na przykład z języka PHP skompilowanego na potrzeby Wasm wysyłają do przeglądarki, w której już ten moduł ma już funkcję śmiechu, rozwiązanie do segregacji odpadów w wersji środowiska Wasm. Właśnie do tego służy 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. Zezwolenie modułom Wasm na ponowne korzystanie z wbudowanej biblioteki GC pozwala uniknąć tych problemów.

Przenoszenie języków programowania do nowych środowisk wykonawczych za pomocą WasmGC

WasmGC to propozycja grupy społeczności WebAssembly. Obecna implementacja Wasm MVP obsługuje tylko liczby całkowite i zmiennoprzecinkowe w pamięci liniowej. W przypadku oferty typów plików referencyjnych Wasm może dodatkowo opierać się na odwołaniach zewnętrznych. WasmGC dodaje teraz typy sterty typu struct i tablic, co oznacza obsługę nieliniowego 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. Ta propozycja dodaje więc do WebAssembly efektywną obsługę języków zarządzanych wysokiego poziomu za pomocą typów sterty i typów struct i tablic, które umożliwiają kompilatorom języków kierowanym na Wasm integrację z mechanizmem czyszczenia pamięci masowej w hostowej maszynie wirtualnej. W uproszczeniu oznacza to, że w przypadku WasmGC przeniesienie języka programowania do Wasm oznacza, że funkcja zbierania odpadów w języku programowania nie musi już być częścią portu, ale można użyć istniejącego modułu do segregacji odpadów.

Aby sprawdzić rzeczywiste skutki tego ulepszenia, zespół Wasm w Chrome przygotował wersje testu porównawczego Fankuch (w którym działają struktury danych) dla systemów C, Rust i Java. W zależności od różnych flag kompilatora pliki binarne C i Rust mogą mieć od 6.1 K do 9.6 K. Natomiast wersja Java jest znacznie mniejsza i ma jedynie 2, 3 KB. 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ż nie wymaga w ogóle pakowania ż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.

Zobaczenie w działaniu języka programowania przeniesionego przez WasmGC

Wasm Kotlin

Jednym z pierwszych języków programowania, które zostały przeniesione do Wasm dzięki WasmGC, jest Kotlin w postaci Kotlin/Wasm. Na poniższej liście pokazano prezentację z kodem źródłowym przygotowanym przez zespół Kotlin.

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"

Być może zastanawiasz się teraz, o co chodzi, ponieważ powyższy kod Kotlin składa się w zasadzie z interfejsów API JavaScript OM przekonwertowanych na Kotlin. Nabiera sensu w połączeniu z funkcją Compose Multiplatform, która umożliwia programistom wykorzystanie interfejsu użytkownika, który już utworzyli dla swoich aplikacji na Androida Kotlin. Możesz najpierw zapoznać się z tą funkcją w wersji demonstracyjnej Kotlin/Wasm z przeglądarką obrazów, a następnie dzięki uprzejmości zespołu Kotlin zapoznać się z kodem źródłowym.

Rzutki i rzeźby

Zespoły Dart i Flutter w Google również przygotowują pomoc dotyczącą 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. Ta wersja demonstracyjna to wersja testowa WasmGC Flutter WasmGC.

Więcej informacji o WasmGC

Ten post na blogu zawiera bardzo mało informacji i jest głównie ogólnym omówieniem WasmGC. Więcej informacji o tej funkcji znajdziesz na tych stronach:

Podziękowania

Baner powitalny: Gary Chan dostępny na kanale Unsplash. Ten artykuł napisali Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler i Rachel Andrew.