ภาษาโปรแกรมมี 2 ประเภท ได้แก่ ภาษาโปรแกรมที่มีการเก็บขยะและภาษาโปรแกรมที่ต้องมีการจัดการหน่วยความจำด้วยตนเอง ตัวอย่างของภาษาที่กล่าวถึงข้างต้น ได้แก่ Kotlin, PHP หรือ Java เป็นต้น ตัวอย่างของภาษาโปรแกรมประเภทหลัง ได้แก่ C, C++ หรือ Rust โดยทั่วไปแล้ว ภาษาโปรแกรมระดับสูงมักจะมีฟีเจอร์มาตรฐานอย่างการเก็บขยะ ในบล็อกโพสต์นี้ เราจะมุ่งเน้นไปที่ภาษาโปรแกรมที่มีการเก็บขยะดังกล่าวและวิธีคอมไพล์ภาษาเหล่านั้นเป็น WebAssembly (Wasm) แต่ก่อนอื่นเรามาดูกันว่าการเก็บขยะ (มักเรียกว่า GC) คืออะไร
Browser Support
การเก็บขยะ
พูดง่ายๆ ก็คือแนวคิดของการเก็บขยะคือการพยายามเรียกคืนหน่วยความจำที่โปรแกรมจัดสรรไว้ แต่ไม่มีการอ้างอิงถึงอีกต่อไป เราเรียกหน่วยความจำดังกล่าวว่าขยะ การใช้การเก็บขยะมีหลายกลยุทธ์ วิธีหนึ่งคือการนับการอ้างอิง ซึ่งมีวัตถุประสงค์เพื่อนับจำนวนการอ้างอิงไปยังออบเจ็กต์ในหน่วยความจำ เมื่อไม่มีการอ้างอิงถึงออบเจ็กต์อีกต่อไป คุณจะทำเครื่องหมายว่าไม่ได้ใช้ออบเจ็กต์นั้นแล้วได้ ซึ่งจะทำให้พร้อมสำหรับการเก็บขยะ ตัวเก็บขยะของ 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 จะทำงานบนแพลตฟอร์มใด สคริปต์ PHP จะได้รับการคอมไพล์เป็นไบต์โค้ดเดียวกันและเรียกใช้โดย Zend Engine Zend Engine เป็นคอมไพเลอร์และสภาพแวดล้อมรันไทม์สำหรับภาษาการเขียนสคริปต์ PHP โดยประกอบด้วย Zend Virtual Machine (VM) ซึ่งประกอบด้วย Zend Compiler และ Zend Executor ภาษาอย่าง PHP ที่ใช้ในภาษาอื่นๆ ระดับสูง เช่น C มักมีการเพิ่มประสิทธิภาพที่กำหนดเป้าหมายไปยังสถาปัตยกรรมที่เฉพาะเจาะจง เช่น Intel หรือ ARM และต้องใช้แบ็กเอนด์ที่แตกต่างกันสำหรับแต่ละสถาปัตยกรรม ในบริบทนี้ Wasm แสดงถึงสถาปัตยกรรมใหม่ หาก VM มีโค้ดเฉพาะสถาปัตยกรรม เช่น การคอมไพล์แบบ Just-In-Time (JIT) หรือ Ahead-Of-Time (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 ได้เพิ่มประเภทฮีปของโครงสร้างและอาร์เรย์ ซึ่งหมายถึงการรองรับการจัดสรรหน่วยความจำแบบไม่เชิงเส้น ออบเจ็กต์ WasmGC แต่ละรายการมีประเภทและโครงสร้างที่แน่นอน ซึ่งช่วยให้ VM สร้างโค้ดที่มีประสิทธิภาพเพื่อเข้าถึงฟิลด์ได้ง่ายโดยไม่ต้องเสี่ยงต่อการเพิ่มประสิทธิภาพที่ภาษาแบบไดนามิกอย่าง JavaScript มี ข้อเสนอนี้จึงเพิ่มการรองรับภาษาที่มีการจัดการระดับสูงอย่างมีประสิทธิภาพให้กับ WebAssembly ผ่านประเภทฮีปของโครงสร้างและอาร์เรย์ ซึ่งช่วยให้คอมไพเลอร์ภาษาที่กำหนดเป้าหมายเป็น Wasm สามารถผสานรวมกับตัวเก็บขยะใน VM ของโฮสต์ได้ กล่าวอย่างง่ายๆ คือ WasmGC ช่วยให้การพอร์ตภาษาโปรแกรมไปยัง Wasm ไม่จำเป็นต้องรวมตัวเก็บขยะของภาษาโปรแกรมไว้ในพอร์ตอีกต่อไป แต่สามารถใช้ตัวเก็บขยะที่มีอยู่แทนได้
ทีม Wasm ของ Chrome ได้รวบรวมเวอร์ชันของการทดสอบประสิทธิภาพ Fannkuch (ซึ่งจัดสรรโครงสร้างข้อมูลขณะทำงาน) จาก C, Rust และ Java เพื่อยืนยันผลกระทบในโลกแห่งความเป็นจริงของการปรับปรุงนี้ ไบนารี C และ Rust อาจมีขนาดตั้งแต่ 6.1 K ถึง 9.6 K ขึ้นอยู่กับแฟล็กคอมไพเลอร์ต่างๆ ในขณะที่เวอร์ชัน Java มีขนาดเล็กกว่ามากเพียง 2.3 K เท่านั้น C และ Rust ไม่มีตัวเก็บขยะ แต่ก็ยังรวม malloc/free
ไว้เพื่อจัดการหน่วยความจำ และเหตุผลที่ Java มีขนาดเล็กกว่าในที่นี้ก็คือไม่จำเป็นต้องรวมโค้ดการจัดการหน่วยความจำใดๆ เลย นี่เป็นเพียงตัวอย่างหนึ่งที่เฉพาะเจาะจง แต่แสดงให้เห็นว่าไบนารี WasmGC มีศักยภาพในการมีขนาดเล็กมาก และนี่เป็นเพียงก่อนที่จะมีการทำงานอย่างจริงจังในการเพิ่มประสิทธิภาพด้านขนาด
ดูการทำงานของภาษาโปรแกรมที่พอร์ตไปยัง WasmGC
Kotlin Wasm
Kotlin ในรูปแบบของ Kotlin/Wasm เป็นหนึ่งในภาษาโปรแกรมแรกๆ ที่ได้รับการพอร์ตไปยัง Wasm ด้วย WasmGC เดโมพร้อมซอร์สโค้ดจากทีม 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 ที่อาจสร้างไว้แล้วสำหรับแอป Android Kotlin ลองดูการสำรวจเบื้องต้นเกี่ยวกับเรื่องนี้ได้ที่โปรแกรมดูรูปภาพ Kotlin/Wasm ซึ่งเป็นผลงานของทีม Kotlin เช่นกัน
Dart และ Flutter
ทีม Dart และ Flutter ที่ Google ก็กำลังเตรียมการรองรับ WasmGC ด้วยเช่นกัน การคอมไพล์ Dart เป็น Wasm ใกล้จะเสร็จสมบูรณ์แล้ว และทีมกำลังทำงานเพื่อรองรับเครื่องมือในการส่งเว็บแอปพลิเคชัน Flutter ที่คอมไพล์เป็น WebAssembly คุณอ่านข้อมูลเกี่ยวกับสถานะปัจจุบันของงานได้ในเอกสารประกอบของ Flutter การสาธิตต่อไปนี้คือตัวอย่าง Flutter WasmGC
ดูข้อมูลเพิ่มเติมเกี่ยวกับ WasmGC
บล็อกโพสต์นี้เพิ่งจะเริ่มต้นและส่วนใหญ่ให้ภาพรวมระดับสูงของ WasmGC ดูข้อมูลเพิ่มเติมเกี่ยวกับฟีเจอร์นี้ได้ที่ลิงก์ต่อไปนี้
- วิธีใหม่ในการนำภาษาโปรแกรมที่ใช้การเก็บขยะมาใช้ใน WebAssembly อย่างมีประสิทธิภาพ
- ภาพรวม WasmGC
- WasmGC MVP
- WasmGC หลัง MVP
คำขอบคุณ
บทความนี้ได้รับการตรวจสอบโดย Matthias Liedtke, Adam Klein, Joshua Bell, Alon Zakai, Jakob Kummerow, Clemens Backes, Emanuel Ziegler และ Rachel Andrew