Istnieją 2 rodzaje języków programowania: języki z automatycznym odśmiecaniem pamięci i języki, które wymagają ręcznego zarządzania pamięcią. Przykłady to m.in. Kotlin, PHP i Java. Przykładami takich języków są C, C++ i Rust. Ogólnie rzecz biorąc, języki programowania wyższego poziomu częściej mają odśmiecanie jako standardową funkcję. W tym poście na blogu skupimy się na językach programowania z odśmiecaniem pamięci i na tym, jak można je skompilować do WebAssembly (Wasm). Czym w ogóle jest odśmiecanie pamięci (często określane jako GC)?
Browser Support
czyszczenie pamięci,
W uproszczeniu odśmiecanie polega na próbie odzyskania pamięci przydzielonej przez program, ale już nieużywanej. Takie miejsce w pamięci nazywa się śmieciami. Istnieje wiele strategii implementacji odśmiecania. Jedną z nich jest zliczanie odwołań, którego celem jest zliczanie liczby odwołań do obiektów w pamięci. Gdy nie ma już odwołań do obiektu, można go oznaczyć jako nieużywany, a tym samym gotowy do odzyskania pamięci. Mechanizm odśmiecania PHP korzysta z liczenia odwołań, a użycie funkcji xdebug_debug_zval()
rozszerzenia Xdebug pozwala zajrzeć pod jego maskę. Rozważmy ten program w PHP.
<?php
$a= (string) rand();
$c = $b = $a;
$b = 42;
unset($c);
$a = null;
?>
Program przypisuje losową liczbę przekształconą w ciąg znaków do nowej zmiennej o nazwie a
. Następnie tworzy 2 nowe zmienne: b
i c
, i przypisuje im wartość a
. Następnie przypisuje b
do numeru 42
i usuwa ustawienie c
. Na koniec ustawia wartość parametru a
na null
. Dodając adnotacje do każdego kroku programu za pomocą symbolu xdebug_debug_zval()
, możesz zobaczyć, jak działa licznik odwołań modułu odśmiecania.
<?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 powyższym przykładzie zostaną wygenerowane te logi. Widać w nich, jak po każdym kroku zmniejsza się liczba odwołań do wartości zmiennej a
, co jest logiczne w kontekście sekwencji kodu. (Twój numer losowy 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ą inne wyzwania związane z odśmiecaniem pamięci, takie jak wykrywanie cykli, ale na potrzeby tego artykułu wystarczy podstawowa wiedza o zliczaniu odwołań.
Języki programowania są implementowane w innych językach programowania
Może się to wydawać skomplikowane, ale języki programowania są implementowane w innych językach programowania. Na przykład środowisko wykonawcze PHP jest zaimplementowane głównie w języku C. Możesz sprawdzić kod źródłowy PHP na GitHubie. Kod czyszczenia pamięci w PHP znajduje się głównie w pliku zend_gc.c
. Większość deweloperów instaluje PHP za pomocą menedżera pakietów systemu operacyjnego. Deweloperzy mogą też skompilować PHP z kodu źródłowego. Na przykład w środowisku Linux kroki ./buildconf && ./configure && make
utworzą PHP dla środowiska wykonawczego Linux. Oznacza to jednak również, że środowisko wykonawcze PHP można skompilować dla 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 wykonawcze dla języka skryptowego PHP. Składa się z maszyny wirtualnej Zend (VM), która zawiera kompilator Zend i wykonawcę Zend. Języki takie jak PHP, które są implementowane w innych językach wysokiego poziomu, np. C, mają zwykle optymalizacje ukierunkowane na konkretne architektury, takie jak Intel lub ARM, i wymagają innego backendu dla każdej architektury. W tym kontekście Wasm reprezentuje nową architekturę. Jeśli maszyna wirtualna zawiera kod specyficzny dla architektury, np. kompilację JIT (just-in-time) lub AOT (ahead-of-time), deweloper implementuje też backend JIT/AOT dla nowej architektury. Takie podejście ma sens, ponieważ główną część kodu można po prostu ponownie skompilować dla każdej nowej architektury.
Ze względu na niski poziom Wasm naturalne jest zastosowanie tego samego podejścia: zrekompiluj główny kod maszyny wirtualnej z parserem, obsługą bibliotek, odśmiecaniem pamięci i optymalizatorem do Wasm, a w razie potrzeby zaimplementuj backend JIT lub AOT dla Wasm. Jest to możliwe od czasu Wasm MVP i w wielu przypadkach sprawdza się w praktyce. W rzeczywistości PHP skompilowany do Wasm jest tym, co napędza WordPress Playground. Więcej informacji o projekcie znajdziesz w artykule Build in-browser WordPress experiences with WordPress Playground and WebAssembly (Tworzenie w przeglądarce środowisk WordPress za pomocą WordPress Playground i WebAssembly).
PHP Wasm działa jednak w przeglądarce w kontekście języka hosta, czyli JavaScriptu. W Chrome JavaScript i Wasm są uruchamiane w V8, czyli w mechanizmie JavaScript o otwartym kodzie źródłowym, który implementuje ECMAScript zgodnie ze specyfikacją ECMA-262. V8 ma już moduł odśmiecania pamięci. Oznacza to, że deweloperzy korzystający na przykład z PHP skompilowanego do Wasm wysyłają do przeglądarki implementację modułu odśmiecania przeniesionego języka (PHP), która już ma moduł odśmiecania. Jest to tak nieefektywne, jak się wydaje. Właśnie tutaj do akcji wkracza WasmGC.
Innym problemem związanym ze starym podejściem, które polega na tym, że moduły Wasm tworzą własny mechanizm odśmiecania na podstawie pamięci liniowej Wasm, jest brak interakcji między mechanizmem odśmiecania Wasm a mechanizmem odśmiecania języka skompilowanego do Wasm, co zwykle powoduje problemy, takie jak wycieki pamięci i nieefektywne próby odśmiecania. Umożliwienie modułom Wasm ponownego używania wbudowanego mechanizmu 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. Po wprowadzeniu propozycji typów referencyjnych Wasm będzie też mógł przechowywać odwołania zewnętrzne. WasmGC dodaje teraz typy sterty struktur i tablic, 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 dostępu do jego pól bez ryzyka deoptymalizacji, które występuje w przypadku języków dynamicznych, takich jak JavaScript. Dzięki temu w WebAssembly dodano wydajną obsługę języków zarządzanych wysokiego poziomu za pomocą typów sterty struktur i tablic, które umożliwiają kompilatorom języków docelowych Wasm integrację z mechanizmem odśmiecania w maszynie wirtualnej hosta. W uproszczeniu oznacza to, że w przypadku WasmGC przeniesienie języka programowania do Wasm nie wymaga już uwzględniania w nim modułu odśmiecania, ponieważ można użyć istniejącego modułu odśmiecania.
Aby sprawdzić, jak ta zmiana wpłynie na rzeczywiste działanie, zespół Chrome ds. Wasm skompilował wersje testu porównawczego Fannkuch (który podczas działania przydziela struktury danych) w językach C, Rust i Java. Pliki binarne w językach C i Rust mogą mieć od 6,1 KB do 9,6 KB w zależności od różnych flag kompilatora, a wersja w języku Java jest znacznie mniejsza i ma tylko 2,3 KB. Języki C i Rust nie mają modułu odśmiecania pamięci, ale zawierają malloc/free
do zarządzania pamięcią. Java jest mniejsza, ponieważ nie musi zawierać żadnego kodu do zarządzania pamięcią. To tylko jeden konkretny przykład, ale pokazuje, że pliki binarne WasmGC mogą być bardzo małe, a to jeszcze przed podjęciem jakichkolwiek znaczących działań w zakresie optymalizacji rozmiaru.
Zobacz, jak działa język programowania przeniesiony do WasmGC
Kotlin Wasm
Jednym z pierwszych języków programowania przeniesionych do Wasm dzięki WasmGC jest Kotlin w postaci Kotlin/Wasm. Poniżej znajdziesz wersję demonstracyjną z kodem źródłowym udostępnionym 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"
Możesz się teraz zastanawiać, jaki jest sens tego rozwiązania, ponieważ powyższy kod Kotlin w zasadzie składa się z interfejsów JavaScript OM API przekonwertowanych na Kotlin. Ma to większy sens w połączeniu z Compose Multiplatform, które umożliwia deweloperom rozwijanie interfejsu, który mogli już utworzyć dla aplikacji na Androida w języku Kotlin. Wczesną wersję tej funkcji możesz sprawdzić w przeglądarce obrazów Kotlin/Wasm, która również została stworzona przez zespół Kotlin.
Dart i Flutter
Zespoły Dart i Flutter w Google również przygotowują obsługę WasmGC. Prace nad kompilacją z Darta do WebAssembly są już prawie ukończone, a zespół pracuje nad narzędziami do dostarczania aplikacji internetowych Fluttera skompilowanych do WebAssembly. Informacje o bieżącym stanie prac znajdziesz w dokumentacji Fluttera. Poniższa wersja demonstracyjna to Flutter WasmGC Preview.
Więcej informacji o WasmGC
Ten post na blogu zaledwie zarysował temat i zawiera głównie ogólne omówienie WasmGC. Więcej informacji o tej funkcji znajdziesz pod tymi linkami:
- Nowy sposób wydajnego przenoszenia języków programowania z odśmiecaniem do WebAssembly
- Omówienie WasmGC
- WasmGC MVP
- WasmGC po MVP
Podziękowania
Ten artykuł został sprawdzony przez Matthiasa Liedtke, Adama Kleina, Joshuę Bella, Alona Zakai, Jakoba Kummerowa, Clemensa Backesa, Emanuela Zieglera i Rachel Andrew.