Chrome 現已預設啟用 WebAssembly 垃圾收集 (WasmGC)

程式設計語言分為兩種:垃圾收集的程式設計語言,以及需要手動管理記憶體的程式設計語言。其範例包括 Kotlin、PHP 或 Java。後者的範例為 C、C++ 或 Rust。一般來說,高階程式設計語言中較有可能將垃圾收集當做標準功能。這篇網誌文章著重於這類垃圾收集的程式設計語言,以及如何編譯到 WebAssembly (Wasm) 語言。不過,從垃圾收集 (通常稱為 GC) 開始是什麼?

瀏覽器支援

  • Chrome:119。
  • Edge:119。
  • Firefox:120。
  • Safari:不支援。

垃圾收集

簡單來說,垃圾收集的概念是嘗試收回由程式分配,但不再參照的記憶體。這類記憶體稱為「垃圾」。實作垃圾收集的策略有很多。其中一種是「參照計數」,目標是計算記憶體中物件的參照數量。如果物件沒有任何參照,系統可能會標示為不再使用,以便進行垃圾收集。PHP 的垃圾收集器採用參照計算,而使用 Xdebug 擴充功能的 xdebug_debug_zval() 函式可讓您迅速瞭解實際情況。請考慮以下 PHP 程式。

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

程式會為名為 a 的新變數指派隨機數字,這些數字會轉換為字串。然後它會建立 bc 兩個新變數,並為它們指派 a 的值。之後,系統會將 b 重新指派給數字 42,然後取消設定 c。最後,它會將 a 的值設為 null。使用 xdebug_debug_zval() 為程式的每個步驟加上註解,您就能查看垃圾收集器在運作中的參考計數器。

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

上述範例會輸出下列記錄,讓您瞭解在完成各項步驟後,變數 a 值的參照數量如何減少。視程式碼序列而定,這很合理。(隨機數字不同)。

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
敬上

垃圾收集功能還有其他挑戰,例如偵測週期,但本文僅說明對參照計數有基本的瞭解。

使用其他程式設計語言實作程式設計語言

這感覺似乎像是新手,但程式設計語言卻是以其他程式設計語言實作。舉例來說,PHP 執行階段主要在 C 語言中實作。您可以前往 GitHub 查看 PHP 原始碼。PHP 的垃圾收集程式碼主要位於 zend_gc.c 檔案中。大部分開發人員會透過其作業系統的套件管理員來安裝 PHP。不過,開發人員也可以從原始碼建構 PHP。例如,在 Linux 環境中,./buildconf && ./configure && make 的步驟會建構適用於 Linux 執行階段的 PHP。但這也表示,您可以對其他執行階段進行 PHP 執行階段編譯,您猜到的是 Wasm。

將語言移植至 Wasm 執行階段的傳統方法

與執行 PHP 的平台不同,PHP 指令碼會編譯為相同的位元碼,並由 Zend Engine 執行。Zend Engine 是 PHP 指令碼語言的編譯器和執行階段環境,這個模式包含 Zend 虛擬機器 (VM),以及由 Zend 編譯器和 Zend Executor 所組成的以其他高階語言 (例如 C) 實作的 PHP 之類的語言,通常會針對特定架構 (例如 Intel 或 ARM) 進行最佳化,並針對每個架構需要不同的後端。在這種情況下,Wasm 代表新的架構。如果 VM 有架構專屬程式碼,例如及時 (JIT) 或預先 (AOT) 編譯,開發人員也會為新架構實作 JIT/AOT 的後端。這種方法很合理,因為通常只要針對每個新架構重新編譯程式碼集的主要部分即可。

鑒於低階 Wasm 的運作情況,自然會採取相同的做法:使用剖析器、程式庫支援、垃圾收集和最佳化工具,重新編譯主要 VM 程式碼,並視需要為 Wasm 實作 JIT 或 AOT 後端。自 Wasm MVP 以來,此做法已成為可能的成果,在許多情況下都能發揮效用。事實上,編譯至 Wasm 的 PHPWordPress Playground 的核心。如要進一步瞭解專案,請參閱「使用 WordPress Playground 和 WebAssembly 打造瀏覽器內的 WordPress 體驗」。

