WebAssembly thu gom rác (wasmGC) hiện đã bật theo mặc định trong Chrome

Có hai loại ngôn ngữ lập trình: ngôn ngữ lập trình có tính năng thu gom rác và ngôn ngữ lập trình yêu cầu quản lý bộ nhớ theo cách thủ công. Ví dụ về ngôn ngữ lập trình trước đây (trong số nhiều ngôn ngữ khác) là Kotlin, PHP hoặc Java. Ví dụ về ngôn ngữ thứ hai là C, C++ hoặc Rust. Theo nguyên tắc chung, các ngôn ngữ lập trình cấp cao có nhiều khả năng có tính năng thu gom rác dưới dạng tính năng tiêu chuẩn. Trong bài đăng này trên blog, chúng ta sẽ tập trung vào những ngôn ngữ lập trình có cơ chế thu gom rác như vậy và cách biên dịch chúng sang WebAssembly (Wasm). Nhưng trước hết, thu gom rác (thường được gọi là GC) là gì?

Browser Support

  • Chrome: 119.
  • Edge: 119.
  • Firefox: 120.
  • Safari: 18.2.

Thu gom rác

Nói một cách đơn giản, ý tưởng về việc thu gom rác là nỗ lực lấy lại bộ nhớ đã được chương trình phân bổ nhưng không còn được tham chiếu nữa. Bộ nhớ như vậy được gọi là rác. Có nhiều chiến lược để triển khai quy trình thu gom rác. Một trong số đó là đếm số lượt tham chiếu, trong đó mục tiêu là đếm số lượt tham chiếu đến các đối tượng trong bộ nhớ. Khi không còn tham chiếu nào đến một đối tượng, đối tượng đó có thể được đánh dấu là không còn được sử dụng và do đó đã sẵn sàng cho quá trình thu thập rác. Trình thu gom rác của PHP sử dụng tính năng đếm số lượt tham chiếu và việc sử dụng hàm xdebug_debug_zval() của tiện ích Xdebug cho phép bạn xem xét kỹ hơn. Hãy xem xét chương trình PHP sau đây.

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

Chương trình sẽ gán một số ngẫu nhiên được truyền đến một chuỗi cho một biến mới có tên là a. Sau đó, hàm này sẽ tạo 2 biến mới là bc, đồng thời chỉ định giá trị a cho các biến đó. Sau đó, hàm này gán lại b cho số 42 rồi huỷ đặt c. Cuối cùng, nó đặt giá trị của a thành null. Chú thích từng bước của chương trình bằng xdebug_debug_zval(), bạn có thể thấy bộ đếm tham chiếu của trình thu gom rác đang hoạt động.

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

Ví dụ trên sẽ xuất ra các nhật ký sau đây, trong đó bạn sẽ thấy số lượng lượt tham chiếu đến giá trị của biến a giảm sau mỗi bước, điều này hợp lý trong chuỗi mã. (Tất nhiên, số ngẫu nhiên của bạn sẽ khác.)

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

Có những thách thức khác đối với việc thu gom rác, chẳng hạn như phát hiện các chu kỳ, nhưng đối với bài viết này, chỉ cần hiểu biết cơ bản về việc đếm số lượt tham chiếu là đủ.

Ngôn ngữ lập trình được triển khai bằng các ngôn ngữ lập trình khác

Có thể bạn sẽ cảm thấy như đang ở trong một giấc mơ, nhưng các ngôn ngữ lập trình được triển khai bằng các ngôn ngữ lập trình khác. Ví dụ: thời gian chạy PHP chủ yếu được triển khai bằng ngôn ngữ C. Bạn có thể xem mã nguồn PHP trên GitHub. Mã thu gom rác của PHP chủ yếu nằm trong tệp zend_gc.c. Hầu hết nhà phát triển sẽ cài đặt PHP thông qua trình quản lý gói của hệ điều hành. Nhưng nhà phát triển cũng có thể tạo PHP từ mã nguồn. Ví dụ: trong môi trường Linux, các bước ./buildconf && ./configure && make sẽ tạo PHP cho thời gian chạy Linux. Nhưng điều này cũng có nghĩa là thời gian chạy PHP có thể được biên dịch cho các thời gian chạy khác, chẳng hạn như Wasm.

