เปิดใช้ WebAssembly Garbage Collection (WasmGC) ใน Chrome แล้วโดยค่าเริ่มต้น

ภาษาโปรแกรมมี 2 ประเภท ได้แก่ ภาษาโปรแกรมที่มีการรวบรวมขยะและภาษาโปรแกรมที่ต้องจัดการหน่วยความจำด้วยตนเอง ตัวอย่างภาษาแบบแรก ได้แก่ Kotlin, PHP หรือ Java ตัวอย่างภาษาหลัง ได้แก่ C, C++ หรือ Rust โดยทั่วไปแล้ว ภาษาโปรแกรมระดับสูงมีแนวโน้มที่จะมีการรวบรวมขยะเป็นฟีเจอร์มาตรฐานมากกว่า บล็อกโพสต์นี้จะเน้นไปที่ภาษาโปรแกรมที่มีการรวบรวมขยะและวิธีคอมไพล์ภาษาเหล่านั้นเป็น WebAssembly (Wasm) แต่การรวบรวมขยะ (มักเรียกว่า GC) คืออะไร

การรองรับเบราว์เซอร์

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

การเก็บขยะ

กล่าวอย่างง่ายคือ แนวคิดของการรวบรวมขยะคือการพยายามเรียกคืนหน่วยความจำที่โปรแกรมจัดสรรไว้ แต่ไม่มีการอ้างอิงอีกต่อไป หน่วยความจำดังกล่าวเรียกว่า "ขยะ" การใช้การเก็บขยะมีกลยุทธ์มากมาย หนึ่งในนั้นคือการนับการอ้างอิง ซึ่งมีวัตถุประสงค์เพื่อนับจํานวนการอ้างอิงถึงออบเจ็กต์ในหน่วยความจํา เมื่อไม่มีการอ้างอิงถึงออบเจ็กต์อีกต่อไป ระบบจะทำเครื่องหมายออบเจ็กต์ว่าไม่ได้ใช้งานแล้วและพร้อมสำหรับการเก็บขยะ เครื่องมือเก็บขยะของ PHP ใช้การนับการอ้างอิง และการใช้ฟังก์ชัน xdebug_debug_zval() ของส่วนขยาย Xdebug ช่วยให้คุณดูข้อมูลเบื้องหลังได้ ลองดูโปรแกรม PHP ต่อไปนี้

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

โปรแกรมจะกําหนดตัวเลขแบบสุ่มที่แคสต์เป็นสตริงให้กับตัวแปรใหม่ชื่อ a จากนั้นจะสร้างตัวแปรใหม่ 2 รายการ ได้แก่ b และ c และกำหนดค่าเป็น 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 คุณสามารถดูซอร์สโค้ด PHP บน GitHub โค้ดการจัดการหน่วยความจำที่ไม่ใช้แล้วของ PHP จะอยู่ส่วนใหญ่ในไฟล์ zend_gc.c นักพัฒนาซอฟต์แวร์ส่วนใหญ่จะติดตั้ง PHP ผ่านเครื่องมือจัดการแพ็กเกจของระบบปฏิบัติการ แต่นักพัฒนาซอฟต์แวร์สามารถสร้าง PHP จากซอร์สโค้ดได้ด้วย เช่น ในสภาพแวดล้อม Linux ขั้นตอน ./buildconf && ./configure && make จะสร้าง PHP สําหรับรันไทม์ Linux แต่นั่นก็หมายความว่ารันไทม์ PHP สามารถคอมไพล์สำหรับรันไทม์อื่นๆ ได้ เช่น Wasm

วิธีการแบบดั้งเดิมในการพอร์ตภาษาไปยังรันไทม์ Wasm

สคริปต์ PHP จะคอมไพล์เป็นไบต์โค้ดเดียวกันและเรียกใช้โดย Zend Engine โดยไม่ขึ้นอยู่กับแพลตฟอร์มที่ PHP ทำงานอยู่ Zend Engine เป็นคอมไพเลอร์และสภาพแวดล้อมรันไทม์สําหรับภาษาสคริปต์ PHP ซึ่งประกอบด้วยเครื่องเสมือน Zend (VM) ที่ประกอบไปด้วย Zend Compiler และ Zend Executor ภาษาอย่าง PHP ที่ใช้งานในภาษาระดับสูงอื่นๆ เช่น C มักจะมีการเพิ่มประสิทธิภาพที่กำหนดเป้าหมายไปยังสถาปัตยกรรมที่เฉพาะเจาะจง เช่น Intel หรือ ARM และต้องอาศัยแบ็กเอนด์ที่แตกต่างกันสำหรับสถาปัตยกรรมแต่ละแบบ ในบริบทนี้ Wasm แสดงถึงสถาปัตยกรรมใหม่ หาก VM มีโค้ดเฉพาะสำหรับสถาปัตยกรรม เช่น การคอมไพล์แบบทันท่วงที (JIT) หรือการคอมไพล์ล่วงหน้า (AOT) นักพัฒนาซอฟต์แวร์จะใช้แบ็กเอนด์สำหรับ JIT/AOT สำหรับสถาปัตยกรรมใหม่ด้วย แนวทางนี้เหมาะมากเนื่องจากบ่อยครั้งที่ส่วนหลักของโค้ดเบสสามารถคอมไพล์ใหม่สำหรับสถาปัตยกรรมใหม่แต่ละรายการได้

