- การเปรียบเทียบประสิทธิภาพของ Rust และ C เป็นประเด็นที่ซับซ้อน ซึ่งขึ้นอยู่กับนิยามของสมมติฐานว่า “ทุกเงื่อนไขเหมือนกัน”
- ในกรณีของ inline assembly ทั้งสองภาษาสามารถสร้างโค้ดแอสเซมบลีแบบเดียวกันได้ จึงไม่มีความต่างด้านความเร็วจากตัวภาษาเอง
- ในเรื่อง การจัดวางหน่วยความจำของ struct Rust อาจมีขนาดเล็กกว่าได้จากการจัดเรียงฟิลด์ใหม่ แต่ก็สามารถใช้แอตทริบิวต์
#[repr(C)] เพื่อให้มี layout แบบเดียวกับ C ได้
- เนื่องจากความต่างของ การตรวจสอบขณะรันไทม์และพฤติกรรมของนักพัฒนา โครงสร้างโค้ดและประสิทธิภาพในโปรเจกต์จริงจึงอาจแตกต่างกัน
- สรุปคือ ไม่มีความต่างด้านประสิทธิภาพที่เกิดจากข้อจำกัดของภาษาเอง และผลลัพธ์จะต่างกันตามปัจจัยของโปรเจกต์และนักพัฒนา
การตั้งคำถามและความกำกวมของสมมติฐาน
- จุดเริ่มต้นมาจากคำถามบน Reddit ว่า “ถ้าเงื่อนไขเหมือนกัน Rust จะเร็วกว่า C ได้หรือไม่”
- คำว่า “ทุกเงื่อนไขเหมือนกัน” เองเป็นแนวคิดที่นิยามได้ยากมากในการเปรียบเทียบภาษา
- การเปรียบเทียบประสิทธิภาพไม่ได้ขึ้นอยู่แค่ความต่างของภาษา แต่ยังขึ้นกับรูปแบบโค้ด การตัดสินใจของผู้พัฒนา และการเพิ่มประสิทธิภาพของคอมไพเลอร์
การเปรียบเทียบ inline assembly
- Rust รองรับ inline assembly ในระดับภาษา ขณะที่ C ทำสิ่งนี้ผ่าน ความสามารถส่วนขยายของคอมไพเลอร์
- ทั้งสองภาษาสามารถเขียนตัวอย่างเดียวกันที่ใช้คำสั่ง
rdtsc ได้
- แอสเซมบลีที่สร้างจาก
rustc 1.87.0 และ clang 20.1.0 ออกมาเหมือนกันทุกประการ
- กรณีนี้ไม่ได้แสดงให้เห็นความต่างด้านประสิทธิภาพของภาษา แต่พิสูจน์ว่า Rust สามารถควบคุมระดับล่างได้ในระดับเดียวกับ C
ความต่างของ layout ของ struct
- struct ของ Rust สามารถ เพิ่มประสิทธิภาพการใช้หน่วยความจำด้วยการจัดเรียงฟิลด์ใหม่ ได้
- ในตัวอย่าง struct ของ Rust มีขนาด 16 ไบต์ ส่วน struct เดียวกันใน C มีขนาด 24 ไบต์
- ใน C ต้องเปลี่ยนลำดับฟิลด์ด้วยตนเองจึงจะได้ขนาดเท่ากัน
- หากใช้แอตทริบิวต์
#[repr(C)] ใน Rust ก็สามารถบังคับให้มี layout หน่วยความจำแบบเดียวกับ C ได้
ปัจจัยทางสังคมและปัจจัยของนักพัฒนา
- ด้วย การตรวจสอบความปลอดภัยของ Rust จึงมีกรณีที่นักพัฒนากล้าลองเพิ่มประสิทธิภาพเชิงรุกมากขึ้น
- ในโปรเจกต์ Stylo ของ Mozilla ความพยายามทำ parallelization สองครั้งด้วย C++ ล้มเหลว แต่ทำสำเร็จเมื่อใช้ Rust
- แม้จะเป็นโปรเจกต์เดียวกัน แต่ ประสิทธิภาพและความเสถียรของโค้ดที่ได้ ก็อาจต่างกันตามภาษาและความชำนาญของนักพัฒนา
- ผลลัพธ์ของ “งานเดียวกัน” ย่อมต่างกันได้ตามระดับผู้เริ่มต้นหรือผู้เชี่ยวชาญ รวมถึงความคุ้นเคยกับภาษา จึงเทียบกันแบบตรงไปตรงมาได้ยาก
การตรวจสอบตอนคอมไพล์และตอนรันไทม์
- การตรวจสอบด้านความปลอดภัยของ Rust จำนวนมากเกิดขึ้น ตอนคอมไพล์ แต่บางส่วนยังคงเป็นการตรวจสอบตอนรันไทม์
- ตัวอย่างเช่น เมื่อเข้าถึง
array[0] Rust จะทำ bounds check แต่ C จะไม่ทำ
- หากใช้
get_unchecked() ใน Rust ก็จะได้พฤติกรรมแบบเดียวกับ C
- หากคอมไพเลอร์พิสูจน์ความปลอดภัยได้ ทั้งสองภาษาก็สามารถ ตัดการตรวจสอบออกผ่านการ optimize ได้
- ความต่างเหล่านี้ส่งผลต่อวิธีเขียนโค้ด และ ท้ายที่สุดอาจทำให้เกิดความต่างด้านประสิทธิภาพได้
บทสรุป
- ต่อให้สมมติว่า C เป็น “ภาษาที่เร็วที่สุด” ก็ ไม่มีเหตุผลว่าทำไม Rust จะให้ประสิทธิภาพในระดับเดียวกันไม่ได้
- มากกว่าข้อจำกัดของตัวภาษาเอง ปัจจัยที่ตัดสินความต่างด้านประสิทธิภาพคือ ลักษณะของโปรเจกต์ ความสามารถของนักพัฒนา ข้อจำกัดด้านเวลา และตัวแปรภายนอกอื่น ๆ
- ดังนั้นคำถามว่า “Rust เร็วกว่า C หรือไม่?” ควรถูกมองว่าเป็น ประเด็นด้านบริบททางวิศวกรรมมากกว่าการเปรียบเทียบตัวภาษา
9 ความคิดเห็น
ความเห็นจาก Hacker News
สรุปคือ ความเร็วสูงสุด แทบไม่ต่างกัน แต่ในโค้ดจริงความต่างค่อนข้างชัด
โดยเฉพาะ มัลติเธรดดิ้ง เป็นตัวแปรสำคัญ Rust บังคับให้ตัวแปร global ทั้งหมดต้อง thread-safe ไม่ว่าจะใช้เธรดหรือไม่ก็ตาม และ borrow checker ก็จำกัดการเข้าถึงหน่วยความจำให้เป็นได้แค่แบบแชร์หรือแบบแก้ไขอย่างใดอย่างหนึ่ง
เพราะงั้นใน Rust การเขียนโค้ดหลายเธรดจึงแทบจะเป็นค่าเริ่มต้นไปแล้ว ในทางกลับกัน C เองแม้แต่การสร้างเธรดยังเป็นภาระเพราะปัญหาความเข้ากันได้ของแพลตฟอร์มและความเสี่ยงในการดีบัก
ใน C การสร้างเธรดไม่ถึงกับยาก แต่ก็ยุ่งยากกว่า Rust แบบ
std::thread::spawn(move || { ... });มากกว่าความปลอดภัยของหน่วยความจำ สิ่งที่มีผลมากกว่าคือ โมเดล concurrency ของภาษา Go ก็ทำงานขนานได้ง่ายด้วย
go f()แม้จะไม่ memory-safeส่วนตัวผมกลับเจอ heisenbug ใน Go บ่อยกว่า
#pragma omp forบรรทัดเดียวก็ทำ parallelize ใน C ได้ง่ายเหมือนกันด้วย traits ของ Rust จึงสร้าง abstraction ที่ทั้งเร็วและยืดหยุ่นกว่าได้
ใน C พอเลียนแบบได้ด้วยแมโครหรือฟังก์ชันพอยน์เตอร์ แต่ใน Rust ผู้เรียกสามารถเลือกได้ว่าจะใช้ dynamic dispatch หรือ static dispatch
ในสภาพแวดล้อม embedded ฟังก์ชันพอยน์เตอร์ทำให้ cache เสียและลดประสิทธิภาพ แต่ Rust traits เปิดทางให้ inline optimization ได้ จึงมีประสิทธิภาพกว่ามาก
จะ Rust หรือ C สุดท้ายก็ต้องจัดการกันในระดับไบต์อยู่ดี และทุกวันนี้เครื่องมือ binary patching ก็พัฒนาดีขึ้นจนใช้งานง่าย
Box<dyn Trait>ใน function signature ก็เท่ากับบังคับให้ผู้เรียกใช้ dynamic dispatchแต่ถ้าใช้
impl Traitผู้เรียกก็ยังมีสิทธิ์เลือกได้ส่วนตัวผมมองว่า Rust, C และ C++ เป็น ตระกูลภาษา low-level ที่ใกล้กันมากอยู่แล้ว ดังนั้นความต่างด้านประสิทธิภาพจึงน้อยมาก
กฎ aliasing ที่เข้มงวด ของ Rust เอื้อต่อการ optimize ขณะที่ UB (undefined behavior) ของ C/C++ ก็มีอยู่เพื่อแลกกับประสิทธิภาพ
และ generics ของ Rust กับ C++ ก็ทรงพลังกว่า C มาก เช่น ถ้าเทียบการ sort ด้วยเทมเพลตกับ
qsort()แบบเดิม การทำ inline optimization ก็ง่ายกว่าเยอะผมคิดว่าการถกเถียงเรื่อง ความเร็วของภาษา แบบนี้ส่วนใหญ่ไม่มีความหมายเท่าไร
สิ่งที่กำหนดประสิทธิภาพจริง ๆ คือ การ implement ของคอมไพเลอร์ มากกว่าตัวภาษา
Rust, C และ C++ ต่างก็เป็น ภาษา low-level แต่คำว่า “เร็ว” ต้องนิยามให้ชัด
กำลังพูดถึงความเร็วสูงสุดของโค้ดที่ผู้เชี่ยวชาญ optimize แล้ว หรือกำลังพูดถึง โอกาสที่นักพัฒนาทั่วไปจะเขียนโค้ดเร็วได้ภายในงบประมาณที่มี กันแน่
แต่ถ้าปรับแต่งด้วยมือเต็มที่ ความต่างระหว่างภาษาก็แทบหายไป
อย่างไรก็ตาม Rust ยังพอได้เปรียบเล็กน้อยตรงที่เป็น ภาษาที่เขียนโค้ดเร็วให้เร็วได้ง่ายกว่า
เดิมทีผมคิดว่าจุดเด่นของ Rust คือ มัลติเธรดดิ้ง กับ การจัดสรรบนสแต็ก
ด้วยโมเดล ownership มันจึงเอาของไปวางบนสแต็กได้มากกว่า C/C++ และช่วยลด โอเวอร์เฮดของ malloc/free
ประเด็นแนวนี้มักเถียงกันด้วยอารมณ์เยอะ เลยอยากพูดถึง ความต่างของกรอบความคิด มากกว่าตัวเลขเฉพาะเจาะจง
เวลาพูดถึง “ความเร็ว” ของภาษา ต้องดูอยู่สองอย่าง
Rust กับ C แทบไม่มี runtime check จึงเร็วกว่าพวก Python หรือ JS
แต่ Rust ส่งข้อมูลเรื่อง aliasing ให้คอมไพเลอร์ได้ดีกว่า จึงมีพื้นที่ให้ optimize มากกว่า
ในโหมด debug มันช้าได้พอ ๆ กับ Ruby แต่ในโหมด release ก็ทำความเร็วได้ระดับ C
เมื่อเทียบกับ C แล้ว C++ หรือ Rust มี ความสามารถฝั่ง compile time ที่มากกว่า จึงเขียนโค้ดเร็วได้ง่ายกว่า
ตัวอย่างเช่น โค้ดนี้ แทบเป็นไปไม่ได้เลยใน C
ถ้าเป็น C จะต้องพึ่ง เครื่องมือภายนอก อย่าง re2c
เนื่องจากแอสเซมบลีไม่ได้เป็นส่วนหนึ่งของมาตรฐาน C จึงเทียบกับ Rust โดยตรงได้ยาก และ Rust เองก็มีลักษณะใกล้กับ โปรเจกต์ GCC มากกว่า
สุดท้ายแล้วภาษาไหนจะ “เร็ว” ก็ขึ้นอยู่กับ implementation และบริบท
มากกว่าความเร็วของตัวภาษาเอง สิ่งที่มีผลมากกว่าคือ การจับคู่กันของคอมไพเลอร์กับฮาร์ดแวร์
โดยเฉลี่ยแล้วผมไม่แน่ใจว่าภาษาไหนเร็วที่สุด แต่คิดว่าความกระจายของผลลัพธ์น่าจะมากที่สุดสำหรับ C++
ในงาน embedded เราเขียนโค้ดโดยคำนึงถึงขนาด cache line ของฮาร์ดแวร์ด้วยอยู่แล้ว ประเด็นคงอยู่ที่ว่านักพัฒนาจะทำ optimization ขั้นสุดบนระดับภาษาได้ไกลแค่ไหน รวมถึงเรื่องประสิทธิภาพของ standard library และ compiler แต่ยังไงทั้งคู่ก็รองรับงาน low-level เหมือนกัน ดังนั้นความต่างของ overhead เล็กน้อยก็น่าจะอยู่ในระดับที่แทบไม่มีนัยสำคัญ เพราะงั้นเลยดูไม่ใช่ประเด็นถกเถียงที่มีความหมายเท่าไรนัก.. ถ้าต้องการ optimization แบบสุดทาง สุดท้ายก็ต้องอาศัยการแทรกแซงของมนุษย์อยู่ดี เพราะ compiler ไม่ได้สมบูรณ์แบบอย่างที่คิด
ผมคิดว่า Rust น่าจะเป็นตัวแทนของ C++ มากกว่า C สำหรับ C แล้ว มันแทบจะเป็นภาษาเดียว (หรืออาจเป็นภาษาสุดท้าย) ที่เราพอจะคาดเดาโค้ดที่คอมไพเลอร์สร้างออกมาได้…
zigก็ไม่เลวนะ... ฮือพอเขียนไปเขียนมา มันกลายเป็นสไตล์สรุปโดย AI ซะงั้น ฮือๆ
ไม่ได้ตั้งใจแกล้งกันใช่ไหมครับ ฮุฮุ
ขึ้นอยู่กับความสามารถของคอมไพเลอร์
ถ้าลองแอสเซมเบิลโค้ดเดียวกันดูก็น่าจะรู้ครับ
ดูเหมือนว่าฝั่งพี่ ๆ ffmpeg จะคิดว่า rust ไม่ได้เร็วกว่า c นะ 555 https://www.memorysafety.org/blog/rav1d-perf-bounty/