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 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ề Kotlin, PHP hoặc Java, cùng nhiều ví dụ khác. Ví dụ: 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 thu thập rác như một tính năng chuẩn. Trong bài đăng trên blog này, trọng tâm là các ngôn ngữ lập trình thu gom rác và cách biên dịch các ngôn ngữ đó thành WebAssembly (Wasm). Nhưng bắt đầu từ việc thu gom rác (thường được gọi là GC) là gì?

Hỗ trợ trình duyệt

  • 119
  • 119
  • 120
  • x

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 nữa. Bộ nhớ như vậy 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ượt 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 nữa, đối tượng có thể được đánh dấu là không còn được sử dụng, do đó sẵn sàng để thu gom rác. Trình thu gom rác của PHP sử dụng phương pháp đếm tham chiếu và việc sử dụng chức năng xdebug_debug_zval() của tiện ích Xdebug cho phép bạn xem chi tiết. 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ẽ chỉ định một số ngẫu nhiên được chuyển thành 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 này. Sau đó, ứng dụng 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 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ẽ cho ra các nhật ký sau. Trong đó, bạn sẽ thấy số 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ý với trình tự 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 về việc thu thập rác, như phát hiện chu kỳ, nhưng đối với bài viết này, bạn chỉ cần có kiến thức cơ bản về việc đếm 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

Điều này có thể giống như khởi đầu, nhưng 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ụ: môi trường thời gian chạy PHP chủ yếu được triển khai bằ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 các 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 của họ. Tuy nhiên, 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ẽ xây dựng 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 thời gian chạy Wasm

Một cách độ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ột mã byte và chạy bởi 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, bao gồm Trình biên dịch Zend và Zend Executor. 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 phương pháp tối ưu hoá dành cho một số kiến trúc cụ thể, chẳng hạn như Intel hoặc ARM, đồng thời đòi hỏi một 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 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 hạn (AOT), thì nhà phát triển cũng sẽ 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ường, bạn chỉ cần biên dịch lại phần chính của cơ sở mã cho từng cấu trúc mới.

Dựa trên mức độ thấp của Wasm, ta nên thử phương pháp 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, hỗ trợ thư viện, thu thập rác và trình tối ưu hoá thành 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ừ MVP của Wasm 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 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 bối cảnh 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ư đã 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 tận dụng, chẳng hạn như PHP được biên dịch thành Wasm, kết thúc việc triển khai trình thu gom rác của ngôn ngữ đã chuyển (PHP) tới trình duyệt đã có trình thu gom rác. Điều này thật lãng phí. Đâ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 xây dựng GC riêng trên bộ nhớ tuyến tính của Wasm là khi đó không có sự tương tác giữa trình thu gom rác của chính Wasm và trình thu gom rác tích hợp sẵn của 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 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ố, tức là số nguyên và số thực trong bộ nhớ tuyến tính và với đề xuất loại tham chiếu đang được chuyển, Wasm có thể giữ lại các tham chiếu bên ngoài. WasmGC hiện đã thêm các loại vùng nhớ khối xếp dạng 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ơ bị huỷ tối ưu hoá như các ngôn ngữ động như JavaScript. Do đó, đề xuất này bổ sung tính 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 dạng mảng và cấu trúc cho phép trình biên dịch ngôn ngữ nhắm đến Wasm tích hợp với bộ thu gom rác trong máy chủ ảo máy chủ. Nói một cách đơn giản thì đ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 tham gia cổng 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 cải tiến này, nhóm Wasm của Chrome đã biên dịch các phiên bản điểm chuẩn Fannkuch (điểm chuẩn phân bổ cấu trúc dữ liệu khi hoạt động) từ C, RustJava. Tệp nhị phân C và Rust có thể nằm trong khoảng 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ỉ 2,3 K! C và Rust không bao gồm trình thu thập rác, nhưng vẫn gói malloc/free để quản lý bộ nhớ. Lý do Java nhỏ hơn ở đây là vì nó hoàn toàn không cần gói bất kỳ 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 trước cả khi thực hiện bất kỳ nỗ lực quan trọng nào để tối ưu hóa cho kích thước.

Xem ngôn ngữ lập trình được cổng của WasmGC trong thực tế

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ạ với mã nguồn do nhóm Kotlin cung cấp được thể hiện trong trang thông tin 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"

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

Dart và Flutter

Các nhóm Dart và Flutter tại Google cũng đang chuẩn bị hỗ trợ cho WasmGC. Công việc biên dịch Dart-to- Wasm gần như hoàn tất và nhóm đang nghiên cứu công cụ hỗ trợ phân phối các ứng dụng web Flutter đã 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 về 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 trên blog này chỉ mới xây dựng được kiến thức và chủ yếu cung cấp thông tin tổng quan 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 đã được Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel ZieglerRachel Andrew xem xét.