เมื่อพิจารณาว่า Wasm เป็นภาษาระดับต่ำเพียงใด ก็เป็นเรื่องปกติที่จะลองใช้แนวทางเดียวกันกับในนั้น โดยคอมไพล์โค้ด VM หลักอีกครั้งด้วยโปรแกรมแยกวิเคราะห์ การสนับสนุนไลบรารี การเก็บขยะ และเครื่องมือเพิ่มประสิทธิภาพเป็น Wasm รวมถึงใช้แบ็กเอนด์ JIT หรือ AOT สำหรับ Wasm หากจำเป็น การดำเนินการนี้ทำได้ตั้งแต่ Wasm MVP และใช้งานได้จริงในหลายกรณี อันที่จริงแล้ว PHP ที่คอมไพล์เป็น Wasm คือสิ่งที่ขับเคลื่อน WordPress Playground ดูข้อมูลเพิ่มเติมเกี่ยวกับโปรเจ็กต์ได้ในบทความสร้างประสบการณ์การใช้งาน WordPress ในเบราว์เซอร์ด้วย WordPress Playground และ WebAssembly

อย่างไรก็ตาม PHP Wasm จะทำงานในเบราว์เซอร์ในบริบทของภาษาโฮสต์ JavaScript ใน Chrome JavaScript และ Wasm จะทำงานใน V8 ซึ่งเป็นเครื่องมือ JavaScript แบบโอเพนซอร์สของ Google ที่ใช้ ECMAScript ตามที่ระบุไว้ใน ECMA-262 และ V8 มีโปรแกรมเก็บขยะอยู่แล้ว ซึ่งหมายความว่านักพัฒนาซอฟต์แวร์ที่ใช้ PHP ที่คอมไพล์เป็น Wasm จะต้องนำโปรแกรมเก็บขยะที่ใช้กับภาษาที่พอร์ต (PHP) ไปไว้ในเบราว์เซอร์ที่มีโปรแกรมเก็บขยะอยู่แล้ว ซึ่งเป็นเรื่องที่สิ้นเปลือง ด้วยเหตุนี้ WasmGC จึงเข้ามามีบทบาท

ปัญหาอีกประการหนึ่งของแนวทางแบบเก่าในการอนุญาตให้โมดูล Wasm สร้าง GC ของตนเองบนหน่วยความจำเชิงเส้นของ Wasm คือจะไม่มีการทำงานร่วมกันระหว่างตัวเก็บขยะของตนเองของ Wasm กับตัวเก็บขยะที่สร้างบนภาษาที่คอมไพล์เป็น Wasm ซึ่งมักจะทำให้เกิดปัญหาต่างๆ เช่น หน่วยความจำรั่วไหลและการพยายามเก็บรวบรวมที่ไม่มีประสิทธิภาพ การให้โมดูล Wasm ใช้ GC ในตัวที่มีอยู่ซ้ำจะช่วยหลีกเลี่ยงปัญหาเหล่านี้

การพอร์ตภาษาโปรแกรมไปยังรันไทม์ใหม่ด้วย WasmGC

WasmGC เป็นข้อเสนอของกลุ่มชุมชน WebAssembly การใช้งาน Wasm MVP ปัจจุบันสามารถจัดการกับตัวเลขเท่านั้น ซึ่งก็คือจำนวนเต็มและทศนิยมในหน่วยความจําแบบเส้นตรง และเมื่อมีการส่งข้อเสนอประเภทข้อมูลอ้างอิงแล้ว Wasm จะเก็บข้อมูลอ้างอิงภายนอกได้อีกด้วย ตอนนี้ WasmGC ได้เพิ่มประเภทกอง STRUCT และ Array ซึ่งหมายความว่ารองรับการจัดสรรหน่วยความจำแบบไม่เป็นเส้นตรง ออบเจ็กต์ WasmGC แต่ละรายการมีประเภทและโครงสร้างแบบคงที่ ซึ่งทำให้ VM สร้างโค้ดที่มีประสิทธิภาพเพื่อเข้าถึงฟิลด์ต่างๆ ได้ง่ายโดยไม่ต้องเสี่ยงกับdeoptimizationsที่ภาษาแบบไดนามิกอย่าง JavaScript มี ข้อเสนอนี้จึงเพิ่มการรองรับภาษาที่มีการจัดการระดับสูงอย่างมีประสิทธิภาพให้กับ WebAssembly ผ่านประเภทกองข้อมูล Struct และ Array ซึ่งช่วยให้คอมไพเลอร์ภาษาที่กำหนดเป้าหมายเป็น Wasm ผสานรวมกับโปรแกรมเก็บขยะใน VM โฮสต์ได้ กล่าวอย่างง่ายคือ เมื่อใช้ WasmGC การพอร์ตภาษาโปรแกรมไปยัง Wasm จะทำให้ไม่จำเป็นต้องรวมโปรแกรมเก็บขยะของภาษาโปรแกรมนั้นไว้ในพอร์ตอีกต่อไป แต่จะใช้โปรแกรมเก็บขยะที่มีอยู่ได้

