WasmGC和Wasm尾调用优化

作者:林语者 分类:工程代码

WebAssembly 垃圾回收(WasmGC)

编程语言主要分为两类:一类使用垃圾回收机制,另一类则需要手动内存管理。前者的代表包括 Kotlin、PHP、Java 等语言;后者则包括 C、C++、Rust 等。通常,高级编程语言都会内置垃圾回收功能作为标准特性。

简单来说,垃圾回收是指程序尝试回收那些已分配但不再被引用的内存,这些内存被称为“垃圾”。实现垃圾回收的方法有很多,其中最容易理解的方式之一是引用计数,它通过统计内存中对象的引用数量来管理内存。

由于编程语言往往由其他编程语言实现,这听起来有些“梦中梦”的意味。例如,PHP 运行时主要用 C 语言实现。当开发者将 PHP 等语言编译为 Wasm 时,通常需要将所有组件一并编译,包括语言解析器、库支持以及垃圾回收等关键部分。

Wasm 在浏览器中运行于宿主语言 JavaScript 的上下文中。在 Chrome 中,JavaScript 和 Wasm 都运行于 Google 开源的 JavaScript 引擎 V8。而 V8 本身已经内置了垃圾回收器。这意味着,使用编译为 Wasm 的 PHP 等语言的开发者,会将移植语言的垃圾回收器实现部署到已有垃圾回收器的浏览器中,这无疑造成了不必要的资源浪费。而 WasmGC 正是为此而生的解决方案。

若想进一步了解 WasmGC,请参阅《WebAssembly 垃圾回收(WasmGC)已在 Chrome 中默认启用》。更多技术细节可参考 V8 博客文章《高效移植垃圾回收语言至 WebAssembly 的新方法》

Wasm 尾调用优化

若一个调用是当前函数返回前执行的最后一条指令,则该调用位于“尾位置”。编译器可以通过丢弃调用方栈帧,并将调用替换为跳转指令来优化这类调用。这一点在递归函数中尤其有用。例如,下面是一个计算链表元素和的 C 函数:

int sum(List* list, int acc) {
  if (list == nullptr) return acc;
  return sum(list->next, acc + list->val);
}

在常规调用中,该函数会消耗 O(n) 的栈空间,因为链表的每个元素都会在调用栈上新增一个帧。若链表过长,栈很快就会溢出。通过将调用替换为跳转,尾调用优化可将此递归函数有效转换为仅使用 O(1) 栈空间的循环:

int sum(List* list, int acc) {
  while (list != nullptr) {
    acc = acc + list->val;
    list = list->next;
  }
  return acc;
}

这项优化在函数式语言中尤为重要,因为这类语言严重依赖递归函数,而像 Haskell 这样的纯函数式语言甚至不提供循环控制结构。通常,所有自定义迭代处理都会以某种形式使用递归。若无尾调用优化,即便是稍复杂的程序也很容易因栈空间不足而发生栈溢出。

尽管最初 WebAssembly 未支持此类尾调用优化,但通过尾调用扩展提案现已实现。更多信息可参考 V8 博客文章《WebAssembly 尾调用》

总结

随着 WasmGC 和尾调用优化作为基线功能正式可用,更多应用将可以利用这些特性提升性能。例如,Google Sheets 已将部分计算工作线程移植至 WasmGC,从而显著提升了运行效率。

标签: WebAssembly

评论

发表评论

正在加载评论...