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

Istnieją 2 typy języków programowania: języki programowania pobrane z wykorzystaniem pamięci masowej i języki programowania wymagające ręcznego zarządzania pamięcią. Przykłady z pierwszej z nich to Kotlin, PHP lub Java. Przykłady tych ostatnich to C, C++ lub Rust. Ogólnie w przypadku języków programowania wyższego poziomu prawdopodobieństwo, że funkcja odśmiecania będzie działać w ramach standardowej funkcji, ma większe szanse. W tym poście skupimy się na językach programowania zbierających niepotrzebne dane i o tym, jak można je kompilować do WebAssembly (Wasm). Czym jednak na początku jest zbieranie odpadów (tzw. czyszczenie pamięci)?

Obsługa przeglądarek

  • 119
  • 119
  • 120
  • x

Wywóz śmieci

Mówiąc prościej, pojęcie czyszczenia pamięci to próba odzyskania pamięci przydzielonej przez program, ale już się do niej nie odnosi. Jest to tzw. czyszczenie pamięci. Istnieje wiele strategii czyszczenia pamięci. Jeden z nich to liczenie plików referencyjnych, w ramach którego celem jest zliczanie odniesień do obiektów w pamięci. Gdy nie ma więcej odwołań do obiektu, można go oznaczyć jako nieużywany i dzięki temu gotowy do czyszczenia pamięci. Moduł na odpady PHP używa liczenia odwołań, a funkcja xdebug_debug_zval() rozszerzenia Xdebug pozwala zajrzeć pod jego maskę. Przyjrzyjmy się następującemu programowi w języku PHP.

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

Program przypisuje do nowej zmiennej o nazwie a liczbę losową, która jest przypisana do ciągu znaków. Następnie tworzy 2 nowe zmienne b i c oraz przypisuje do nich wartość a. Następnie przypisuje wartość b do numeru 42, a następnie anuluje ustawienie c. Na koniec ustawia wartość a na null. Jeśli dodasz do każdego kroku w programie adnotację xdebug_debug_zval(), możesz zobaczyć działający licznik odpadów selektora śmieci.

