ภาษาโปรแกรมมิ่ง Rust ยังคงเผชิญกับการวิพากษ์วิจารณ์เรื่องเวลาคอมไพล์ที่ช้าอย่างฉาวโฉ่ แม้จะมีการปรับปรุงอย่างมีนัยสำคัญจากทีมพัฒนาในช่วงหลายปีที่ผ่านมา การวิเคราะห์โดยละเอียดจากกลุ่มงานด้านประสิทธิภาพคอมไพเลอร์ Rust เผยให้เห็นความท้าทายที่ซับซ้อนเบื้องหลังปัญหาที่ยังคงมีอยู่นี้ และเหตุใดการแก้ไขอย่างรวดเร็วจึงยังคงเป็นสิ่งที่เข้าถึงได้ยาก
การปรับปรุงประสิทธิภาพของ Rust Compiler (2021-2024)
- ความเร็วในการคอมไพล์โดยรวม: เร็วขึ้น ~2 เท่าในช่วง 3 ปี
- การลดเวลาในการ build โปรเจคตัวอย่าง: 1 นาที 15 วินาที → 1 นาที 9 วินาที
- การติดตามประสิทธิภาพ: ชุดเบนช์มาร์คที่ครอบคลุมทำงานหลังจากทุก PR ที่ถูก merge
- กระบวนการคัดกรองปัญหาประสิทธิภาพที่ถดถอยแบบรายสัปดาห์
สถาปัตยกรรมทางเทคนิคสร้างคอขวดขั้นพื้นฐาน
สาเหตุหลักของความช้าในการคอมไพล์ของ Rust มาจากการตัดสินใจด้านสถาปัตยกรรมในระดับลึกที่ให้ความสำคัญกับประสิทธิภาพรันไทม์มากกว่าความเร็วในการคอมไพล์ การออกแบบภาษารอบ zero-cost abstractions หมายความว่าคอมไพเลอร์จะสร้างโค้ด intermediate representation (IR) จำนวนมากที่ต้องได้รับการปรับปรุงอย่างหนักโดย LLVM ซึ่งเป็นโครงสร้างพื้นฐานคอมไพเลอร์แบ็กเอนด์ แนวทางนี้สร้างการแลกเปลี่ยนขั้นพื้นฐานที่การบรรลุประสิทธิภาพรันไทม์ที่เร็วต้องใช้การประมวลผลในเวลาคอมไพล์อย่างมาก
ชุมชนได้ระบุ LLVM เป็นคอขวดหลัก แต่ปัญหาขยายไปเกินกว่าแค่แบ็กเอนด์ ระบบ monomorphization ของ Rust ซึ่งสร้างเวอร์ชันเฉพาะของฟังก์ชัน generic สำหรับแต่ละประเภทที่ใช้งาน ทำให้ปริมาณโค้ดที่ต้องคอมไพล์เพิ่มขึ้นเป็นทวีคูณ นอกจากนี้ โมเดลการคอมไพล์แบบ crate-based แม้จะให้ความสามารถในการแยกส่วนที่ยอดเยี่ยม แต่อาจนำไปสู่หน่วยการคอมไพล์ที่ใหญ่กว่าที่ภาษาอื่นๆ มักจะจัดการ
ข้อจำกัดทางเทคนิคที่ระบุได้
- การปรับปรุงประสิทธิภาพ LLVM backend : เป็นข้อจำกัดหลักสำหรับโครงการจำนวนมาก
- Monomorphization : สร้างสำเนาหลายชุดของฟังก์ชันแบบ generic
- การคอมไพล์ dependency : ต้องสร้างใหม่สำหรับแต่ละโครงการ
- การคอมไพล์แบบเพิ่มหน่วย : มีประสิทธิภาพจำกัดใน workspace ขนาดใหญ่
- ขั้นตอน linking : ใช้เวลาเป็นส่วนสำคัญของเวลาสร้างทั้งหมด
การเติบโตของระบบนิเวศเร็วกว่าการปรับปรุงคอมไพเลอร์
แม้ว่าคอมไพเลอร์ Rust จะมีประสิทธิภาพเพิ่มขึ้นเกือบสองเท่าในช่วงสามปีที่ผ่านมา แต่การเติบโตอย่างรวดเร็วของระบบนิเวศได้ชดเชยผลประโยชน์เหล่านี้ไปหลายส่วน โปรเจ็กต์ Rust สมัยใหม่มักจะรวม dependencies หลายสิบหรือหลายร้อยตัว ซึ่งแต่ละตัวต้องการการคอมไพล์ ชุมชนสังเกตว่าไลบรารีต่างๆ ยังคงเพิ่มฟีเจอร์และ dependencies เร็วกว่าที่คอมไพเลอร์จะสามารถปรับปรุงให้เหมาะสมได้
สิ่งนี้สร้างประสบการณ์ที่น่าหงุดหงิดที่โปรเจ็กต์แต่ละตัวอาจเห็นเวลาบิลด์เพิ่มขึ้นแม้จะใช้คอมไพเลอร์ที่เร็วขึ้น การระเบิดของ dependency ส่งผลกระทบเป็นพิเศษต่อการบิลด์ครั้งแรก ที่นักพัฒนาต้องคอมไพล์ dependency tree ทั้งหมดตั้งแต่เริ่มต้น บางโปรเจ็กต์รายงานเวลาการ clean build มากกว่า 10 นาทีสำหรับโค้ดเบสที่ค่อนข้างเจียมเนื้อเจียมตัวเมื่อรวม dependencies ทั้งหมด
เวลาในการ Build ที่รายงานโดยชุมชน
- โปรเจกต์ Rust ขนาดเล็ก (โค้ดไม่กี่พันบรรทัด): 1-10 นาทีสำหรับการ clean build
- โปรเจกต์ขนาดกลาง (40k บรรทัด + dependencies): 1-2 นาที
- เปรียบเทียบโปรเจกต์ C++ ขนาดใหญ่: 30 นาทีบนฮาร์ดแวร์ระดับไฮเอนด์
- โปรเจกต์เทียบเท่าใน Go : 2-40 วินาทีรวม dependencies
ความท้าทายขององค์กรจำกัดการปรับปรุงครั้งใหญ่
โปรเจ็กต์ Rust ดำเนินการเป็นความพยายามโอเพ่นซอร์สที่ขับเคลื่อนโดยอาสาสมัคร ซึ่งสร้างความท้าทายที่เป็นเอกลักษณ์สำหรับการจัดการกับการปรับปรุงประสิทธิภาพขนาดใหญ่ การเปลี่ยนแปลงสถาปัตยกรรมหลักต้องการการประสานงานข้ามกลุ่มงานหลายกลุ่มและการรักษาความเข้ากันได้กับการพัฒนาที่กำลังดำเนินอยู่ ไม่เหมือนกับภาษาที่ได้รับการสนับสนุนจากบริษัทที่สามารถอุทิศทีมงานให้กับปัญหาเฉพาะ Rust พึ่งพาผู้มีส่วนร่วมที่ทำงานในประเด็นที่พวกเขาสนใจ
ผลประโยชน์ใหญ่ๆ จะได้รับในภายหลังของไปป์ไลน์ แต่สิ่งเหล่านี้ไม่ใช่สิ่งที่ทำให้คอมไพเลอร์ Rust ช้า คุณจะไม่ได้รับผลประโยชน์ใหญ่ๆ จากการเปลี่ยนแปลงสิ่งเหล่านี้
การปรับปรุงที่มีแนวโน้มดีที่สุดจะต้องการการเปลี่ยนแปลงขั้นพื้นฐานต่อสถาปัตยกรรมภายในของคอมไพเลอร์ ซึ่งอาจใช้เวลาหลายปีในการดำเนินการในขณะที่รักษาโค้ดเบสที่มีอยู่ ความพยายามเหล่านี้แข่งขันกับงานที่ให้ผลตอบแทนทันทีมากกว่า เช่น การเพิ่มฟีเจอร์ภาษาใหม่หรือการแก้ไขบัก
การเปรียบเทียบกับภาษาอื่นเน้นการแลกเปลี่ยน
การถกเถียงเรื่องความเร็วในการคอมไพล์มักจะมุ่งเน้นไปที่การเปรียบเทียบกับ Go ซึ่งได้รับการออกแบบอย่างชัดเจนสำหรับการคอมไพล์ที่เร็วตั้งแต่เริ่มต้น Go บรรลุเวลาบิลด์ในระดับวินาทีสำหรับโปรเจ็กต์ขนาดใหญ่โดยการเลือกการออกแบบที่แตกต่าง รวมถึงระบบประเภทที่เรียบง่ายกว่าและการหลีกเลี่ยงการปรับปรุงที่ซับซ้อนที่ Rust ดำเนินการ อย่างไรก็ตาม สิ่งนี้มาพร้อมกับต้นทุนของประสิทธิภาพรันไทม์และการรับประกันความปลอดภัยของหน่วยความจำที่ Rust ให้
นักพัฒนา C++ กลับบ่นเรื่องเวลาคอมไพล์ของ Rust บ่อยครั้งแม้จะทำงานกับภาษาที่ฉาวโฉ่เรื่องการบิลด์ที่ช้า ความแตกต่างอยู่ที่กลยุทธ์การคอมไพล์แบบเพิ่มหน่วย - โปรเจ็กต์ C++ สามารถจัดโครงสร้างด้วยการจัดการ header อย่างระมัดระวังและหน่วยการคอมไพล์แบบแยกส่วน ในขณะที่เครื่องมือปัจจุบันของ Rust ไม่ให้ความยืดหยุ่นที่เทียบเท่า
ทีม Rust ยังคงทำงานในโซลูชันรวมถึงแบ็กเอนด์ Cranelift สำหรับการบิลด์ดีบักที่เร็วขึ้นและการปรับปรุงการคอมไพล์แบบเพิ่มหน่วย อย่างไรก็ตาม การปรับปรุงเหล่านี้แสดงถึงการเปลี่ยนแปลงแบบวิวัฒนาการมากกว่าการปฏิวัติ ความตึงเครียดขั้นพื้นฐานระหว่างเวลาคอมไพล์และประสิทธิภาพรันไทม์ยังคงเป็นลักษณะเฉพาะที่กำหนดของภาษา ซึ่งสะท้อนถึงการตัดสินใจอย่างมีสติที่จะให้ความสำคัญกับความปลอดภัยและความเร็วของการดำเนินการมากกว่าความเร็วของการคอมไพล์
อ้างอิง: Why doesn't Rust care more about compiler performance?