ชุมชนโปรแกรมเมอร์ C++ กำลังเป็นพยานในการถกเถียงที่ไม่เคยมีมาก่อนเกี่ยวกับหนึ่งในฟีเจอร์ที่ทะเยอทะยานที่สุดของภาษานี้ หลังจากผ่านมากว่าห้าปีนับตั้งแต่ C++20 ได้นำ modules มาใช้ เสียงของนักพัฒนาและผู้เชี่ยวชาญที่เพิ่มขึ้นเรื่อยๆ กำลังตั้งคำถามว่าฟีเจอร์นี้ควรจะอยู่ในมาตรฐานต่อไปหรือไม่
ความขัดแย้งนี้เกิดขึ้นจากความเป็นจริงที่โหดร้าย แม้จะมีความพยายามในการพัฒนามาหลายปี C++ modules ก็ยังล้มเหลวในการทำตามสัญญาหลักที่จะทำให้เวลาการคอมไพล์เร็วขึ้นอย่างมาก สิ่งที่เคยได้รับการยกย่องว่าเป็นทางออกสำหรับปัญหาความเร็วในการ build ที่มีชื่อเสียงของ C++ กลับกลายเป็นแหล่งความหงุดหงิดสำหรับนักพัฒนาที่พยายามใช้แนวปฏิบัติ C++ สมัยใหม่
คำสัญญาด้านประสิทธิภาพที่ไม่เคยเป็นจริง
เมื่อ C++ modules ถูกเสนอครั้งแรก จุดขายหลักนั้นชัดเจน คือการปรับปรุงความเร็วการคอมไพล์อย่างมหาศาล ระบบการรวม header แบบดั้งเดิมสร้างอัลกอริทึม O(N²) ที่โค้ดเดียวกันถูกแยกวิเคราะห์ซ้ำๆ ในไฟล์ source หลายไฟล์ Modules ควรจะแก้ไขปัญหานี้โดยการเก็บโค้ดที่ผ่านการประมวลผลไว้ในรูปแบบไบนารีที่สามารถโหลดจากดิสก์ได้อย่างรวดเร็ว
อย่างไรก็ตาม การทดสอบในโลกแห่งความเป็นจริงเผยให้เห็นเรื่องราวที่แตกต่างออกไป การใช้งานปัจจุบันแสดงให้เห็นการปรับปรุงเพียงเล็กน้อยที่ 10-20% ในกรณีที่ดีที่สุด ซึ่งห่างไกลจากผลประโยชน์ที่เปลี่ยนแปลงโฉมหน้าที่สัญญาไว้ในตอนแรก สมาชิกในชุมชนบางคนได้ค้นพบว่าเทคนิคที่มีอยู่แล้ว เช่น standard libraries ที่ออกแบบอย่างระมัดระวัง สามารถทำให้ความเร็วการคอมไพล์เพิ่มขึ้น 4 เท่าได้โดยไม่ต้องใช้ความซับซ้อนที่ modules นำมา
สถานการณ์จะน่าเป็นห่วงมากขึ้นเมื่อพิจารณาว่า precompiled headers ซึ่งเป็นเทคโนโลยีที่เก่ากว่ามาก มักจะให้ผลประโยชน์ด้านประสิทธิภาพที่คล้ายกันหรือดีกว่าด้วยความซับซ้อนในการใช้งานที่น้อยกว่ามาก สิ่งนี้ทำให้เกิดคำถามพื้นฐานว่าความพยายามทางวิศวกรรมขนาดใหญ่ที่ลงทุนใน modules นั้นคุ้มค่าหรือไม่
ประสิทธิภาพของโมดูลปัจจุบันเทียบกับความคาดหวัง
- คำสัญญาเดิม: เพิ่มความเร็วในการคอมไพล์ 5-10 เท่า
- ความเป็นจริงปัจจุบัน: ปรับปรุงได้เพียง 10-20% ในกรณีที่ดีที่สุด
- Precompiled Headers: มักให้ประสิทธิภาพเทียบเท่าหรือดีกว่า
- โซลูชันทางเลือก: ZapCC ทำความเร็วได้มากกว่า 5 เท่าโดยใช้เทคโนโลยีที่มีอยู่
ความวุ่นวายในการใช้งานระหว่าง Compilers
หนึ่งในแง่มุมที่สร้างความเสียหายมากที่สุดของการเปิดตัว modules คือการขาดการประสานงานระหว่างผู้จำหน่าย compiler และนักพัฒนา build system แต่ละ compiler หลักได้ใช้งาน modules แตกต่างกัน ทำให้เกิดระบบนิเวศที่แยกส่วนซึ่งโค้ดที่ทำงานกับ toolchain หนึ่งอาจล้มเหลวกับอีกตัวหนึ่ง
ความท้าทายในการรวมระบบนั้นรุนแรงมากจนระบบ build ต้องสร้าง compiler flags เพิ่มเติมระหว่างการคอมไพล์ เก็บไว้ในไฟล์ชั่วคราว และส่งต่อไปยังคำสั่งการคอมไพล์ที่ตามมา ระดับความซับซ้อนนี้ตรงข้ามกับความเรียบง่ายที่สง่างามที่ modules ควรจะนำมาสู่การพัฒนา C++
เราไม่ต้องการเปลี่ยน compiler ให้เป็น build system กลายเป็นคำตอบทั่วไปจากนักพัฒนา compiler เมื่อเผชิญกับข้อเสนอเพื่อปรับปรุงการรวม module ซึ่งสร้างสถานการณ์ติดขัดที่ไม่มีฝ่ายใดต้องการรับผิดชอบในการทำให้ระบบทำงานได้อย่างราบรื่น
การขาด product owner ที่เป็นหนึ่งเดียวที่มีอำนาจเหนือส่วนที่เคลื่อนไหวทั้งหมดได้สร้างสิ่งที่หลายคนอธิบายว่าเป็นฝันร้ายแบบ kafkaesque ของความซับซ้อน หากไม่มีใครที่ได้รับอำนาจในการประสานงานระหว่างทีม compiler ผู้ดูแล build system และผู้ใช้งาน standard library modules จะยังคงติดอยู่ในสถานะของการทำงานบางส่วน
ความท้าทายหลักในการนำไปใช้งาน
- การแยกส่วนของ Compiler: แต่ละผู้ผลิตนำ modules ไปใช้งานแตกต่างกัน
- การรวมระบบ Build: ต้องการการจัดการไฟล์ชั่วคราวที่ซับซ้อน
- ปัญหาความสามารถในการพกพา: ไฟล์ binary ของ module ไม่สามารถพกพาระหว่าง compilers ได้ (ยกเว้น MSVC )
- การสนับสนุน Toolchain: การสนับสนุน module ของ Apple ยังคงระบุว่า "บางส่วน"
- โค้ดเก่า: ไม่สามารถผสม
include <vector>
และimport <vector>
ในโปรเจกต์เดียวกันได้
การเรียนรู้จากแนวทางทางเลือก
การอภิปรายของชุมชนได้เน้นแนวทางทางเลือกหลายแนวทางที่อาจจะประสบความสำเร็จมากกว่า นักพัฒนาบางคนชี้ไปที่ภาษาโปรแกรม D ซึ่งใช้งานระบบ module ที่สะอาดเมื่อกว่าสองทศวรรษที่แล้วและยังคงทำงานได้อย่างน่าเชื่อถือ แนวทางของ D รวมถึงฟีเจอร์เช่น closed namespaces และ semantic independence ที่ทำให้ modules แยกตัวและคาดเดาได้อย่างแท้จริง
ข้อเสนอแนะอื่นๆ มุ่งเน้นไปที่โซลูชันที่เรียบง่ายกว่าที่สามารถให้ประโยชน์ส่วนใหญ่ด้วยความซับซ้อนที่น้อยกว่ามาก คำสั่ง import ที่ตรงไปตรงมาที่ทำงานเหมือน include แต่ไม่มีการรั่วไหลของ context สามารถใช้งานได้ง่ายกว่ามากในขณะที่ยังคงเปิดใช้งานการปรับปรุง compiler ที่สำคัญ
เครื่องมือเช่น ZapCC ซึ่งบรรลุความเร็วการคอมไพล์ที่เพิ่มขึ้น 5 เท่าขึ้นไปผ่านกระบวนการ compiler ที่ต่อเนื่อง แสดงให้เห็นว่าการปรับปรุงเวลา build อย่างมากเป็นไปได้แล้วโดยใช้เทคโนโลยีที่มีอยู่ โซลูชันเหล่านี้ถูกละเลยเป็นส่วนใหญ่เพื่อสนับสนุนแนวทาง modules ที่ซับซ้อนกว่า
เส้นทางข้างหน้า
เมื่อการถกเถียงทวีความรุนแรงขึ้น ผลลัพธ์ที่เป็นไปได้หลายแบบกำลังถูกหารือกัน บางคนโต้แย้งว่าควรถอด modules ออกจากมาตรฐานทั้งหมดและเริ่มใหม่ด้วยแนวทางที่เรียบง่ายกว่า คนอื่นๆ แนะนำให้มุ่งเน้นไปที่ subset import std ซึ่งสามารถให้ประโยชน์ที่มีความหมายสำหรับการใช้งาน standard library โดยไม่ต้องใช้ความซับซ้อนทั้งหมดของ modules ทั่วไป
ความท้าทายพื้นฐานยังคงอยู่ที่ modules ต้องการการประสานงานอย่างกว้างขวางระหว่างองค์กรอิสระหลายแห่ง แต่ละแห่งมีลำดับความสำคัญและข้อจำกัดของตัวเอง หากไม่มีโครงสร้างการกำกับดูแลที่ชัดเจนและความมุ่งมั่นร่วมกันในการทำให้ modules ทำงานได้อย่างถูกต้อง ฟีเจอร์นี้อาจยังคงใช้งานได้เพียงครึ่งเดียวตลอดไป
ชุมชน C++ ตอนนี้เผชิญกับทางเลือกที่ยากลำบาก คือการลงทุนทรัพยากรต่อไปในฟีเจอร์ที่ล้มเหลวในการตอบสนองความคาดหวังอย่างต่อเนื่อง หรือยอมรับความท้าทายในการใช้งานและติดตามแนวทางทางเลือกที่สามารถส่งมอบการปรับปรุงความเร็วการคอมไพล์ที่นักพัฒนาต้องการอย่างยิ่ง
อ้างอิง: Nibble Stew