1 คะแนน โดย GN⁺ 2 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ภายใน 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 ความคิดเห็น

 
GN⁺ 2 시간 전
ความคิดเห็นจาก Hacker News
  • ลองย้ายโค้ดบางส่วนไปใช้ Zig 0.16.0 แล้ว ผลลัพธ์ค่อนข้างน่าพอใจ
    แม้ว่าส่วนที่ได้รับผลกระทบจะมี จำนวนมากพอสมควร แต่ตัวการเปลี่ยนแปลงเองถือว่าดี และดูเป็นทิศทางที่ทำให้อนาคตของภาษานี้สดใส
    โดยเฉพาะ กลไกอินพุต/เอาต์พุตแบบใหม่ ที่เปิดทางให้เขียนโค้ดได้อย่างมีประสิทธิภาพและดูดี ทั้งในงานแบบเธรดเดียว หลายเธรด และการทำ event loop
    ถ้ายังไม่ได้ลองใช้ Zig หลัง 0.16.0 แนะนำให้ไปดูเลย release notes รอบนี้ยาวมาก
    https://ziglang.org/download/0.16.0/release-notes.html

    • ตอนนี้ยังเรียกว่า “มีประสิทธิภาพมาก” ได้ไม่เต็มปาก Io ยังเป็น dynamic dispatch และยังมีการอ้างอิงทางอ้อมหลายชั้น
      เท่าที่รู้มันยังช้ากว่าเดิมด้วย
      คาดว่าในรีลีสถัด ๆ ไปน่าจะจัดการปัญหาที่ว่า “เป้าหมายของ dispatch รู้ได้ตั้งแต่คอมไพล์ไทม์ แต่ยังเป็นแบบ dynamic อยู่” เพื่อให้เสียประสิทธิภาพน้อยลง
    • กลไกอินพุต/เอาต์พุตใหม่ โดยเฉพาะ async/await ค่อนข้างเข้าใจยากว่าทำงานยังไง
      ถ้าเรียก io.async บน implementation ของ IO ที่อิง event loop ผมเดาว่าภายในมันจะเริ่มอะไรสักอย่างคล้าย “task” และ future ก็คือ handle ของมัน
      ส่วนที่ไม่เข้าใจคือเวลาที่เรียก future.await(io) implementation ของ IO จะหยุดฟังก์ชันปัจจุบันไว้ก่อนแล้วค่อยกลับมาทำต่อเมื่อ future ถูก resolve แล้วหรือเปล่า? ถ้าใช่ นั่นแปลว่าทุกฟังก์ชันใน Zig เป็น stackless coroutine ใช่ไหม?
    • สักวันก็คงได้ใช้ฟีเจอร์ใหม่พวกนี้ แต่ตอนนี้เพราะเรื่องเสถียรภาพและ target ที่ยังทดสอบไม่มากพอ เลยยังใช้ .use_llvm = true ในบิลด์ Zig อยู่
  • หลังจากใช้ Zig มาหลายเดือน ผมเริ่มมั่นใจว่ามันเป็น ภาษาสำหรับทำเครื่องมือ ที่ยอดเยี่ยม
    เหมาะมากเวลาอยากหยิบขึ้นมาปั้นไอเดียแบบเร็ว ๆ สบาย ๆ และทุกจุดที่มักติดขัด คนสร้างภาษาก็ดูเหมือนจะคิดทางออกที่ใช้งานสบายไว้ให้แล้ว
    แต่ก็ไม่ได้บังคับว่าต้องใช้ภาษาแบบ “ถูกต้อง” เท่านั้น
    ตอนนี้มันกลายเป็นภาษาหลักของผมเวลาจะ “ลงมือประดิษฐ์อะไรเล่น ๆ ในโรงรถ” ไปแล้ว

    • จะบอกว่าไม่ได้บังคับวิธีใช้ที่ “ถูกต้อง” ก็คงไม่เต็มนัก เพราะมัน ไม่ยอมให้มีตัวแปรที่ไม่ได้ใช้ และก็ไม่มี multiline comment
      สำหรับผม นี่เป็นปัญหาเรื่อง productivity พอสมควร
    • มันดีขนาดนั้นเลยเหรอ?
      ภาษาหลักของผมสำหรับ “ทำอะไรเล่น ๆ ในโรงรถ” คือ Python มาตลอด ทั้งไวยากรณ์เบา ใช้งานไม่ขวางมือ standard library ก็แน่น ของที่ไม่มีส่วนใหญ่ก็หาได้จากแพ็กเกจ
      จุดเด่นของ Zig คืออะไร?
    • จุดติดหลักของผมในเรื่องนี้คือ ผมคิดว่า Mojo น่าจะกลายเป็นภาษาหลักสำหรับการทดลองของผมมากกว่า
    • มันเป็นภาษาสำหรับทดลองที่ดีมากจริง แต่ทีม Zig กับคอมมูนิตี้ก็มี รสนิยมที่ชัดและแรงมาก ว่าควรใช้ภาษานี้แบบไหนถึงจะ “ถูกต้อง”
  • ดูวิดีโอสัมภาษณ์ Andrew Kelley แล้วทำให้อยากลองเรียนรู้ Zig: https://www.youtube.com/watch?v=iqddnwKF8HQ

    • ผมเคารพ Andrew มากและก็ใช้ Zig อย่างสนุก แต่บทสัมภาษณ์นั้นแย่มาก
      คำตอบของ Andrew เองโอเคนะ แต่บรรยากาศโดยรวมมันให้ความรู้สึก ประจบเกินไป
  • อยากลองใช้ Zig มานานแล้ว แต่ภาษานี้ยังเปลี่ยนเร็วเกินไป
    แต่ละรีลีสมีการ ทำให้ API เดิมใช้ไม่ได้ เลยยากที่จะตามให้ทันไปพร้อมกันทั้งการเรียนภาษา การดีบัก build system และการลงมือทำสิ่งที่อยากสร้างจริง ๆ
    ดูบทสัมภาษณ์กับ JetBrains แล้วก็อยากลองใหม่อีกครั้ง แต่คงจะรอจนกว่า 1.0 จะออกมาก่อน

  • ผมคิดเรื่องที่เรียกว่า การเขียนโปรแกรมแบบคู่ มานานแล้ว
    คือการมีสแต็กภาษาแค่สองภาษา ได้แก่ภาษาระดับสูงหนึ่งภาษาและภาษาระดับต่ำหนึ่งภาษา
    แนวคิดคือเขียนให้มากที่สุดเท่าที่ทำได้ในภาษาระดับสูง แล้วค่อยลงไปใช้ภาษาระดับต่ำเมื่อจำเป็นจริง ๆ
    ปัญหาคือ ถ้าไม่ได้เชี่ยวชาญภาษาระดับต่ำอยู่แล้ว ก็มีโอกาสสูงว่าต้องกลับไปทำความคุ้นเคยใหม่ก่อนจะเริ่มงานระดับต่ำได้
    เพราะแบบนั้น C++ หรือ Rust เลยใช้งานยากกว่า C และสำหรับผม C จึงกลายเป็นตัวเลือกตั้งต้น แต่ C เองก็มีปัญหาที่รู้กันดี
    Zig ดูเหมือนจะเติมเต็ม จุดลงตัวนั้น ได้ดี เพราะมันเรียบง่ายพอที่จะกลับมาหยิบใช้อีกครั้งได้แม้เว้นไปนาน และก็ยังมีเครื่องมือสมัยใหม่ที่ช่วยให้เขียนโปรแกรมได้ง่ายขึ้น

    • ไอเดียนั้นน่าสนใจนะ แต่ผมคิดกลับกัน
      คือทำให้มากที่สุดเท่าที่ทำได้ใน ภาษาระดับต่ำ แล้วค่อยย้ายขึ้นไปใช้ภาษาระดับสูงเมื่อความสะดวกคุ้มกับต้นทุนที่ต้องจ่าย
      Roc ทำแบบนี้ได้ ทุกโปรแกรมจะมี platform ที่เขียนด้วยภาษาระดับต่ำ และโปรแกรม Roc จะใช้ API ที่ platform นั้นเปิดให้
      https://www.roc-lang.org/
      แน่นอนว่าจะแบ่งสมดุลระหว่างระดับสูงกับระดับต่ำอย่างไรก็เป็นเรื่องที่แต่ละคนเลือกเอง
    • เท่าที่รู้ SpaCy ก็พัฒนาในลักษณะนั้น
      ตัวโมเดลเขียนด้วย Cython และ API สำหรับผู้ใช้ก็ให้ผ่าน Python
    • C# ก็เข้าใกล้เป้าหมายนั้นมากพอสมควร
    • หรือจะใช้ภาษาเดียวที่ทำได้ดีทั้ง การเขียนโปรแกรมระดับสูงและระดับต่ำ แบบ Rust ก็ได้
      ตอนนี้ผมทำทุกอย่างด้วย Rust และโดยเฉพาะเพราะมี type system แบบคล้าย OCaml ผมยังไม่เจออะไรที่มันทำไม่ได้
    • คู่แบบนี้ที่เคยนิยมกันมากคือ C กับ Lua
      Lua ถูกออกแบบมาให้เป็นภาษาแบบ embedded เลยเชื่อมต่อใช้งานร่วมกันได้ดี และในขณะเดียวกันก็เป็นภาษาระดับสูงที่ใช้ง่าย
      มีเหตุผลที่ Factorio ใช้ Lua เป็นภาษา scripting
  • สิ่งที่ผมชอบในการพัฒนา Zig คือพวกเขาทุ่มแรงไปกับ เครื่องมือและวงจร feedback ของนักพัฒนา อย่างมาก มากกว่าการเพิ่มฟีเจอร์ภาษาเสียอีก
    ภาษาตัวใหม่ยังพออยู่รอดได้แม้ขาดฟีเจอร์บางอย่างไปสักพัก
    แต่ถ้าทุกครั้งที่คอมไพล์ ลิงก์ หรืออัปเดต dependency แล้วรู้สึกช้า มันจะอยู่รอดได้ยากกว่ามาก
    การโฟกัสกับการทำให้รอบการพัฒนาอยู่ในระดับ มิลลิวินาที ไม่ใช่วินาที ดูเป็นการเดิมพันระยะยาวที่ดี

  • แปลกใจที่บอกว่า “มีแผนจะปล่อย 0.17.0 ภายในไม่กี่สัปดาห์”
    0.16 ใช้เวลาไป เกินหนึ่งปี ไม่ใช่เหรอ?
    ไม่คิดว่าจะมี 0.17 ออกเร็วขนาดนี้ แต่เพิ่งรู้วันนี้แล้วดีใจมาก

    • หลัก ๆ เพราะการเปลี่ยน build system ครั้งนี้กับการ อัปเกรดเป็น LLVM 22 น่าจะเป็นการเปลี่ยนใหญ่เพียงอย่างเดียวของ 0.17.0: https://ziglang.org/download/0.16.0/release-notes.html#Roadm...
  • ฟังดูเป็นข่าวดี เวลา คอมไพล์ ของ Zig นั้นยอดเยี่ยมอยู่แล้ว และการเปลี่ยนแปลงนี้ก็น่าจะทำให้ดีขึ้นอีก

    • จากประสบการณ์ของผม คำพูดนั้นตอนนี้ยังคง ใกล้เคียงกับเป้าหมายมากกว่า
      มันเป็นเป้าหมายสำคัญอย่างไม่ต้องสงสัย และหมุดหมายในการไปให้ถึงก็ชัดเจน แต่ในทางปฏิบัติยังยากที่จะเรียกเวลารออันทรมานตอนคอมไพล์ครั้งแรกของโปรเจ็กต์เปล่า ๆ หรือเวลาที่ ZLS สร้างใหม่หลัง direnv allow ว่า “ยอดเยี่ยม”
    • ต่อให้สร้างไฟล์หนึ่งไฟล์ที่มีแค่ dummy test แล้วรัน 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
    • โชคดีที่ ความเร็วคอมไพล์แบบยุค 90s กำลังค่อย ๆ กลับมา
  • คงดีมากถ้า Zig มี กลไกอย่างเป็นทางการ สำหรับส่งออก Linux library stubs
    ความสามารถด้าน cross-compilation และการกำหนดเป้าหมาย glibc เวอร์ชันตามต้องการของ Zig นั้นเหมือนเวทมนตร์ล้วน ๆ
    ผมเอาเวทมนตร์นี้ไปใช้ในระบบบิลด์ C++ แยกต่างหากอยู่ แต่ถ้าจะดึง library stubs จาก Zig ก็ต้องอ้อมทาง
    ถ้ามีให้เป็น output อย่างเป็นทางการก็คงดี

  • มีเหตุผลอะไรที่ทำให้อยากใช้สิ่งนี้แทน Node.js กับ TypeScript ไหม?

    • อิจฉาวิธีคิดแบบนี้จัง ถ้าผมเป็นแบบนั้นบ้าง ป่านนี้คงปล่อยแอปออกไปแล้วหลายตัว
    • แนะนำให้ลองใช้ Ghostty ที่เขียนด้วย Zig สักพัก แล้วเทียบกับเทอร์มินัลอย่าง Hyper ที่เขียนด้วย JavaScript
    • หรือว่าเพราะทั้งคู่ถูกออกแบบมาสำหรับคนละโดเมนโดยสิ้นเชิง?
    • ถ้างานที่ทำเหมาะกับ Node และ TypeScript อยู่แล้ว ผมคิดว่านอกจากเรื่องการเรียนรู้หรือความอยากรู้อยากเห็น ก็ไม่มีเหตุผลจะใช้ Zig
      ถ้าไม่ได้ต้องการหยดสุดท้ายของประสิทธิภาพ หรือ layout และการควบคุมหน่วยความจำ Zig ก็มีข้อเสียมากกว่าข้อดี
      งาน CRUD หรืองาน “enterprise” หรือเว็บไซต์ แทบไม่ได้ประโยชน์จาก Zig เลย
    • ในระบบ embedded นั้นหน่วยความจำมีจำกัดจน รัน Node ไม่ได้
      โปรแกรม Zig ที่คอมไพล์แล้วอาจมีขนาดเพียงไม่กี่ KB โดยไม่มี dependency เลย
      การเข้าถึงอาร์เรย์ที่เขียนด้วยภาษาระดับล่างสามารถ optimize ด้วย SIMD และ parallelization ได้ และอาจเร็วกว่า doing งานเดียวกันใน JavaScript หลายลำดับขั้น
      ทั้งงานประมวลผลข้อความ การปรับแต่งภาพ การประมวลผลวิดีโอ การแฮช ฯลฯ ล้วนเห็นความต่างชัดเจน
      เหตุผลที่จะไม่ใช้ JavaScript นั้นแทบจะมีไม่สิ้นสุด