- ตรรกะ การวิเคราะห์ชนิด (type resolution) ของคอมไพเลอร์ Zig ถูกออกแบบใหม่ทั้งหมด ทำให้โครงสร้างภายในเรียบง่ายขึ้นและมีการปรับปรุงที่ผู้ใช้มองเห็นได้โดยตรง
- การออกแบบใหม่ใช้การประมวลผล การวิเคราะห์ฟิลด์ของชนิดแบบ lazy จึงไม่ตรวจสอบโครงสร้างรายละเอียดของชนิดที่ยังไม่ได้เริ่มต้นโดยไม่จำเป็น
- ข้อความข้อผิดพลาดของ dependency loop ถูกปรับปรุงให้ชัดเจนขึ้น ทำให้สามารถระบุสาเหตุของลูปได้อย่างแม่นยำ
- มีการแก้ปัญหาการวิเคราะห์มากเกินไปและบั๊กจำนวนมากในฟีเจอร์ incremental compilation ส่งผลให้ความเร็วในการบิลด์ดีขึ้นอย่างมาก
- การเปลี่ยนแปลงครั้งนี้รวมถึงการแก้บั๊กหลายสิบรายการและการปรับปรุงภาษาขนาดเล็ก ช่วยเสริมทั้ง ประสิทธิภาพและประสบการณ์การพัฒนา ของคอมไพเลอร์ Zig โดยรวม
การออกแบบตรรกะการวิเคราะห์ชนิดใหม่
- มีการรวม PR ขนาดประมาณ 30,000 บรรทัด ซึ่งเขียนตรรกะการวิเคราะห์ชนิดของคอมไพเลอร์ Zig ใหม่ให้มีโครงสร้างที่มีเหตุผลและเข้าใจง่ายขึ้น
- ในกระบวนการนี้ โครงสร้างภายในของคอมไพเลอร์ถูกจัดระเบียบใหม่ และยังให้ผลลัพธ์การปรับปรุงที่ผู้ใช้สัมผัสได้โดยตรง
- คอมไพเลอร์ถูกเปลี่ยนให้ ประเมินการวิเคราะห์ฟิลด์ของชนิดแบบ lazy ทำให้ไม่ต้องไล่สำรวจโครงสร้างรายละเอียดของชนิดที่ยังไม่ได้เริ่มต้นโดยไม่จำเป็น
- ในโค้ดตัวอย่าง หากโครงสร้างที่มีฟิลด์
@compileError ถูกใช้เป็นเพียง namespace ก่อนหน้านี้จะเกิดข้อผิดพลาดระหว่างคอมไพล์ แต่ตอนนี้สามารถคอมไพล์ได้ตามปกติ
- สิ่งนี้ช่วยป้องกันการพึ่งพาโค้ดที่ไม่จำเป็นเมื่อใช้ชนิดแบบ namespace เช่น
std.Io.Writer
ปรับปรุงข้อความข้อผิดพลาดของ dependency loop
- ก่อนหน้านี้ ข้อความข้อผิดพลาดของ dependency loop ค่อนข้างกำกวม แต่ตอนนี้จะแสดงสาเหตุและตำแหน่งของลูปอย่างชัดเจน
- ในโค้ดตัวอย่าง เมื่อโครงสร้าง
Foo และ Bar อ้างอิงถึงกัน ข้อความข้อผิดพลาดจะชี้ตำแหน่งการพึ่งพาของแต่ละชนิดอย่างเฉพาะเจาะจง
- ข้อความยังรวมถึงความยาวของลูป ตำแหน่งการประกาศแต่ละฟิลด์ และตำแหน่งของคำสั่งสอบถามการจัดเรียง
- แม้ในกรณีของลูปที่ซับซ้อน ก็ยังให้ข้อมูลเพียงพอเพื่อให้ระบุสาเหตุของปัญหาได้ง่าย
ปรับปรุงประสิทธิภาพ incremental compilation
- การเปลี่ยนแปลงครั้งนี้แก้ไข บั๊กจำนวนมากของฟีเจอร์ incremental compilation
- โดยเฉพาะได้แก้ปัญหา “over-analysis” ทำให้มีการปรับให้คอมไพล์ใหม่เฉพาะส่วนที่เปลี่ยนแปลงเท่านั้น
- ผลลัพธ์คือในหลายกรณี ความเร็วในการคอมไพล์ดีขึ้นอย่างมาก
- นักพัฒนาสามารถเปิดใช้ incremental compilation ใน Zig เวอร์ชัน 0.15.1 ขึ้นไป เพื่อสัมผัสประสบการณ์การพัฒนาที่ดีขึ้น
การปรับปรุงอื่น ๆ
- PR นี้ยังรวมถึง การแก้บั๊ก หลายสิบรายการ การเปลี่ยนแปลงภาษาขนาดเล็ก และ การปรับปรุงประสิทธิภาพคอมไพเลอร์
- ส่วนใหญ่เป็นรายละเอียดปลีกย่อยหรือกรณีเฉพาะทาง
- ดูรายละเอียดการเปลี่ยนแปลงทั้งหมดได้ที่ Codeberg ใน PR #31403
- หากพบบั๊กใหม่ แนะนำให้ รายงานอีเวนต์ปัญหา
ความสำคัญของการเปลี่ยนแปลง
- การทำให้ตรรกะการวิเคราะห์ชนิดเรียบง่ายขึ้นและการเพิ่มประสิทธิภาพ incremental compilation ช่วยเสริม เสถียรภาพและประสิทธิภาพ ของคอมไพเลอร์ Zig
- นักพัฒนาจะได้รับฟีดแบ็กที่รวดเร็วและชัดเจนขึ้น และสามารถคาดหวัง ประสิทธิภาพการทำงานที่ดีขึ้นแม้ในโค้ดเบสขนาดใหญ่
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ฉันคือผู้เขียน devlog นี้เอง
ผมเข้าใจความกังวลเรื่อง การพังของความเข้ากันได้ จากการเปลี่ยนแปลงภาษา แต่ขอชี้แจงว่าการเปลี่ยนคอมไพเลอร์ครั้งนี้ไม่ได้อยู่ในระดับที่จะสร้างผลกระทบวงกว้างมากนัก
ตัวอย่างเช่น ตอน build ZLS บน branch ใหม่ ก็ต้องแก้แค่เปลี่ยน
.{}เป็น.emptyเท่านั้น ซึ่งเกิดจากการเอาค่าเริ่มต้นของstd.ArrayListออก และมันก็อยู่ในสถานะ deprecated มาตั้งแต่ 1 ปีก่อนแล้วอีกกรณีคือโปรเจ็กต์ Awebo ก็มีจุดที่ต้องแก้ในทั้ง dependency tree แค่สามจุด — เปลี่ยน
.empty, เพิ่มcomptime, และเพิ่มorelse @alignOf(T)การแก้แบบนี้ส่วนใหญ่เรียบง่ายถึงระดับที่นักพัฒนา Zig แทบจะ ทำได้แบบอัตโนมัติตามสัญชาตญาณ
แกนหลักของ PR นี้ไม่ใช่เรื่องความพัง แต่เป็น การแก้บั๊กและการปรับปรุง incremental compilation
ผมคิดว่า คุณภาพและการวางแผน ของ PR นี้สูงมาก และไม่ได้มีเจตนาจะลดทอนความพยายามของผู้เขียนเลยแม้แต่น้อย
เพียงแต่ได้บทเรียนว่าต่อไปควรใส่คำอธิบายประกอบให้มากขึ้นและแสดงความคิดเห็นอย่างระมัดระวังมากขึ้น
lib/std/multi_array_list.zigแล้วก็เกิดคำถามขึ้นมาผมไม่เข้าใจว่าทำไมการใช้
@alignOf(T)ในการนิยามMultiArrayList(T)ถึงทำให้เกิด การพึ่งพากันแบบวนซ้ำถึง
Tจะเป็นMultiArrayListเอง มันก็ไม่ใช่ชนิด monomorphic ที่แยกขาดจากกันอย่างสมบูรณ์หรือ? เหมือนผมจะพลาดอะไรบางอย่างโค้ดที่เกี่ยวข้อง: ลิงก์
อยากรู้ประสบการณ์ของคนที่ใช้ Zig ใน สภาพแวดล้อมโปรดักชัน
ภาษานี้เปลี่ยนบ่อย เลยอยากรู้ว่ารอบการอัปเดตหรือรีไรต์เป็นอย่างไร และมีกรณีที่แพ็กเกจ dependency ตามเวอร์ชันภาษาไม่ทันบ้างไหม
รู้ว่า Bun ใช้ Zig ได้ดี แต่ก็อยากฟังตัวอย่างอื่นด้วย
ตลอดช่วง 1–2 ปีที่ผ่านมา การเปลี่ยนแปลงของภาษาและ standard library ดำเนินไปโดย แทบไม่มีปัญหาใหญ่
เมื่อก่อนการอัปเกรดค่อนข้างยุ่งยาก แต่ตอนนี้ให้ความรู้สึกแค่ประมาณ “น่ารำคาญเล็กน้อย”
ถ้าถามถึงประสบการณ์ใช้ Zig เรื่องนี้แทบไม่ใช่ประเด็นที่ผมจะหยิบมาพูดเลย เพราะมันนิ่งมาก
โปรเจ็กต์ใหญ่แบบนี้จะอัปเกรดตาม tagged release และปกติก็ทำ migration เสร็จภายในไม่กี่วันถึงไม่กี่สัปดาห์
dependency ก็แทบไม่มี เลยไม่ได้เป็นภาระหนักเวลาอัปเกรด
แต่บางครั้งแค่พิมพ์ผิดเล็กน้อยก็ทำให้เกิด SIGBUS compiler crash จน debug ยาก
.zig-cacheโตได้ถึง 173GB เลยสร้างปัญหาบน ARM VPS ด้วยตอนอัปเกรด
lightpandaจาก 0.14→0.15 ก็ราบรื่นดี และคิดว่า 0.16 ก็คงไม่มีปัญหาใหญ่แต่ในฐานะนักพัฒนาไลบรารี กลับรู้สึกว่าตามความเร็วของการเปลี่ยนแปลงใน 0.16 ไม่ค่อยทัน
ตอนนี้เลยรองรับแบบทดลองเฉพาะบน branch “dev” เท่านั้น
ผมรีไรต์โมดูล Node.js/TypeScript เป็น Zig แล้วได้ผลคือ เร็วขึ้น 2 เท่าและใช้หน่วยความจำน้อยลง 70%
การรองรับ
sqlite/การ serializeJSONของ Zig แข็งแกร่งมาก เลยเป็นข้อดีสำคัญข้อเสียคือการไม่มี closure หรือไวยากรณ์ vtable ทำให้แยกชั้นของโค้ดได้ยาก
ผมใช้
Arcsกับ bump allocator เพื่อให้ได้ความปลอดภัยด้านหน่วยความจำ และตั้งใจจะรันต่อใน DebugSafe modeตอนสลับไป ReleaseFast มีความเร็วเพิ่มขึ้น 25% แต่ไม่มากพอจะคุ้มกับการเสียความปลอดภัย
ถึงจะต้องแก้โค้ดบ้าง แต่ในระยะยาวนี่คือ แนวทางที่ถูกต้อง
ประทับใจกับผลงานของทีม Zig มาก
ผมใช้ เทอร์มินัล ghostty ที่เขียนด้วย Zig บ่อยมาก และมันเสถียรมาก
แต่โดยส่วนตัวผมยังชอบ Rust มากกว่า
Rust เลือกโมเดลแบบ “โลกปิด” ส่วน Zig เลือกโมเดลแบบ “โลกเปิด”
ใน Rust ต้อง implement trait อย่างชัดเจน แต่ใน Zig ถ้า shape ของชนิดตรงกันก็ใช้งานได้
สิ่งนี้ทำให้ Zig ทำ metaprogramming ได้ทรงพลัง แต่ก็มีข้อเสียคือ type inference ไม่ชัดเจน ทำให้ autocomplete, เอกสาร, และการรองรับ LSP ทำได้ยาก
ฟังดูคล้าย interface ของ Go แต่เท่าที่รู้ Zig ไม่มีแนวคิดที่ตรงกันแบบนั้น
การเปลี่ยนจาก
kernel32ไปNtdllน่าสนใจมากนี่เป็นแนวคิดที่เอาไปเทียบกับ API ฝั่ง user space ของ Linux ได้ด้วย
โดยเฉพาะวิธี จัดการข้อผิดพลาดตรงขอบเขตระหว่าง kernel กับ user ที่คล้ายกัน
แต่ใน Linux นั้น libc กับ kernel ผูกกันแน่นมากจนการใช้
errnoเป็นสิ่งจำเป็นเลยสงสัยว่าเพราะอะไรบน Windows ถึงเกิดแพตเทิร์นแบบนี้เหมือนกัน
errnoหรือGetLastError()เป็น มรดกจากยุคก่อนเธรดสมัยก่อนเป็น cooperative scheduling เลยใช้ตัวแปร global ได้อย่างปลอดภัย แต่เมื่อมี multicore และเธรดก็เริ่มอันตราย
ดังนั้น thread local จึงเกิดขึ้นมาเป็นทางออก
แทนที่จะใช้ชนิดเป็น namespace ผมสงสัยว่าการเพิ่ม namespace แบบ explicit เข้าไปในภาษาจะดีกว่าไหม
ถ้าเป็นฟีเจอร์ที่เพิ่มเข้ามาแล้วเกิดประโยชน์ต่อการ optimize ได้ในหลายจุด ค่อยเพิ่มในตอนนั้น
ใน Zig,
@importจะเปลี่ยนไฟล์ให้เป็น struct และ namespace ก็ถูกแทนด้วย struct ที่ซ้อนกัน แบบตรงไปตรงมากล่าวคือ namespace ก็ไม่ได้ต่างจาก import อีกแบบหนึ่ง
(กาแฟยังเข้าร่างไม่พอ เลยไม่กล้ารับประกันความแม่นยำ)
มีประเด็นหนึ่งที่มักถูกมองข้ามเวลาอภิปรายเรื่องการเปลี่ยนภาษาคือ ผลกระทบต่อ ecosystem
ถ้าภาษาพังบ่อย ไม่ใช่แค่แอป แต่ ไลบรารี เครื่องมือ และบทสอน ก็ต้องวิ่งตามตลอด
สุดท้าย ecosystem ก็จะเอนเอียงไปทางโปรเจ็กต์ที่ มีการดูแลรักษาอย่างต่อเนื่อง มากกว่าไลบรารีแบบ “ทำครั้งเดียวแล้วปล่อยทิ้ง”
นี่เป็น trade-off ที่สมเหตุสมผลในช่วงออกแบบภาษาเริ่มต้น แต่ระยะยาวย่อมมีผลต่อการเติบโตของ ecosystem
ภาษาเกิดใหม่หลายตัวทุ่มแรงมากกับการ ลดความล้าจากการเปลี่ยนแปลง แบบนี้
การได้เห็นว่าแนวทางของ Zig จะนำไปสู่อะไรจึงน่าสนใจดี
Blender ทำ API พังบ่อย แต่การแก้ส่วนใหญ่ก็มักเล็กน้อย
อย่างไรก็ดี ใครสักคนก็ต้องเป็นคนไปแก้ และถ้าไม่มีคนดูแลต่อ ผู้ใช้ก็ต้องแพตช์เอง
addon แบบเสียเงินมีโอกาสถูกรักษาไว้มากกว่า แต่ก็ ไม่ได้รับประกัน
ไลบรารีที่ไม่มีคนดูแลก็เป็น โค้ดที่แย่ อยู่ดี
แทนที่จะวิจารณ์ Zig ก็เลิกโปรโมตภาษาอื่นอย่าง C3 ได้แล้ว
ข้อความใน PR ของ Zig ที่บอกว่า “Chromium, boringssl, Firefox, Rust เรียก SystemFunction036 ของ advapi32.dll” นั้นไม่จริง
พวกมันใช้ ProcessPrng กันอยู่แล้ว และบน Windows 10 ขึ้นไปมันไม่ล้มเหลว
หลักฐานที่เกี่ยวข้องอยู่ใน Microsoft whitepaper
การร้องขอ RNG ถูกออกแบบมาให้ไม่มีทางล้มเหลว และถ้าล้มเหลวจริงก็จะจบการทำงานของโปรเซสไปเลย
กล่าวคือเพื่อรับประกันว่าได้ เลขสุ่มคุณภาพสูง จึงไม่ส่งคืน error code
semantic ของภาษา Zig ดูเรียบง่ายบนผิวหน้า แต่ปฏิสัมพันธ์ระหว่างองค์ประกอบค่อนข้างละเอียดอ่อน
เรื่องแบบนี้อาจก่อให้เกิด corner case ที่ซับซ้อน ขึ้นตามกาลเวลา คล้ายกับกฎของ template ใน C++
PR ขนาด 30,000 บรรทัดถือเป็นความสำเร็จที่น่าทึ่ง
แต่การเปลี่ยน semantic ของภาษา ก็เป็นเรื่องใหญ่มาก เลยรู้สึกตกใจอยู่เหมือนกัน
เข้าใจว่า Zig ยังไม่ถึง 1.0 เลยเปลี่ยนเร็วได้ แต่การพูดแบบ สบาย ๆ ว่า “เราเปลี่ยน semantic บน branch นี้” ก็ทำให้ผมงงนิดหน่อย
เลยสงสัยว่านี่เป็นวัฒนธรรมเฉพาะของ Zig หรือว่าผมตามยุคไม่ทันเอง
คำว่า “modern Zig” ก็ชวนขำดี เพราะความเร็วในการเปลี่ยนแปลงของภาษานี่แหละ
devlog ไม่ใช่บทความการตลาด แต่ใกล้เคียงกับบันทึกภายในสำหรับคนวงในมากกว่า และ Zig ก็ยังไม่ใช่ 1.0
ใน PR มีบริบทและเหตุผลรองรับครบถ้วน
การเลือกใช้ Zig ก็เท่ากับยอมรับ ความเสี่ยงจากการเปลี่ยนภาษา ในระดับหนึ่งตั้งแต่แรกแล้ว
ที่จริงการเก็บงานให้สะอาดตั้งแต่ตอนนี้กลับเป็นผลดีในระยะยาว
(นึกถึงมรดกที่แก้ไม่ได้อย่างลำดับความสำคัญของ bit operator ใน C ก็ได้)
mluggเป็นผู้มีส่วนร่วมหลักของ Zig และเป็นสมาชิก Zig Foundationการเปลี่ยนครั้งนี้มีไว้เพื่อ คลี่คลายการพึ่งพาแบบวนซ้ำ และจัดระเบียบระบบชนิด
ข้อเสนอที่เกี่ยวข้องเปิดสาธารณะอยู่ใน #3257 และ #15909
การเปลี่ยนนี้ทำให้การ resolve type ของ Zig ถูกจัดระเบียบเป็นโครงสร้าง DAG (directed acyclic graph) ส่งผลให้เสถียรภาพของคอมไพเลอร์ดีขึ้นมาก
Zig บริหารด้วยโมเดล BDFN (Benevolent Dictator For Now) และอำนาจตัดสินใจสุดท้ายอยู่ที่ Andrew Kelley
แต่ทีมทำงานในรูปแบบองค์กรไม่แสวงกำไร และให้ความสำคัญสูงสุดกับ ความเชื่อมั่นของผู้ใช้และคุณภาพของภาษา
สำหรับผม การได้ร่วมงานกับ Matthew เป็นเกียรติอย่างยิ่ง
C ดูเหมือนจะเป็นระบบระเบียบในเชิงรูปแบบ แต่ในทางปฏิบัติกลับเป็น ภาษาที่โกลาหล มาก