- ตอนนี้ Zig เปลี่ยนเส้นทางค่าเริ่มต้นบนเป้าหมาย x86_64 จากเดิมที่ LLVM ลด bitcode ลงเป็นไฟล์อ็อบเจ็กต์ มาใช้ แบ็กเอนด์ x86 แบบโฮสต์ด้วยตนเอง แทน ทำให้การคอมไพล์บิลด์ดีบักเร็วขึ้นมากและใช้หน่วยความจำน้อยลง
- แบ็กเอนด์ x86 แบบโฮสต์ด้วยตนเองผ่าน behavior test 1987 รายการ มากกว่าแบ็กเอนด์ LLVM ที่ผ่าน 1980 รายการ และจากทั้งหมด 2084 รายการนั้น มีบางการทดสอบเพิ่มเติมที่รันเฉพาะกับการทดสอบ self-hosted x86 เท่านั้น
- ในเบนช์มาร์ก
hello.zigค่าเฉลี่ย 918ms ของเส้นทาง LLVM ลดลงเหลือ 275ms บนแบ็กเอนด์แบบโฮสต์ด้วยตนเองที่เป็นค่าเริ่มต้น ทำให้ wall time ลดลง 70.1% และ peak RSS ลดจาก 214MB เหลือ 137MB - แม้ในโปรเจ็กต์ขนาดใหญ่เช่นตัวคอมไพเลอร์ Zig เอง เวลาบิลด์ก็ลดจาก 75 วินาทีเหลือ 20 วินาที แต่บน Windows ยังไม่เปลี่ยนค่าเริ่มต้น เพราะยังต้องทำงานส่วนของ COFF linker เพิ่มเติม
- งานที่เหลือหลังจากนี้คือการทำให้การสร้างโค้ดขนานได้อย่างสมบูรณ์ ปรับปรุง linker ทำ incremental compilation ให้เสถียร ปรับคุณภาพโค้ด x86 และขยายแบ็กเอนด์ไปยัง aarch64
การสลับแบ็กเอนด์เริ่มต้นของ x86_64
- สำหรับการบิลด์เป้าหมาย x86_64 ตอนนี้ Zig ใช้ แบ็กเอนด์ x86 แบบโฮสต์ด้วยตนเอง เป็นค่าเริ่มต้น
- เส้นทางค่าเริ่มต้นก่อนหน้านี้คือให้ LLVM ลดไฟล์ bitcode ลงเป็นไฟล์อ็อบเจ็กต์
- บน Windows ยังไม่มีการเปลี่ยนค่าเริ่มต้น
- เพราะยังต้องทำงานกับ COFF linker เพิ่มเติม
สถานะการผ่าน behavior test
- แบ็กเอนด์ x86 แบบโฮสต์ด้วยตนเองผ่าน behavior test 1987 รายการ
- แบ็กเอนด์ LLVM ผ่าน behavior test 1980 รายการ
- behavior test ทั้งหมดมี 2084 รายการ แต่การทดสอบเพิ่มเติมส่วนใหญ่ซ้ำกับการทดสอบแบ็กเอนด์ x86 ของ LLVM เอง
- การทดสอบเพิ่มเติมเหล่านี้จะรันเฉพาะตอนทดสอบ self-hosted x86
- เมื่อนับตามจำนวนที่ผ่าน แบ็กเอนด์ x86 ของ Zig สำหรับการอิมพลีเมนต์ภาษา Zig อยู่ล้ำหน้าแบ็กเอนด์ LLVM แล้ว
เหตุผลที่ต้องแข่งขันกับเส้นทาง LLVM
- เหตุผลใหญ่ที่สุดที่ Zig แข่งขันกับ LLVM ในด้านการสร้างโค้ด คือ สามารถสร้างความแตกต่างด้านความเร็วในการคอมไพล์ได้มาก
- ดูพื้นหลังที่เกี่ยวข้องได้ใน คำอธิบายบน Ziggit
เบนช์มาร์ก hello.zig
- ผลลัพธ์
zig build-exe hello.zig -fllvm:- ค่าเฉลี่ย wall time: 918ms
- peak RSS: 214MB
- CPU cycles: 4.53G
- instructions: 8.50G
- ผลลัพธ์ของเส้นทางค่าเริ่มต้น
zig build-exe hello.zig:- ค่าเฉลี่ย wall time: 275ms
- peak RSS: 137MB
- CPU cycles: 1.57G
- instructions: 3.21G
- แบ็กเอนด์แบบโฮสต์ด้วยตนเองที่เป็นค่าเริ่มต้นลดตัวชี้วัดหลายอย่างเมื่อเทียบกับเส้นทาง LLVM
- wall time ลดลง 70.1%
- peak RSS ลดลง 36.2%
- CPU cycles ลดลง 65.2%
- instructions ลดลง 62.2%
- cache misses ลดลง 86.1%
- branch misses ลดลง 78.3%
ผลต่อโปรเจ็กต์ขนาดใหญ่
- ในโปรเจ็กต์ที่ใหญ่ขึ้นอย่างตัวคอมไพเลอร์ Zig เอง เวลาบิลด์ลดจาก 75 วินาทีเหลือ 20 วินาที
- แบ็กเอนด์ x86 แบบโฮสต์ด้วยตนเองไม่ได้ช่วยเฉพาะตัวอย่างเล็ก ๆ แต่ยังลดเวลาคอมไพล์ในโค้ดเบสขนาดใหญ่ได้อย่างมาก
งานถัดไป
- ตอนนี้ Zig เริ่มทำงานด้าน การสร้างโค้ดแบบขนานอย่างสมบูรณ์ แล้ว
- มีเดโมที่ดูได้จาก บันทึก asciinema
- เมื่อปรับปรุง linker และแก้บั๊กเพิ่มเติม ก็จะสามารถทำ incremental compilation ให้เสถียรและแข็งแรงได้ด้วยแบ็กเอนด์นี้
- คุณภาพของโค้ด x86 ที่สร้างขึ้นยังมีพื้นที่ให้ปรับปรุงอีก
- เป้าหมายถัดไปคือ aarch64 และคาดว่างานจะเร็วขึ้นด้วย Legalize pass ใหม่
- สามารถดาวน์โหลดบิลด์ล่าสุดของ master branch จากหน้าดาวน์โหลดของ Zig มาลองได้ด้วยตนเอง
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
เท่าที่ผมรู้ Zig มีงานที่กำลังทำอยู่มากมายเพื่อให้ประสบการณ์การพัฒนาดีขึ้น แทบทุกวันจะมีอะไรสักอย่างถูกพัฒนาอยู่ และเมื่อกี้ก็มีอย่าง https://github.com/ziglang/zig/pull/24124 โผล่ขึ้นมา
เมื่อก่อนผมจำได้ว่าเคยมีแผนเรื่อง hot code replacement ด้วย และด้วยความเร็วในการพัฒนาตอนนี้ ถ้ามันใช้งานได้บน x86_64 ภายใน 1 ปี ผมก็คงไม่แปลกใจ
ตอนนี้สิ่งที่เจ็บปวดที่สุดสำหรับผมเป็นการส่วนตัวคือความเร็วของ
comptimeคอมไพเลอร์ยังมีงานต้องทำอีกมากตรงนี้ และการรัน brainF** DSL ตอน compile time ก็ค่อนข้างช้า ผมลองทำเองแล้ว เป็นการทดลองที่ตลกดีbackend ใหม่ ๆ ที่ Zig กำลังนำเข้ามาโดยรวมแล้วน่าตื่นเต้นมาก ผมอยากลองทำ backend URCL(https://github.com/ModPunchtree/URCL) สำหรับ Zig เอง
comptimeรู้แล้วว่าต้องทำอะไร และนานมาแล้วก็เคยเริ่มทำงานบน branch ไว้ด้วย เพียงแต่มันต้องแก้โค้ด semantic analysis ใหม่ค่อนข้างมาก ดังนั้นเป็นงานที่ทำได้แน่ ควรทำ และจะทำ แต่ตอนนี้ต้องแข่งกับลำดับความสำคัญอื่น ๆ อยู่comptimeที่ช้านี่เป็นปัญหาจริง ๆ ไหม ผมกำลังทำไลบรารี JSON-RPC และพึ่งพาcomptimeอย่างหนักเพื่อ dispatch คำขอ JSON ไปยังฟังก์ชันใด ๆเพราะ static type ที่เข้มงวด ทำให้ไม่มีทางทำ dynamic dispatch ไปยังฟังก์ชันที่มีพารามิเตอร์ใด ๆ ใน runtime ได้ และวิธีเดียวที่ผมพบคือใช้
comptimeเพื่อหา mapping ของ function type ตอน compile timeดูเหมือนว่า code size จะใหญ่ขึ้น เพราะมีสำเนาโค้ดที่ถูก
comptimeเพิ่มขึ้นสำหรับแต่ละฟังก์ชันโดยเฉพาะ น่าจะทำ backend ที่รับ AIR แล้วสร้างรายงานความปลอดภัยของหน่วยความจำได้ เช่นระบุการใช้ค่าที่ยังไม่ได้กำหนด, stack pointer หลุดออกไป, use-after-free, double free, alias xor mut อะไรทำนองนั้น
นี่เป็นผลงานที่ยิ่งใหญ่มากอยู่แล้ว แต่ตามที่เขียนไว้ใน dev log ยังมีอีกมากที่เหลืออยู่ข้างหน้า ไอเดียของ compiler ที่แก้เฉพาะส่วนที่จำเป็นในไบนารีระหว่างคอมไพล์นั้นสดใหม่และสุดโต่งมาก แต่ดูเหมือนตอนนี้จะอยู่ในระยะที่โปรเจกต์ Zig เอื้อมถึงแล้ว
รอดูอนาคตเลย
ส่วนที่ว่า “โปรเจกต์ใหญ่ ๆ อย่างคอมไพเลอร์ Zig ลดจาก 75 วินาทีเหลือ 20 วินาที และนี่เพิ่งเริ่มต้นเท่านั้น” ทำให้น่าตื่นเต้น อยากรู้ว่าคนนี้จะทำอะไรกับมันได้บ้าง และดูฉลาดมากจริง ๆ
สงสัยว่า package management อยู่ในสถานะไหนแล้ว ผมเคยพยายามทำแอป QuickJS + SDL3 แต่เพราะความวุ่นวายฝั่ง C++ เลยย้ายไป Rust และที่นั่นก็ใช้งานได้ดีเลย ถ้าลองทำใน Zig ได้ก็คงดี
มันก็มีข้อดีอยู่ เพราะพึ่งพา archive ใด ๆ ก็ได้ และแพ็กเกจ Zig จำนวนมากที่ห่อ C library ก็แทบจะเป็น build script ที่พึ่งพา tarball release ที่ไม่ได้แก้ไข แน่นอนว่าสำหรับมือใหม่จะยุ่งยากขึ้นนิดหน่อย
SDL3 มี wrapper Zig แบบ native อยู่: https://github.com/Gota7/zig-sdl3
มีอันที่ repackage ไลบรารี/API C แบบพื้นฐานกว่าด้วย: https://github.com/castholm/SDL
สำหรับ QuickJS ทางเลือกเดียวคือ C API: https://github.com/allyourcodebase/quickjs-ng
Zig ทำให้ใช้ C package โดยตรงแบบนี้ได้ง่ายมาก แต่ type ของ Zig เข้มงวดกว่ามาก เลยต้อง cast เยอะเวลาโต้ตอบกับ API
real 0m18.444s,user 0m17.408s,sys 0m1.688sแม้บนโปรเซสเซอร์เก่ามากก็ยังได้ประมาณนี้ เลยเร็วเกินพอจนไม่จำเป็นต้องอัปเกรด
สเปกประมาณ
AMD Athlon(tm) 64 X2 Dual Core Processor 4400+, 2 คอร์, 2.3GHz, แคช 512KBผมเคยพูดตอน D และ Nature แล้วว่า สำหรับทุกภาษาที่มี backend ของตัวเอง เรามีหน้าที่ต้องสนับสนุน โปรเจกต์ที่พยายามไม่พึ่งพา LLVM
ดูเหมือนว่า LLVM ทำให้การวิจัยและพัฒนาคอมไพเลอร์ชะงักงัน มีภาษามากเกินไปที่เลือกพึ่งพา LLVM และมีคนมากเกินไปที่ไม่เห็นคุณค่าของรอบการทำซ้ำที่รวดเร็ว หรือไม่คาดหวังสิ่งที่ดีกว่านั้นอีกแล้ว
การ iterate อย่างรวดเร็วด้วย incremental compilation และ binary patching รวมถึงการ debugging ที่ดี ควรเป็นความคาดหวังของภาษาใหม่ ไม่ใช่ถูกมองว่าเป็นฟีเจอร์เฉพาะกลุ่มหรือเรื่องที่ยากเกินไป
อุตสาหกรรม realtime rendering ทั้งหมดแทบจะสร้างอยู่บน LLVM หรือ fork ของ LLVM และ Microsoft ก็เปลี่ยน shader compiler มาเป็น LLVM แล้ว และเพิ่งเริ่ม upstream โค้ดในตอนนี้
โครงสร้างพื้นฐานคอมไพเลอร์ของคอนโซลเกมส่วนใหญ่ก็อิง Clang เช่นกัน Xbox ยังยึด MSVC มาจนถึงตอนนี้ แต่ถือว่าเป็นข้อยกเว้นมากกว่า
โดยรวมแล้ว LLVM ประสบความสำเร็จอย่างมหาศาล โดยเฉพาะในการ bootstrap สิ่งใหม่ ๆ
ไม่ได้อยากให้ฟังดูเหมือนเรียกร้องหรือไม่รู้สึกขอบคุณ เพราะ Zig เป็นงานที่ทำให้ใช้ฟรี เพียงแต่สิ่งที่อยากรู้ที่สุดคือ กำหนดการ 1.0 ที่สมจริง
Zig แทบจะตรงกับสิ่งที่ผมต้องการจากภาษา low-level อย่างพอดี และกำลังรอให้มันเสถียร
แน่นอนว่าผมรู้สึกขอบคุณปรัชญาการออกแบบแบบมินิมัลของ Zig มากจริง ๆ
โปรแกรม hello world ที่สร้างด้วย
zig initเมื่อคอมไพล์แล้วมีขนาด 9.3MB เมื่อเทียบกับ 7.6KB ของ-Doptimize=ReleaseSmallถือว่าใหญ่กว่ากันเกิน 1000 เท่า ซึ่งมหาศาลมาก-OReleaseSmall -fno-stripสร้างไฟล์รันได้ขนาด 580KB และ-ODebug -fstripสร้างไฟล์รันได้ขนาด 1.4MBแบ็กเอนด์ x86 ของ Zig ให้ประสบการณ์ดีบักที่ดีกว่ามากเมื่อใช้ร่วมกับ fork ของ lldb ที่เข้าใจ Zig: https://github.com/ziglang/zig/wiki/LLDB-for-Zig
ตอนนี้จำไม่ได้ว่าสามารถ step execute ลอจิก
comptimeได้หรือยัง เรื่องนี้เป็นประเด็นที่เพิ่งคุยกันไม่นานมานี้ดูเหมือนว่า Julia ควรพิจารณาย้ายไปใช้ Zig ถ้าต้องการได้ประสิทธิภาพเพิ่มขึ้นมาก ๆ ผมจำได้ว่าผู้เขียน Julia เคยกังวลทุกครั้งที่ LLVM ออกรีลีสว่าจะมี performance regression
คอมไพเลอร์ค่อนข้าง retarget ได้ และนี่ก็เป็นพื้นที่ที่กำลังพัฒนาอย่างจริงจัง ดังนั้นในอนาคตอาจจินตนาการได้ว่า Zig เป็นคอมไพเลอร์ทางเลือกสำหรับบางส่วนของภาษา
@code_llvmที่แสดง IR ได้จริง ๆเช่น compile cache ที่ละเอียดขึ้น, เครื่องมือที่ดีกว่าเพื่อป้องกัน invalidation, การเอา optimization แบบ world splitting ออก, ใช้ multi-threading ในคอมไพเลอร์ให้มากขึ้น, precompile ล่วงหน้าโดยอัตโนมัติสำหรับ concrete signature, และ code generation ที่ lazy กว่าเดิมซึ่ง hot-swap โค้ดเมื่อคอมไพล์เสร็จ
ในฐานะมือใหม่สุด ๆ ผมสงสัยว่า Zig ดีกว่าภาษาอื่นตรงไหน เข้าใจว่าเป็น C ที่ทันสมัยกว่า แล้ว ส่วนที่ทันสมัย นั้นคืออะไร?
ต่างจาก array ของ C, Zig มี slice ที่รู้ความยาว จึงดีกว่าในแง่ buffer overflow, ต้องตรวจสอบ explicit optional type เสมอ และไม่อนุญาต null pointer แม้ในกรณีที่อนุญาตเมื่อเชื่อมกับโค้ด C type ก็จะแสดงเรื่องนั้นอย่างชัดเจน
ยังมี enum, tagged union และการบังคับตรวจสอบให้ครบทุกกรณีใน
switchexpressionการจัดการข้อผิดพลาดเป็นแบบชัดเจน และฟังก์ชันจะคืนข้อผิดพลาด (ค่า enum) ที่ผู้เรียกต้องจัดการไม่ทางใดก็ทางหนึ่ง ใน C แม้ฟังก์ชันจะคืนจำนวนเต็มเพื่อบอกข้อผิดพลาด ก็สามารถเพิกเฉยได้ทั้งหมด
อย่างไรก็ตาม ภาษายังไม่ได้มีวิธีมาตรฐานในตัวสำหรับคืนข้อมูลพร้อมกับข้อผิดพลาด แพตเทิร์นการส่ง struct ข้อผิดพลาดผ่านพารามิเตอร์ให้ความรู้สึกเหมือนสิ่งที่แปะเพิ่มเข้ามา ผมคิดว่าควรมี syntax พิเศษสำหรับเรื่องนี้
มีบล็อก
deferและerrdeferสำหรับ cleanup หลังจาก return จากฟังก์ชันหรือเกิดข้อผิดพลาด และสามารถใช้ การสร้างโค้ดด้วยcomptimeกับ type reflection อย่าง@typeInfoแทน macro ได้ส่ง allocator ให้ไลบรารี เพื่อให้ผู้เรียกเป็นคนตัดสินใจโดยทั่วไปว่าจะ allocate memory ที่ไหนและอย่างไร และแค่ใช้
GeneralPurposeAllocatorก็ช่วยหา memory leak ได้ง่ายหลังเริ่มเขียนโปรแกรมมา ผมใช้แต่ภาษาระดับสูงมาตลอด และเกลียดจุดที่เข้าใจยากและขัดกับสัญชาตญาณของ C กับ ecosystem รอบตัวมัน แต่ Zig ทำให้ผมสนุกกับ system programming เป็นครั้งแรก
นี่แค่เปลี่ยนแบ็กเอนด์ใช่ไหม? สงสัยว่าการวิเคราะห์และ type pass ทั้งหมดยังอยู่เหมือนเดิมหรือไม่ หรือว่าลดการตรวจสอบลงด้วย
รอบ compile ที่เร็วช่วยเรื่อง productivity ก็จริง แต่ผมคิดว่าจะเป็นแบบนั้นก็ต่อเมื่อรวมถึง การทดสอบที่เร็ว ด้วย
ถ้าอย่างนั้น สำหรับ debug เอา Zig ไปรันแบบ interpret เลยจะง่ายกว่าไหม? แบบนั้นน่าจะแก้ปัญหาที่ต้องทำงานซ้ำสำหรับแต่ละ target ได้ด้วย
gdbหรือlldbดูไม่น่าจะเป็นเรื่องเล็กน้อย เพราะเครื่องมือเหล่านั้นคาดหวัง executable ที่มีข้อมูลดีบัก DWARFเพิ่มเติมคือ โดยเฉพาะในวงการอย่าง game development ประสิทธิภาพของ debug mode ก็สำคัญมากจริง ๆ
ไม่มีความจำเป็นจริง ๆ ที่ต้องเพิ่ม interpreter การมี custom backend หมายความว่าตอนนี้ใช้สำหรับ debug แต่ในอนาคตที่ไกลกว่านั้นอาจแข่งขันกับ LLVM ในด้านความเร็วได้ด้วย
ต่อให้เพิ่ม interpreter สุดท้ายก็ยังต้องเขียน custom backend อยู่ดี จึงมีประโยชน์น้อย
ปัญหาคือ LLVM ช้าทั้งใน debug และ release
นี่ไม่ใช่หนึ่งใน prerequisite สำหรับนำ async/await กลับมาใน Zig หรือ?
https://github.com/ziglang/zig/wiki/FAQ#what-is-the-status-o...