不過,PHP Wasm 在瀏覽器中於主機語言 JavaScript 環境中執行。在 Chrome 中,JavaScript 和 Wasm 在 V8 中執行;這是 Google 的開放原始碼 JavaScript 引擎,實作 ECMAScript,如 ECMA-262 所述。而 V8 已有垃圾收集器。這意味著開發人員使用以將 PHP 編譯為 Wasm 的 PHP 程式碼後,最終會把移植語言 (PHP) 的垃圾收集器推送到已裝有垃圾收集器的瀏覽器上,這跟垃圾收集器一樣浪費。這時 WasmGC 就能派上用場。

舊方法讓 Wasm 模組除了使用 Wasm 線性記憶體外,還建構自己的 GC,另一個問題是 Wasm 自身的垃圾收集器與編譯至 Wasm 語言的內建垃圾收集器之間不會產生互動,這往往會導致記憶體流失,以及收集效率不彰的問題。讓 Wasm 模組重複使用現有的內建 GC,可避免這些問題。

使用 WasmGC 將程式設計語言移植到新的執行階段

WasmGC 是 WebAssembly 社群團體提案。目前的 Wasm MVP 實作只能處理數字 (也就是線性記憶體中的整數和浮點值,以及出貨的參照類型提案),Wasm 也可以額外保留外部參照。WasmGC 現已新增結構體和陣列堆積類型,也就是支援非線性記憶體配置。每個 WasmGC 物件都有固定的類型和結構,可讓 VM 輕鬆產生高效程式碼來存取欄位,而不會有 JavaScript 等動態語言產生的去解風險。因此,本提案透過結構體和陣列堆積類型,為 WebAssembly 增添了針對高階代管語言的有效支援,可讓指定 Wasm 的語言編譯器與主機 VM 中的垃圾收集器整合。簡單來說,這代表使用 WasmGC 將程式設計語言移植至 Wasm 後,該程式設計語言的垃圾收集器不再需要做為充電座的一部分,但可以使用現有的垃圾收集器。

為驗證這項改善項目的實際影響,Chrome 的 Wasm 團隊彙整了 Fannkuch 基準 (這項工具會按運作時分配資料結構) 的 CRustJava 版本。C 和 Rust 二進位檔的可能介於 6.1 K9.6 K 之間,視不同的編譯器標記而定,而 Java 版本則小得比 2.3 K 小很多!C 和 Rust 並未包含垃圾收集器,但仍會封裝 malloc/free 來管理記憶體,而 Java 的範圍較小,是因為完全不需封裝任何記憶體管理程式碼。這只是一個具體範例,但顯示 WasmGC 二進位檔的潛力極小,甚至在針對大小進行最佳化調整之前,先完成這些作業。

實際查看 WasmGC 移植的程式設計語言

Kotlin Wasm

WasmGC 首次移植到 Wasm 的程式設計語言之一,就是採用 Kotlin/Wasm 形式的 Kotlin。以下內容顯示由 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"

現在,您可能想知道到底是什麼,因為上方的 Kotlin 程式碼基本上是由轉換為 Kotlin 的 JavaScript OM API 組成。我們開始將這項技術與 Compose Multiplatform 搭配使用,讓開發人員能夠以先前為 Android Kotlin 應用程式建立的 UI 進行建構。透過 Kotlin/Wasm 圖片檢視器示範影片,搶先體驗並探索原始碼,同樣由 Kotlin 團隊提供。

Dart 和 Flutter

Google 的 Dart 和 Flutter 團隊也正在準備支援 WasmGC。Dart-to-Wasm 編譯作業即將完成,團隊也正在努力開發工具支援,以便提供編譯為 WebAssembly 的 Flutter 網頁應用程式。如要瞭解工作目前的狀態,請參閱 Flutter 說明文件。以下是 Flutter WasmGC 預先發布版

進一步瞭解 WasmGC

這篇網誌文章並未完全刮傷表面,大部分都是 WasmGC 的概略介紹。如要進一步瞭解這項功能,請參閱下列連結:

特別銘謝

主頁橫幅由 Gary Chan 提供,來源為 Unsplash 網站上。本文評論者為 Matthias LiedtkeAdam KleinJoshua BellAlon ZakaiJakob KummerowClemens BackesEmanuel ZieglerRachel Andrew