Các phương pháp truyền thống để chuyển ngôn ngữ sang thời gian chạy Wasm

Bất kể nền tảng mà PHP đang chạy, các tập lệnh PHP đều được biên dịch thành cùng một mã byte và chạy bằng Zend Engine. Zend Engine là một trình biên dịch và môi trường thời gian chạy cho ngôn ngữ tập lệnh PHP. Nó bao gồm Máy ảo (VM) Zend, được tạo thành từ Trình biên dịch Zend và Trình thực thi Zend. Các ngôn ngữ như PHP được triển khai bằng các ngôn ngữ cấp cao khác như C thường có các hoạt động tối ưu hoá nhắm đến các kiến trúc cụ thể, chẳng hạn như Intel hoặc ARM, và yêu cầu một phần phụ trợ khác cho mỗi kiến trúc. Trong bối cảnh này, Wasm đại diện cho một cấu trúc mới. Nếu VM có mã dành riêng cho cấu trúc (chẳng hạn như biên dịch đúng lúc (JIT) hoặc biên dịch trước khi thực thi (AOT)), thì nhà phát triển cũng sẽ triển khai một phần phụ trợ cho JIT/AOT cho cấu trúc mới. Phương pháp này rất hợp lý vì thường thì phần chính của cơ sở mã chỉ cần được biên dịch lại cho mỗi cấu trúc mới.

Vì Wasm có cấp độ thấp, nên việc thử cùng một phương pháp ở đó là điều tự nhiên: Biên dịch lại mã VM chính bằng trình phân tích cú pháp, hỗ trợ thư viện, thu gom rác và trình tối ưu hoá thành Wasm, đồng thời triển khai một phần phụ trợ JIT hoặc AOT cho Wasm nếu cần. Điều này có thể thực hiện được kể từ MVP Wasm và hoạt động hiệu quả trong thực tế ở nhiều trường hợp. Trên thực tế, PHP được biên dịch sang Wasm là nền tảng của WordPress Playground. Tìm hiểu thêm về dự án này trong bài viết Xây dựng trải nghiệm WordPress trong trình duyệt bằng WordPress Playground và WebAssembly.

Tuy nhiên, PHP Wasm chạy trong trình duyệt trong ngữ cảnh của ngôn ngữ máy chủ JavaScript. Trong Chrome, JavaScript và Wasm được chạy trong V8, công cụ JavaScript nguồn mở của Google triển khai ECMAScript như được chỉ định trong ECMA-262. Ngoài ra, V8 đã có một trình thu gom rác. Điều này có nghĩa là các nhà phát triển sử dụng, chẳng hạn như PHP được biên dịch sang Wasm, sẽ kết thúc việc triển khai một trình thu gom rác của ngôn ngữ được chuyển (PHP) sang trình duyệt đã có trình thu gom rác, điều này lãng phí như bạn nghĩ. Đây là lúc WasmGC phát huy tác dụng.

Vấn đề khác của phương pháp cũ là cho phép các mô-đun Wasm tạo GC riêng trên bộ nhớ tuyến tính của Wasm. Khi đó, sẽ không có sự tương tác giữa trình thu gom rác riêng của Wasm và trình thu gom rác được tạo trên ngôn ngữ được biên dịch sang Wasm. Điều này có xu hướng gây ra các vấn đề như rò rỉ bộ nhớ và các lần thu gom không hiệu quả. Việc cho phép các mô-đun Wasm sử dụng lại GC tích hợp sẵn hiện có sẽ tránh được những vấn đề này.

Chuyển các ngôn ngữ lập trình sang thời gian chạy mới bằng WasmGC