ทีม Wasm ของ Chrome ได้รวบรวมการทดสอบประสิทธิภาพ Fannkuch (ซึ่งจัดสรรโครงสร้างข้อมูลขณะทำงาน) จาก C, Rust และ Java เพื่อยืนยันผลกระทบในชีวิตจริงของการปรับปรุงนี้ ไฟล์ไบนารี C และ Rust มีขนาดตั้งแต่ 6.1 K ถึง 9.6 K โดยขึ้นอยู่กับ Flag ต่างๆ ของคอมไพเลอร์ ส่วนเวอร์ชัน Java จะมีขนาดเล็กกว่ามากเพียง 2.3 K C และ Rust ไม่มีเครื่องมือเก็บขยะ แต่ยังคงรวม malloc/free เพื่อจัดการหน่วยความจำอยู่ และสาเหตุที่ Java มีขนาดเล็กกว่าก็เพราะว่าไม่ต้องรวมโค้ดการจัดการหน่วยความจำใดๆ เลย นี่เป็นเพียงตัวอย่างที่เฉพาะเจาะจงเพียงตัวอย่างเดียว แต่แสดงให้เห็นว่าไบนารีของ WasmGC มีแนวโน้มที่จะมีขนาดไฟล์เล็กมาก และนี่ยังอยู่ในช่วงก่อนการทํางานที่สำคัญใดๆ ในการเพิ่มประสิทธิภาพขนาด

การดูภาษาโปรแกรมที่พอร์ต WasmGC ใช้งานได้จริง

Kotlin Wasm

หนึ่งในภาษาโปรแกรมแรกๆ ที่พอร์ตไปยัง Wasm ด้วย WasmGC คือ Kotlin ในรูปแบบ Kotlin/Wasm ตัวอย่างพร้อมซอร์สโค้ดจากทีม 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 ด้านบนประกอบด้วย JavaScript OM API ที่แปลงเป็น Kotlin เป็นหลัก เครื่องมือนี้จะเริ่มมีประโยชน์มากขึ้นเมื่อใช้ร่วมกับ Compose Multiplatform ซึ่งช่วยให้นักพัฒนาแอปสร้าง UI บน UI ที่สร้างไว้สำหรับแอป Android Kotlin อยู่แล้วได้ โปรดดูการสำรวจเบื้องต้นเกี่ยวกับเรื่องนี้ด้วยตัวอย่างโปรแกรมดูรูปภาพ Kotlin/Wasm และสำรวจซอร์สโค้ดของโปรแกรมดูรูปภาพดังกล่าว ซึ่งทีม Kotlin เป็นผู้จัดทำขึ้น

Dart และ Flutter

ทีม Dart และ Flutter ของ Google กำลังเตรียมการสนับสนุนสำหรับ WasmGC ด้วย การคอมไพล์ Dart เป็น WASM เกือบเสร็จสมบูรณ์แล้ว และทีมกำลังดำเนินการเกี่ยวกับการสนับสนุนเครื่องมือสำหรับนำส่งเว็บแอปพลิเคชัน Flutter ที่คอมไพล์เป็น WebAssembly คุณสามารถอ่านเกี่ยวกับสถานะปัจจุบันของการทำงานได้ในเอกสารประกอบของ Flutter การสาธิตต่อไปนี้คือ Flutter WasmGC Preview

ดูข้อมูลเพิ่มเติมเกี่ยวกับ WasmGC

บล็อกโพสต์นี้เป็นเพียงข้อมูลเบื้องต้นและส่วนใหญ่ให้ภาพรวมระดับสูงของ WasmGC ดูข้อมูลเพิ่มเติมเกี่ยวกับฟีเจอร์นี้ได้ที่ลิงก์ต่อไปนี้

ขอขอบคุณ

บทความนี้ได้รับการตรวจสอบโดย Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler และ Rachel Andrew