现在,Chrome 会默认启用 WebAssembly 垃圾回收 (WasmGC)

编程语言有两种:一种是垃圾回收的编程语言,一种是需要手动管理内存的编程语言。例如 Kotlin、PHP 或 Java。前者的示例包括 C、C++ 或 Rust。一般来说,较高级别的编程语言更有可能将垃圾回收作为标准功能。在这篇博文中,我们将重点介绍此类垃圾回收编程语言,以及如何将其编译为 WebAssembly (Wasm)。但是,什么是垃圾回收(通常称为 GC)呢?

浏览器支持

  • 119
  • 119
  • 120
  • x

垃圾回收

简单来说,垃圾回收的概念是尝试回收由程序分配但不再引用的内存。此类内存称为垃圾回收。实现垃圾回收的策略有很多。其中一种是“引用计数”,其目标是计算对内存中对象的引用数量。当没有对某个对象的更多引用时,可将其标记为不再使用,从而准备进行垃圾回收。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。但这也意味着,可以针对其他运行时(比如 Wasm)编译 PHP 运行时。

将语言移植到 Wasm 运行时的传统方法

PHP 脚本会独立于运行 PHP 的平台,而会编译成相同的字节码并由 Zend Engine 运行。Zend Engine 是 PHP 脚本语言的编译器和运行时环境。它由 Zend 虚拟机 (VM) 组成,后者由 Zend 编译器和 Zend Executor 组成。PHP 等以其他高级语言(如 C)实现的语言通常针对特定架构(例如 Intel 或 ARM)进行优化,并需要为每种架构使用不同的后端。在这种情况下,Wasm 代表了一种新的架构。如果虚拟机具有特定于架构的代码,如即时 (JIT) 或预先 (AOT) 编译,则开发者还会为新架构实现 JIT/AOT 后端。这种方法很有意义,因为代码库的主要部分通常可以针对每个新架构进行重新编译。

鉴于 Wasm 的低层级,很自然地可以尝试相同的方法:使用其解析器、库支持、垃圾回收和优化器重新编译主虚拟机代码到 Wasm,并根据需要为 Wasm 实现 JIT 或 AOT 后端。自 Wasm MVP 以来,这一功能便成为可能,并且在许多情况下都非常实用。事实上,WordPress Playground 依托 PHP 语言编译到 Wasm 中。如需详细了解该项目,请参阅使用 WordPress Playground 和 WebAssembly 打造浏览器内 WordPress 体验一文。

然而,PHP Wasm 在托管语言 JavaScript 的上下文中在浏览器中运行。在 Chrome 中,JavaScript 和 Wasm 在 V8 中运行,这是 Google 的开源 JavaScript 引擎,按照 ECMA-262 的规定实现 ECMAScript。此外,V8 已有垃圾回收器。这意味着,开发者如果使用通过 Wasm 编译的 PHP 等,最终会将移植语言 (PHP) 的垃圾回收器实现传送到已有垃圾回收器的浏览器,这听起来很浪费资源。这正是 WasmGC 的用武之地。

让 Wasm 模块在 Wasm 的线性内存上构建自己的 GC 的旧方法的另一个问题是,Wasm 自己的垃圾回收器与编译到 Wasm 语言的内置垃圾回收器之间不会进行互动,这往往会导致内存泄漏和收集尝试效率低下等问题。让 Wasm 模块重复使用现有的内置 GC 可以避免这些问题。

使用 WasmGC 将编程语言移植到新的运行时

WasmGC 是 WebAssembly Community Group提案。当前的 Wasm MVP 实现只能处理线性内存中的数字(即整数和浮点数),并且随着引用类型提案的推出,Wasm 还可以保留外部引用。WasmGC 现在添加了结构体和数组堆类型,这意味着支持非线性内存分配。每个 WasmGC 对象都有固定的类型和结构,这使得虚拟机能够轻松生成高效的代码以访问其字段,而没有像 JavaScript 等动态语言那样进行去优化的风险。因此,此方案通过结构体和数组堆类型,向 WebAssembly 添加了对高级托管语言的有效支持,从而使以 Wasm 为目标的语言编译器能够与主机虚拟机中的垃圾回收器集成。简单地说,这意味着使用 WasmGC 将编程语言移植到 Wasm 意味着编程语言的垃圾回收器不再需要是端口的一部分,而是可以使用现有的垃圾回收器。

为了验证这项改进对现实的影响,Chrome 的 Wasm 团队编译了 CRustJavaFannkuch 基准版本(在工作期间分配数据结构)。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 应用创建的界面进行构建。通过 Kotlin/Wasm 图片查看器演示对此进行早期探索,并探索其源代码,同样由 Kotlin 团队提供。

Dart 和 Flutter

Google 的 Dart 和 Flutter 团队也在准备为 WasmGC 提供支持。Dart 到 Wasm 的编译工作即将完成,该团队正致力于提供工具支持,以便提供编译为 WebAssembly 的 Flutter Web 应用。您可以在 Flutter 文档中了解相应工作的当前状态。以下演示为 Flutter WasmGC 预览版

详细了解 WasmGC

这篇博文基本上是对 WasmGC 的简要概述。如需详细了解该功能,请访问以下链接:

致谢

主打图片,创作者:Gary Chan,来源:Unsplash 用户。本文由 Matthias LiedtkeAdam KleinJoshua BellAlon ZakaiJakob KummerowClemens BackesEmanuel ZieglerRachel Andrew 发表。