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 thu thập 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ề Kotlin, PHP hoặc Java cùng nhiều ngôn ngữ khác. Ví dụ về ngôn ngữ thứ hai là C, C++ hoặc Rust. Theo quy tắc chung, các ngôn ngữ lập trình cấp cao hơn có nhiều khả năng lấy dữ liệu rác làm tính năng tiêu chuẩn. Trong bài đăng trên blog này, chủ đề sẽ tập trung vào các ngôn ngữ lập trình được thu thập rác như vậy và cách biên dịch chúng thành WebAssembly (Wasm). Nhưng thu gom rác (thường được gọi là GC) để bắt đầu là gì?

Hỗ trợ trình duyệt

  • Chrome: 119.
  • Cạnh: 119.
  • Firefox: 120.
  • Safari: không được hỗ trợ.

Thu gom rác

Nói một cách đơn giản, ý tưởng 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 việc thu gom rác. Một trong số đó là đếm tham chiếu, trong đó mục tiêu là đếm số lần tham chiếu đến các đối tượng trong bộ nhớ. Khi không còn tham chiếu đế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 việc thu gom rác. Trình thu thập rác của PHP sử dụng chức năng đếm tham chiếu, đồng thời việc sử dụng hàm xdebug_debug_zval() của tiện ích Xdebug cho phép bạn xem bên trong. Hãy xem xét chương trình PHP sau.

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

