- ภายใน zig build ถูกแยกออกเป็นกระบวนการ configurer และ maker และกราฟการบิลด์ที่
build.zig สร้างขึ้นจะถูกซีเรียลไลซ์เป็นไฟล์คอนฟิกแบบไบนารี
- โปรเซสแม่จะเก็บไฟล์คอนฟิกไว้ในแคช และคอมไพล์ maker แบบอะซิงโครนัสในโหมด Release โดย maker จะถูกคอมไพล์เพียงครั้งเดียวต่อเวอร์ชัน Zig ลงใน แคชส่วนกลาง
- เมื่อผู้ใช้แก้ไข
build.zig จะไม่ต้องคอมไพล์ ระบบบิลด์ทั้งหมด ไปพร้อมกันอีกต่อไป ทำให้ภาระด้านเวลาจากการเพิ่มฟีเจอร์อย่าง --watch, --fuzz, --webui ลดลง
- เวลาเฉลี่ยในการรัน
zig build --help ลดจาก 150ms เหลือ 14.3ms และจำนวน CPU cycles, instructions และ cache references ก็ลดลงราว 94–96%
- API ส่วนใหญ่ยังคงเข้ากันได้ แต่การตรวจสอบ
b.args โดยตรงจะถูกแทนที่ด้วย addPassthruArgs() และ Zig 0.17.0 มีกำหนดออกภายในไม่กี่สัปดาห์
การเปลี่ยนโครงสร้างของระบบบิลด์
- บรานช์ขนาดใหญ่ ได้ถูก merge แล้ว โดยภายใน
zig build ถูกแยกเป็น โปรเซส configurer และ โปรเซส maker
- ในโครงสร้างเดิม ไฟล์
build.zig และอิมพลีเมนเทชันของระบบบิลด์ทั้งหมดจะถูกคอมไพล์รวมกันเป็นโปรเซสขนาดใหญ่หนึ่งตัวในโหมด Debug และหลังจาก build.zig สร้างกราฟการบิลด์ไว้ในหน่วยความจำแล้ว “build runner” จะเป็นผู้รันกราฟนั้น
- ในโครงสร้างใหม่
build.zig จะถูกคอมไพล์เป็นโปรเซส configurer ขนาดเล็กในโหมด Debug และกราฟการบิลด์จะถูกซีเรียลไลซ์เป็น ไฟล์คอนฟิกแบบไบนารี
- โปรเซสแม่ของ
zig build จะตรวจจับไฟล์คอนฟิก เก็บไว้ในแคชสำหรับการรันครั้งถัดไป และคอมไพล์ maker แบบอะซิงโครนัสในโหมด Release ซึ่งรับหน้าที่รันกราฟการบิลด์
- เมื่อไฟล์คอนฟิกและการคอมไพล์ maker พร้อมแล้ว maker จะรับไฟล์คอนฟิกไปทำงานต่อ โดยอาศัย แคชส่วนกลาง ทำให้ต้องคอมไพล์เพียงครั้งเดียวต่อ
zig version
ผลของการปรับปรุงความเร็ว
- เป้าหมายหลักคือการเพิ่มความเร็วของ
zig build และเมื่อผู้ใช้แก้ไข build.zig ก็จะไม่ต้องคอมไพล์ ระบบบิลด์ทั้งหมด ไปพร้อมกันอีกต่อไป
- เมื่อมีการเพิ่ม
--watch, --fuzz, --webui เข้ามา ความหมายของการเปลี่ยนแปลงนี้ยิ่งชัดเจน เพราะต่อให้ฟังก์ชันของระบบบิลด์เพิ่มขึ้น เวลาของ zig build ก็ไม่ควรเพิ่มตามไปด้วย
- หากตัดสินได้ว่าไม่มีการเปลี่ยนแปลง ก็สามารถนำคอนฟิกเดิมกลับมาใช้ได้โดยไม่ต้องรันลอจิกของ
build.zig ซ้ำอีกครั้ง
- ตัวอย่างเช่น ต่อให้เพิ่ม
-freference-trace ในบรรทัดคำสั่งของ zig build ก็ไม่จำเป็นต้องรันลอจิกของ build.zig ใหม่โดยไม่จำเป็น
- เนื่องจากโปรเซสที่รันกราฟการบิลด์จริงถูกคอมไพล์โดย เปิดการปรับแต่งประสิทธิภาพ ขั้นตอนการรันจึงเร็วขึ้นด้วย
ผลการเบนช์มาร์ก
- เวลาเฉลี่ยในการรัน
zig build --help ลดลงจาก 150ms ในโครงสร้างเดิมเหลือ 14.3ms ในโครงสร้างใหม่
- เวลาแบบ wall-clock ลดจาก
150ms เหลือ 14.3ms คิดเป็น 90.4% ขณะที่ CPU cycles ลดจาก 593M เหลือ 24.1M หรือ 95.9%
- จำนวน instructions ลดจาก
995M เหลือ 43.7M หรือ 95.6% และ cache references ลดจาก 25.8M เหลือ 1.46M หรือ 94.3%
- ค่า peak RSS ลดจาก
84.8MB เหลือ 78.5MB หรือ 7.4%
- ความแตกต่างที่ใหญ่ที่สุดมาจากการเปลี่ยนจากวิธีที่รันลอจิก
build.zig สำหรับทุกคำสั่ง zig build ไปเป็นการนำคอนฟิกแบบซีเรียลไลซ์ที่แคชไว้กลับมาใช้ซ้ำ
ผลกระทบต่อเครื่องมือและความเข้ากันได้
- เครื่องมือภายนอก อย่าง ZLS สามารถได้ประโยชน์จากการเปลี่ยนไปใช้ไฟล์คอนฟิกที่ถูกซีเรียลไลซ์แล้ว แทนการ fork และดูแล build runner เอง
- แม้กลไกภายในจะเปลี่ยนไปมาก แต่ในมุมของ API แล้วส่วนใหญ่ยังคง รักษาความเข้ากันได้
- ข้อยกเว้นคือรายการเปลี่ยนแปลงที่ถูกรวบรวมไว้ใน PR ที่ merge แล้ว
การเปลี่ยนแปลงที่กระทบย้อนหลัง
- การเปลี่ยนแปลงที่มีแนวโน้มว่าผู้ใช้จำนวนมากจะเจอ คือการยกเลิกรูปแบบที่ตรวจสอบ
b.args โดยตรง
- โค้ดเดิม:
if (b.args) |args| {
run_cmd.addArgs(args);
}
run_cmd.addPassthruArgs();
- การเปลี่ยนแปลงนี้ทำให้สคริปต์บิลด์ไม่สามารถตรวจสอบอาร์กิวเมนต์ดังกล่าวได้อีกต่อไป จึงถือว่า มีการตัดฟังก์ชันหนึ่งออก
- แต่ข้อดีคือเมื่อเปลี่ยนอาร์กิวเมนต์เหล่านั้น ก็ไม่จำเป็นต้องสร้างสคริปต์บิลด์จากซอร์สใหม่อีก
การทดสอบและกำหนดการออกเวอร์ชัน
- ผู้ใช้ที่ต้องการมีอิทธิพลต่อทิศทางของ Zig สามารถอัปเกรดโปรเจกต์ไปใช้ เวอร์ชันพัฒนา เพื่อทดลองการเปลี่ยนแปลงใหม่และส่งข้อเสนอแนะได้
- Zig
0.17.0 มีกำหนดออกภายในไม่กี่สัปดาห์
- แม้จะไม่มีเวลาทดสอบล่วงหน้า และบิลด์อาจพังใน
0.17.0 ก็ยังมีโอกาสมากพอที่จะแก้ไขและใส่ไว้ในแท็ก 0.17.1
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ลองย้ายโค้ดบางส่วนไปใช้ Zig 0.16.0 แล้ว ผลลัพธ์ค่อนข้างน่าพอใจ
แม้ว่าส่วนที่ได้รับผลกระทบจะมี จำนวนมากพอสมควร แต่ตัวการเปลี่ยนแปลงเองถือว่าดี และดูเป็นทิศทางที่ทำให้อนาคตของภาษานี้สดใส
โดยเฉพาะ กลไกอินพุต/เอาต์พุตแบบใหม่ ที่เปิดทางให้เขียนโค้ดได้อย่างมีประสิทธิภาพและดูดี ทั้งในงานแบบเธรดเดียว หลายเธรด และการทำ event loop
ถ้ายังไม่ได้ลองใช้ Zig หลัง 0.16.0 แนะนำให้ไปดูเลย release notes รอบนี้ยาวมาก
https://ziglang.org/download/0.16.0/release-notes.html
เท่าที่รู้มันยังช้ากว่าเดิมด้วย
คาดว่าในรีลีสถัด ๆ ไปน่าจะจัดการปัญหาที่ว่า “เป้าหมายของ dispatch รู้ได้ตั้งแต่คอมไพล์ไทม์ แต่ยังเป็นแบบ dynamic อยู่” เพื่อให้เสียประสิทธิภาพน้อยลง
ถ้าเรียก
io.asyncบน implementation ของ IO ที่อิง event loop ผมเดาว่าภายในมันจะเริ่มอะไรสักอย่างคล้าย “task” และ future ก็คือ handle ของมันส่วนที่ไม่เข้าใจคือเวลาที่เรียก
future.await(io)implementation ของ IO จะหยุดฟังก์ชันปัจจุบันไว้ก่อนแล้วค่อยกลับมาทำต่อเมื่อ future ถูก resolve แล้วหรือเปล่า? ถ้าใช่ นั่นแปลว่าทุกฟังก์ชันใน Zig เป็น stackless coroutine ใช่ไหม?.use_llvm = trueในบิลด์ Zig อยู่หลังจากใช้ Zig มาหลายเดือน ผมเริ่มมั่นใจว่ามันเป็น ภาษาสำหรับทำเครื่องมือ ที่ยอดเยี่ยม
เหมาะมากเวลาอยากหยิบขึ้นมาปั้นไอเดียแบบเร็ว ๆ สบาย ๆ และทุกจุดที่มักติดขัด คนสร้างภาษาก็ดูเหมือนจะคิดทางออกที่ใช้งานสบายไว้ให้แล้ว
แต่ก็ไม่ได้บังคับว่าต้องใช้ภาษาแบบ “ถูกต้อง” เท่านั้น
ตอนนี้มันกลายเป็นภาษาหลักของผมเวลาจะ “ลงมือประดิษฐ์อะไรเล่น ๆ ในโรงรถ” ไปแล้ว
สำหรับผม นี่เป็นปัญหาเรื่อง productivity พอสมควร
ภาษาหลักของผมสำหรับ “ทำอะไรเล่น ๆ ในโรงรถ” คือ Python มาตลอด ทั้งไวยากรณ์เบา ใช้งานไม่ขวางมือ standard library ก็แน่น ของที่ไม่มีส่วนใหญ่ก็หาได้จากแพ็กเกจ
จุดเด่นของ Zig คืออะไร?
ดูวิดีโอสัมภาษณ์ Andrew Kelley แล้วทำให้อยากลองเรียนรู้ Zig: https://www.youtube.com/watch?v=iqddnwKF8HQ
คำตอบของ Andrew เองโอเคนะ แต่บรรยากาศโดยรวมมันให้ความรู้สึก ประจบเกินไป
อยากลองใช้ Zig มานานแล้ว แต่ภาษานี้ยังเปลี่ยนเร็วเกินไป
แต่ละรีลีสมีการ ทำให้ API เดิมใช้ไม่ได้ เลยยากที่จะตามให้ทันไปพร้อมกันทั้งการเรียนภาษา การดีบัก build system และการลงมือทำสิ่งที่อยากสร้างจริง ๆ
ดูบทสัมภาษณ์กับ JetBrains แล้วก็อยากลองใหม่อีกครั้ง แต่คงจะรอจนกว่า 1.0 จะออกมาก่อน
ผมคิดเรื่องที่เรียกว่า การเขียนโปรแกรมแบบคู่ มานานแล้ว
คือการมีสแต็กภาษาแค่สองภาษา ได้แก่ภาษาระดับสูงหนึ่งภาษาและภาษาระดับต่ำหนึ่งภาษา
แนวคิดคือเขียนให้มากที่สุดเท่าที่ทำได้ในภาษาระดับสูง แล้วค่อยลงไปใช้ภาษาระดับต่ำเมื่อจำเป็นจริง ๆ
ปัญหาคือ ถ้าไม่ได้เชี่ยวชาญภาษาระดับต่ำอยู่แล้ว ก็มีโอกาสสูงว่าต้องกลับไปทำความคุ้นเคยใหม่ก่อนจะเริ่มงานระดับต่ำได้
เพราะแบบนั้น C++ หรือ Rust เลยใช้งานยากกว่า C และสำหรับผม C จึงกลายเป็นตัวเลือกตั้งต้น แต่ C เองก็มีปัญหาที่รู้กันดี
Zig ดูเหมือนจะเติมเต็ม จุดลงตัวนั้น ได้ดี เพราะมันเรียบง่ายพอที่จะกลับมาหยิบใช้อีกครั้งได้แม้เว้นไปนาน และก็ยังมีเครื่องมือสมัยใหม่ที่ช่วยให้เขียนโปรแกรมได้ง่ายขึ้น
คือทำให้มากที่สุดเท่าที่ทำได้ใน ภาษาระดับต่ำ แล้วค่อยย้ายขึ้นไปใช้ภาษาระดับสูงเมื่อความสะดวกคุ้มกับต้นทุนที่ต้องจ่าย
Roc ทำแบบนี้ได้ ทุกโปรแกรมจะมี platform ที่เขียนด้วยภาษาระดับต่ำ และโปรแกรม Roc จะใช้ API ที่ platform นั้นเปิดให้
https://www.roc-lang.org/
แน่นอนว่าจะแบ่งสมดุลระหว่างระดับสูงกับระดับต่ำอย่างไรก็เป็นเรื่องที่แต่ละคนเลือกเอง
ตัวโมเดลเขียนด้วย Cython และ API สำหรับผู้ใช้ก็ให้ผ่าน Python
ตอนนี้ผมทำทุกอย่างด้วย Rust และโดยเฉพาะเพราะมี type system แบบคล้าย OCaml ผมยังไม่เจออะไรที่มันทำไม่ได้
Lua ถูกออกแบบมาให้เป็นภาษาแบบ embedded เลยเชื่อมต่อใช้งานร่วมกันได้ดี และในขณะเดียวกันก็เป็นภาษาระดับสูงที่ใช้ง่าย
มีเหตุผลที่ Factorio ใช้ Lua เป็นภาษา scripting
สิ่งที่ผมชอบในการพัฒนา Zig คือพวกเขาทุ่มแรงไปกับ เครื่องมือและวงจร feedback ของนักพัฒนา อย่างมาก มากกว่าการเพิ่มฟีเจอร์ภาษาเสียอีก
ภาษาตัวใหม่ยังพออยู่รอดได้แม้ขาดฟีเจอร์บางอย่างไปสักพัก
แต่ถ้าทุกครั้งที่คอมไพล์ ลิงก์ หรืออัปเดต dependency แล้วรู้สึกช้า มันจะอยู่รอดได้ยากกว่ามาก
การโฟกัสกับการทำให้รอบการพัฒนาอยู่ในระดับ มิลลิวินาที ไม่ใช่วินาที ดูเป็นการเดิมพันระยะยาวที่ดี
แปลกใจที่บอกว่า “มีแผนจะปล่อย 0.17.0 ภายในไม่กี่สัปดาห์”
0.16 ใช้เวลาไป เกินหนึ่งปี ไม่ใช่เหรอ?
ไม่คิดว่าจะมี 0.17 ออกเร็วขนาดนี้ แต่เพิ่งรู้วันนี้แล้วดีใจมาก
ฟังดูเป็นข่าวดี เวลา คอมไพล์ ของ Zig นั้นยอดเยี่ยมอยู่แล้ว และการเปลี่ยนแปลงนี้ก็น่าจะทำให้ดีขึ้นอีก
มันเป็นเป้าหมายสำคัญอย่างไม่ต้องสงสัย และหมุดหมายในการไปให้ถึงก็ชัดเจน แต่ในทางปฏิบัติยังยากที่จะเรียกเวลารออันทรมานตอนคอมไพล์ครั้งแรกของโปรเจ็กต์เปล่า ๆ หรือเวลาที่ ZLS สร้างใหม่หลัง
direnv allowว่า “ยอดเยี่ยม”zig test file.zig -OReleaseSafeบนเครื่องผมก็ยังใช้เวลาหลายวินาทีและทุกครั้งที่แก้ไฟล์ก็ต้องรอเวลาเดิมซ้ำอีก ผมใช้ 0.16 หรือไม่ก็ master อยู่ ดังนั้นไม่ใช่ว่า toolchain เก่า แถมยังเป็นสภาพแวดล้อม Linux
ตัวภาษา Zig เองนั้นเขียนสนุกมาก แต่ผมมองว่าคอมไพเลอร์และไลบรารีมาตรฐานยังไม่ได้ถูกพัฒนาแบบอนุรักษ์นิยมมากพอ
ถ้าเริ่มจาก hello world คุณอาจยังไม่เจอปัญหาแบบนี้ แต่พอจะทำ fuzz test หรือ benchmark คุณก็ย่อมอยากรัน ไบนารีที่เปิด optimization
แล้วมันก็กลายเป็นว่าน่าหงุดหงิดเกินไปแม้จะคอมไพล์โค้ดในปริมาณที่ค่อนข้างเล็ก
ถ้าจะเทียบกัน ผมคิดว่า Zig ในฐานะภาษาเหนือกว่า Rust/C++/C มาก แต่ปัญหาแบบนี้ใน Rust/C++/C แทบไม่ค่อยเกิดขึ้นเลย โดยสมมติว่าใน C/C++ ใช้ clang/gcc/ninja ฯลฯ
บนเครื่องเดียวกันนั้น ผมสามารถตั้งค่า บิลด์ (-O2 หรือ -O3) และทดสอบโปรเจ็กต์ C++ ราว 10,000 บรรทัดด้วย Ninja/Python/clang ได้ในเวลา 200ms
คงดีมากถ้า Zig มี กลไกอย่างเป็นทางการ สำหรับส่งออก Linux library stubs
ความสามารถด้าน cross-compilation และการกำหนดเป้าหมาย glibc เวอร์ชันตามต้องการของ Zig นั้นเหมือนเวทมนตร์ล้วน ๆ
ผมเอาเวทมนตร์นี้ไปใช้ในระบบบิลด์ C++ แยกต่างหากอยู่ แต่ถ้าจะดึง library stubs จาก Zig ก็ต้องอ้อมทาง
ถ้ามีให้เป็น output อย่างเป็นทางการก็คงดี
มีเหตุผลอะไรที่ทำให้อยากใช้สิ่งนี้แทน Node.js กับ TypeScript ไหม?
ถ้าไม่ได้ต้องการหยดสุดท้ายของประสิทธิภาพ หรือ layout และการควบคุมหน่วยความจำ Zig ก็มีข้อเสียมากกว่าข้อดี
งาน CRUD หรืองาน “enterprise” หรือเว็บไซต์ แทบไม่ได้ประโยชน์จาก Zig เลย
โปรแกรม Zig ที่คอมไพล์แล้วอาจมีขนาดเพียงไม่กี่ KB โดยไม่มี dependency เลย
การเข้าถึงอาร์เรย์ที่เขียนด้วยภาษาระดับล่างสามารถ optimize ด้วย SIMD และ parallelization ได้ และอาจเร็วกว่า doing งานเดียวกันใน JavaScript หลายลำดับขั้น
ทั้งงานประมวลผลข้อความ การปรับแต่งภาพ การประมวลผลวิดีโอ การแฮช ฯลฯ ล้วนเห็นความต่างชัดเจน
เหตุผลที่จะไม่ใช้ JavaScript นั้นแทบจะมีไม่สิ้นสุด