<?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 logi, gdzie widać, jak liczba odwołań do wartości zmiennej a zmniejsza się po każdym kroku, co ma sens w związku z sekwencją kodu. (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

Istnieją też inne wyzwania związane z odbieraniem odpadów, takie jak wykrywanie cykli, ale w tym artykule wystarczy podstawowy poziom wiedzy o zliczaniu plików referencyjnych.

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

Może się to wydawać, że się wydaje, 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 na GitHubie znajdziesz na stronie GitHub. Kod PHP do odśmiecania danych znajduje się głównie w pliku zend_gc.c. Większość programistów instaluje PHP za pomocą menedżera pakietów systemu operacyjnego. Jednak deweloperzy mogą też tworzyć PHP na podstawie kodu źródłowego. Na przykład w środowisku Linux kroki ./buildconf && ./configure && make spowodują utworzenie języka PHP na potrzeby środowiska wykonawczego Linuxa. Oznacza to jednak również, że można kompilować środowisko wykonawcze PHP do innych środowisk wykonawczych, np. 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 w ten sam kod bajtowy i uruchamiane przez Zend Engine. Zend Engine to kompilator i środowisko wykonawcze dla języka skryptów 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 zaimplementowane w innych językach najwyższego poziomu (np. C) zwykle mają optymalizacje pod kątem określonych architektur, takich jak Intel czy ARM, i wymagają osobnego backendu dla każdej z nich. W tym kontekście Wasm reprezentuje nową architekturę. Jeśli maszyna wirtualna ma kod związany z konkretną architekturą, na przykład kompilację just-in-time (JIT) lub wyprzedzoną (AOT), deweloper zaimplementuje również backend dla JIT/AOT dla nowej architektury. Takie podejście ma sens, ponieważ często wystarczy skompilować główną część bazy kodu pod kątem każdej nowej architektury.

Biorąc pod uwagę, jak niskopoziomowy jest Wasm, naturalne jest zastosowanie tego samego podejścia: ponownie skompiluj główny kod maszyny wirtualnej z aneksem, obsługą biblioteki, czyszczeniem pamięci i optymalizatorem do Wasm, a w razie potrzeby zaimplementuj backend JIT lub AOT dla Wasm. Jest to możliwe od wprowadzenia minimalnej wersji systemu Wasm (MVP) i w wielu przypadkach sprawdza się to w praktyce. WordPress Playground to źródło skompilowanego pliku PHP do Wasm. Więcej informacji o projekcie znajdziesz w artykule Tworzenie środowiska WordPress w przeglądarce za pomocą narzędzi WordPress Playground i WebAssembly.

Jednak program PHP Wasm uruchamia się w przeglądarce w kontekście JavaScriptu języka hosta. W Chrome JavaScript i Wasm są uruchamiane w V8, czyli opracowany przez Google mechanizm JavaScript typu open source, który implementuje ECMAScript zgodnie ze standardem ECMA-262. V8 ma już moduł śmieci. Oznacza to, że deweloperzy używający np. języka PHP skompilowanego na serwer Wasm wysyłają do przeglądarki, w której już istnieje ten mechanizm, co jest śmieciowe, jak mogłoby się wydawać. W tym miejscu do akcji wkracza WasmGC.

Innym problemem, który polegał na tym, że zezwalanie modułom Wasm na tworzenie własnych GC na bazie liniowej pamięci Wasm, jest brak interakcji między własnym kolektora śmieci Wasm a wbudowanym kolekcjonerem śmieci skompilowanego w Wasm, co powoduje problemy takie jak wycieki pamięci i niewydajne próby zbierania danych. Umożliwienie modułom Wasm ponownego wykorzystania istniejących wbudowanych 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, czyli liczby całkowite i zmiennoprzecinkowe, w pamięci liniowej. W ramach wysyłanej oferty typów referencyjnych Wasm może dodatkowo przechowywać odniesienia zewnętrzne. WasmGC dodaje teraz typy sterty typu struct i tablice, co oznacza, że obsługuje nieliniową alokację pamięci. Każdy obiekt WasmGC ma stały typ i strukturę, co ułatwia maszynom wirtualnym generowanie wydajnego kodu zapewniającego dostęp do pól bez ryzyka deoptymalizacji stosowanej w językach dynamicznych takich jak JavaScript. Ta oferta dodaje do WebAssembly efektywną obsługę języków zarządzanych wysokiego poziomu za pomocą typów sterty typu struct i tablic, które umożliwiają kompilatorom języka kierowanym na Wasm integrację z kolektorem czyszczenia pamięci w głównej maszynie wirtualnej. W uproszczeniu oznacza to, że w przypadku WasmGC przeniesienie języka programowania do Wasm oznacza, że kolekcjoner śmieci języka programowania nie musi już być częścią portu, ale zamiast tego można użyć istniejącego już odczyszczania.

Aby zweryfikować rzeczywisty wpływ tego ulepszenia, zespół Wasm Chrome skompilował wersje testu porównawczego Fannkucha (w którym następuje przydzielanie struktur danych w miarę ich działania) z środowisk C, Rust i Java. Pliki binarne C i Rust mogą mieć od 6,1 K do 9,6 K w zależności od różnych flag kompilatora. Z kolei wersja Java jest znacznie mniejsza i wynosi tylko 2,3 K. Wersje C i Rust nie zawierają modułu zbierania odpadów, ale i tak tworzą pakiet malloc/free w celu zarządzania pamięcią. Java jest w tym przypadku mniejsza, ponieważ nie musi w ogóle dodawać żadnego kodu zarządzania pamięcią. To tylko jeden konkretny przykład, ale pokazuje, że pliki binarne WasmGC mogą być bardzo małe i to jeszcze przed istotnymi działaniami w zakresie optymalizacji pod kątem rozmiaru.

Język programowania obsługiwany przez WasmGC w praktyce

Wasm Kotlin

Jednym z pierwszych języków programowania, które zostały przeniesione do Wasm dzięki WasmGC, jest Kotlin w formie Kotlin/Wasm. Prezentacja z kodem źródłowym udostępnionym przez zespół Kotlin znajduje się poniżej.

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"

Powyższy kod Kotlin w zasadzie składa się z JavaScript OM API przekonwertowanych na Kotlin, więc pewnie zastanawiasz się, o co chodzi. Nabiera to bardziej sensu w połączeniu z funkcją Compose Multi Platform, która pozwala deweloperom wykorzystywać interfejs, który mogli już utworzyć dla swoich aplikacji Kotlin na Androida. Zapoznaj się z wcześniejszą wersją demonstracyjną przeglądarki obrazów Kotlin/Wasm i poznaj jej kod źródłowy, podobnie jak dzięki uprzejmości zespołu Kotlin.

Rzutki i fale

Zespoły Dart i Flutter w Google przygotowują się również do obsługi klienta WasmGC. Prace nad kompilacją Dart-to-Wasm są prawie ukończone, dlatego nasz zespół pracuje nad obsługą narzędzi do dostarczania aplikacji internetowych Flutter skompilowanych w WebAssembly. Informacje o bieżącym stanie pracy znajdziesz w dokumentacji Flutter. Poniższa wersja demonstracyjna to wersja testowa Flutter WasmGC.

Więcej informacji o WasmGC

Ten post jest tylko ogólnym omówieniem tematu WasmGC. Więcej informacji o tej funkcji znajdziesz pod tymi linkami:

Podziękowania

Baner powitalny od Gary'ego Chana na kanale Unsplash. Ten artykuł napisali Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler i Rachel Andrew.