Chương trình này sẽ chỉ định một số ngẫu nhiên được truyền tới một chuỗi cho một biến mới có tên là a. Sau đó, phương thức này tạo hai biến mới là bc, đồng thời chỉ định giá trị a cho chúng. Sau đó, hàm này sẽ chỉ định lại b cho số 42 rồi huỷ đặt c. Cuối cùng, hàm này đặt giá trị của a thành null. Khi chú giải 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 các nhật ký sau đây, trong đó bạn sẽ thấy số lượng tham chiếu đến giá trị của biến a giảm như thế nào sau mỗi bước. Điều này hợp lý với trình tự mã. (Tất nhiên là 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 khi thu gom rác, chẳng hạn như phát hiện chu kỳ, nhưng đối với bài viết này, bạn chỉ cần nắm được ở mức độ cơ bản về cách đếm tệp 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

Việc này có vẻ giống như ban đầu, 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 trong 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 các nhà phát triển sẽ cài đặt PHP qua trình quản lý gói của hệ điều hành của họ. Tuy nhiên, các 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 môi trường thời gian chạy Linux. Nhưng điều này cũng có nghĩa là môi trường thời gian chạy PHP có thể được biên dịch cho các môi trường thời gian chạy khác, chẳng hạn như Wasm.

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

Độc lập với nền tảng PHP đang chạy, các tập lệnh PHP được biên dịch thành cùng mã byte và được Zend Engine chạy. 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. Bộ công cụ này bao gồm Máy ảo (VM) Zend, gồm có Zend Compiler và Zend Executor. Những 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ó tính nă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 phần phụ trợ riêng cho từng kiến trúc. Trong trường hợp này, Wasm đại diện cho một kiến trúc mới. Nếu máy ảo có mã dành riêng cho từng cấu trúc, chẳng hạn như biên dịch đúng thời điểm (JIT) hoặc biên dịch trước thời gian (AOT), thì nhà phát triển cũng triển khai phần phụ trợ cho JIT/AOT cho kiến trúc mới. Phương pháp này rất hợp lý vì thường thì chỉ cần biên dịch lại phần chính của cơ sở mã cho từng kiến trúc mới.

Với Wasm cấp thấp là như thế nào, hoàn toàn có thể thử cách tiếp cận tương tự ở đó: Biên dịch lại mã máy ảo chính bằng trình phân tích cú pháp, tính năng hỗ trợ thư viện, thu thập rác và trình tối ưu hoá cho Wasm, đồng thời triển khai phần phụ trợ JIT hoặc AOT cho Wasm nếu cần. Điều này đã có thể xảy ra kể từ khi ra mắt Wasm MVP và hoạt động hiệu quả trong thực tế trong nhiều trường hợp. Trên thực tế, PHP được biên dịch thành Wasm chính là công cụ hỗ trợ 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 JavaScript của ngôn ngữ máy chủ. Trong Chrome, JavaScript và Wasm chạy trong V8, công cụ JavaScript nguồn mở của Google giúp triển khai ECMAScript theo chỉ định trong ECMA-262. Đồng thời, V8 đã có trình thu gom rác. Điều này có nghĩa là các nhà phát triển đang sử dụng, chẳng hạn như PHP được biên dịch sang Wasm, sẽ chuyển triển khai trình thu gom rác của ngôn ngữ đã chuyển (PHP) đến trình duyệt đã có trình thu gom rác, điều này cũng lãng phí như bạn nghe. Đây chính là lý do 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 dựa trên bộ nhớ tuyến tính của Wasm là không có sự tương tác giữa trình thu gom rác của riêng Wasm và trình thu thập rác tích hợp trên ngôn ngữ được biên dịch thành Wasm, điều này có xu hướng gây ra các sự cố như rò rỉ bộ nhớ và nỗ lực thu thập 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ẽ giúp tránh những vấn đề này.

Chuyển đổi ngôn ngữ lập trình sang môi trường 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 Wasm MVP hiện tại chỉ có thể xử lý các số (ví dụ: số nguyên và số thực có độ chính xác đơn) trong bộ nhớ tuyến tính. Ngoài ra, Wasm có thể giữ lại các tham chiếu bên ngoài với các loại tham chiếu được đề xuất. WasmGC hiện thêm các loại vùng nhớ khối xếp cấu trúc và mảng, tức là hỗ trợ phân bổ bộ nhớ phi tuyến tính. Mỗi đối tượng WasmGC có một loại và cấu trúc cố định, giúp máy ảo 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 có nguy cơ huỷ tối ưu hoá như các ngôn ngữ động 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 vào WebAssembly, thông qua các loại vùng nhớ khối xếp 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 máy ảo máy chủ lưu trữ. 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 có nghĩa là trình thu gom rác của ngôn ngữ lập trình không còn cần phải là một phần của cổng nữa, mà thay vào đó bạn có thể sử dụng trình thu gom rác hiện có.

Để xác minh tác động thực tế của điểm cải tiến này, nhóm Wasm của Chrome đã tổng hợp các phiên bản đ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 nhỏ hơn nhiều chỉ ở mức 2,3 K! C và Rust không bao gồm trình thu gom rác, nhưng vẫn gói malloc/free để quản lý bộ nhớ, lý do Java nhỏ hơn ở đây là vì hoàn toàn không cần gói mã quản lý bộ nhớ nào. Đây chỉ là một ví dụ cụ thể, nhưng nó cho thấy các tệp nhị phân WasmGC có tiềm năng rất nhỏ và điều này thậm chí còn xảy ra trước khi có bất kỳ công việc quan trọng nào về việc tối ưu hoá kích thước.

Nhìn thấy ngôn ngữ lập trình được chuyển cổng WasmGC trong thực tế

Wasm trong Kotlin

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ạ (có mã nguồn nhờ nhóm Kotlin) xuất hiện trong trang thông tin sau đây.

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"

Bây giờ, bạn có thể đang thắc mắc vấn đề đó là gì, vì về cơ bản, mã Kotlin ở trên bao gồm các API OM của JavaScript được chuyển đổi thành Kotlin. Giao diện này bắt đầu có ý nghĩa hơn khi kết hợp với Compose Multiplatform, 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 ứng dụng Android bằng Kotlin. Hãy xem phần khám phá ban đầu về vấn đề này qua bản minh hoạ trình xem hình ảnh Kotlin/Wasm và khám phá mã nguồn của Kotlin, tương tự như sự cho phép của nhóm Kotlin.

Dart và Flutter

Nhóm Dart và Flutter tại Google cũng đang chuẩn bị hỗ trợ cho WasmGC. Quá trình biên dịch Dart-to- Wasm đã gần hoàn tất, và nhóm của chúng tôi đang nghiên cứu việc hỗ trợ công cụ nhằm phân phối các ứng dụng web Flutter được biên dịch cho 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 về Flutter. Bản minh hoạ sau đây là Bản xem trước của Flutter WasmGC.

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

Bài đăng trên blog này hầu như không gây ra những vấn đề gì và hầu như cung cấp cái nhìn tổng quan ở cấp cao về WasmGC. Để tìm hiểu thêm về tính năng này, hãy truy cập vào các đường liên kết sau:

Xác nhận

Hình ảnh chính của Gary Chan trên Unsplash. Bài viết này do Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel ZieglerAndrew đánh giá.