WasmGC là một đề xuất của Nhóm cộng đồng WebAssembly. Việc triển khai MVP Wasm hiện tại chỉ có thể xử lý các số (tức là số nguyên và số thực) trong bộ nhớ tuyến tính. Với đề xuất các loại tham chiếu được gửi đi, Wasm có thể giữ thêm các tham chiếu bên ngoài. WasmGC hiện thêm các loại heap cấu trúc và mảng, tức là hỗ trợ việc phân bổ bộ nhớ không tuyến tính. Mỗi đối tượng WasmGC đều có một kiểu và cấu trúc cố định, giúp các VM dễ dàng tạo mã hiệu quả để truy cập vào các trường của chúng mà không gặp phải nguy cơ giảm hiệu suất như các ngôn ngữ động (chẳng hạn như JavaScript). Do đó, đề xuất này bổ sung khả năng hỗ trợ hiệu quả cho các ngôn ngữ được quản lý cấp cao cho WebAssembly, thông qua các loại heap cấu trúc và mảng cho phép các trình biên dịch ngôn ngữ nhắm đến Wasm tích hợp với một trình thu gom rác trong VM máy chủ. Nói một cách đơn giản, điều này có nghĩa là với WasmGC, việc chuyển một ngôn ngữ lập trình sang Wasm không còn cần đến bộ thu gom rác của ngôn ngữ lập trình đó nữa, mà thay vào đó, bạn có thể sử dụng bộ thu gom rác hiện có.

Để xác minh tác động thực tế của việc cải thiện này, nhóm Wasm của Chrome đã biên dịch các phiên bản của điểm chuẩn Fannkuch (phân bổ cấu trúc dữ liệu khi hoạt động) từ C, RustJava. Các tệp nhị phân C và Rust có thể có kích thước từ 6,1 K đến 9,6 K tuỳ thuộc vào nhiều cờ trình biên dịch, trong khi phiên bản Java có kích thước nhỏ hơn nhiều, chỉ 2,3 K! C và Rust không có trình thu gom rác, nhưng chúng vẫn kết hợp malloc/free để quản lý bộ nhớ. Lý do Java có kích thước nhỏ hơn ở đây là vì Java không cần kết hợp bất kỳ mã quản lý bộ nhớ nào. Đây chỉ là một ví dụ cụ thể, nhưng cho thấy rằng các tệp nhị phân WasmGC có khả năng rất nhỏ, và điều này xảy ra ngay cả trước khi có bất kỳ nỗ lực đáng kể nào để tối ưu hoá kích thước.

Xem ngôn ngữ lập trình được chuyển sang WasmGC đang hoạt động

Kotlin Wasm

Một trong những ngôn ngữ lập trình đầu tiên được chuyển sang Wasm nhờ WasmGC là Kotlin dưới dạng Kotlin/Wasm. Bản minh hoạmã nguồn do nhóm Kotlin cung cấp được trình bày trong danh sách sau.

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"

Có thể bạn đang thắc mắc mục đích của việc này là gì, vì mã Kotlin ở trên về cơ bản bao gồm các API OM JavaScript được chuyển đổi sang Kotlin. Điều này sẽ hợp lý hơn khi kết hợp với Compose Multiplatform. Nền tảng này cho phép nhà phát triển xây dựng dựa trên giao diện người dùng mà họ có thể đã tạo cho các ứng dụng Android Kotlin. Hãy xem bản khám phá ban đầu về vấn đề này bằng trình xem hình ảnh Kotlin/Wasm, cũng do nhóm Kotlin cung cấp.

Bản minh hoạ trình xem hình ảnh Kotlin/Wasm.

Dart và Flutter

Các nhóm Dart và Flutter tại Google cũng đang chuẩn bị hỗ trợ WasmGC. Công việc biên dịch Dart sang Wasm gần như đã hoàn tất và nhóm đang nỗ lực hỗ trợ công cụ để phân phối các ứng dụng web Flutter được biên dịch sang WebAssembly. Bạn có thể đọc về trạng thái hiện tại của công việc trong tài liệu Flutter. Bản minh hoạ sau đây là Bản xem trước Flutter WasmGC.

Tìm hiểu thêm về WasmGC

Bài đăng này trên blog chỉ mới đề cập đến những thông tin cơ bản và chủ yếu cung cấp thông tin tổng quan cấp cao về WasmGC. Để tìm hiểu thêm về tính năng này, hãy xem các đường liên kết sau:

Lời cảm ơn

Bài viết này được đánh giá bởi Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel ZieglerRachel Andrew.