เหตุใด SQLite จึงเขียนด้วยภาษา C
(sqlite.org)- SQLite ถูกพัฒนาด้วย ภาษา C มาตั้งแต่ช่วงเริ่มต้น (ปี 2000) เพราะเหตุผลด้านประสิทธิภาพ, ความเข้ากันได้, การพึ่งพาภายนอกที่น้อย, และความเสถียร
- C ใช้งานได้บนแทบทุกระบบปฏิบัติการและทุกภาษา โดยเฉพาะเหมาะกับการเป็น ไลบรารีระดับต่ำ ที่ต้องทำงานได้รวดเร็ว
- เหตุผลที่เลือก C แทนภาษาเชิงวัตถุคือเรื่องความยืดหยุ่นในการขยาย, การเรียกใช้จากหลายภาษา, และเพราะในเวลาพัฒนานั้น C++ กับ Java ยังไม่สุกงอมพอ
- SQLite มีโครงสร้างแบบ ไฟล์เดียวที่แทบไม่มี dependency และใช้เพียงฟังก์ชันขั้นต่ำของไลบรารีมาตรฐาน C
- แม้จะมีการพูดถึงการเขียนใหม่ด้วย "ภาษาที่ปลอดภัย" อย่าง Rust และ Go แต่ในด้านการควบคุมคุณภาพ, ประสิทธิภาพ, และความสามารถในการถูกเรียกใช้เป็นไลบรารี C ก็ยังได้เปรียบอยู่
1. ทำไม C จึงเป็นตัวเลือกที่เหมาะสมที่สุด
- SQLite คงไว้เป็นภาษา C มาตั้งแต่เริ่มพัฒนาครั้งแรกเมื่อวันที่ 29 พฤษภาคม 2000 จนถึงปัจจุบัน
- ตอนนี้ยังไม่มีแผนจะเขียนใหม่ด้วยภาษาอื่น
- C มี ความสามารถในการควบคุมที่ใกล้กับฮาร์ดแวร์ ขณะเดียวกันก็พกพาได้สูง จนถูกเรียกว่า “แอสเซมบลีแบบพกพา”
- ภาษาอื่นอาจอ้างว่า “เร็วพอๆ กับ C” ได้ แต่ ไม่มีภาษาใดอ้างว่าเร็วกว่า C
1.1. ประสิทธิภาพ
- ไลบรารีระดับต่ำ อย่าง SQLite ถูกเรียกใช้งานบ่อยมาก จึงจำเป็นต้องทำงานให้เร็วอย่างยิ่ง
- ภาษา C เหมาะกับการเขียนโค้ดที่รวดเร็ว และยังเข้าถึงฮาร์ดแวร์ได้ใกล้ชิดโดยยังคงความสามารถในการพกพา
- ภาษาสมัยใหม่อื่นๆ ก็อาจบอกว่า ‘เร็วเท่า C’ แต่ในงานเขียนโปรแกรมทั่วไป ยังไม่มีภาษาใดที่มั่นใจได้ว่าเร็วกว่า C
- C ควบคุมหน่วยความจำและทรัพยากร CPU ได้ละเอียด จึงอาจให้ ประสิทธิภาพเร็วกว่าระบบไฟล์ 35% ได้ด้วย
- ตัวอย่าง: Internal vs External BLOBs
1.2. ความเข้ากันได้
- แทบทุกระบบสามารถเรียกใช้ ไลบรารีที่เขียนด้วย C ได้
- ตัวอย่างเช่น แม้แต่บน Android (ที่อิงกับ Java) ก็ยังใช้ SQLite ผ่าน adaptor ได้
- หาก SQLite ถูกเขียนด้วย Java ก็จะไม่สามารถใช้บน iPhone (Objective-C, Swift) ได้ ทำให้ความเป็นสากลลดลงอย่างมาก
1.3. การพึ่งพาภายนอกต่ำ
- เพราะพัฒนาเป็น ไลบรารี C จึงมี runtime dependency น้อยมาก
- ในการตั้งค่าขั้นต่ำ จะใช้เพียงฟังก์ชันพื้นฐานมากๆ ของไลบรารีมาตรฐาน C เท่านั้น เช่น memcmp(), memcpy(), memmove(), memset(), strcmp(), strlen(), strncmp()
- แม้ใน build ที่สมบูรณ์กว่าก็ยังมี dependency เพียงไม่กี่อย่าง เช่น malloc(), free(), การอ่านเขียนไฟล์ เป็นต้น
- ภาษาสมัยใหม่มักต้องพึ่งพา runtime ขนาดใหญ่จำนวนมาก และ อินเทอร์เฟซนับพันรายการ
1.4. ความเสถียร
- C เป็น ภาษาที่เก่าและเปลี่ยนแปลงน้อยจนน่าเบื่อ แต่นั่นก็หมายถึง ความคาดการณ์ได้และความเสถียร
- สำหรับการสร้าง เอนจินฐานข้อมูลที่เล็ก, เร็ว, และเชื่อถือได้ อย่าง SQLite ภาษาที่สเปกไม่เปลี่ยนบ่อยย่อมเหมาะกว่า
- หากสเปกของภาษาหรือ implementation เปลี่ยนอยู่บ่อยๆ ก็จะไม่เป็นผลดีต่อความเสถียรของ SQLite
2. ทำไมจึงไม่เขียนด้วยภาษาเชิงวัตถุ
- นักพัฒนาบางคนอาจคิดว่าหากไม่ใช้แนวคิดเชิงวัตถุก็คงยากที่จะสร้างระบบซับซ้อนอย่าง SQLite แต่เมื่อเทียบกับ C แล้ว การสร้างไลบรารีด้วย C++ หรือ Java จะทำให้ภาษาอื่นเรียกใช้ได้ยากกว่า
- เพื่อรองรับภาษาที่หลากหลาย เช่น Haskell, Java เป็นต้น การเลือก ไลบรารี C จึงสมเหตุสมผล
- เชิงวัตถุไม่ใช่ภาษา แต่เป็นรูปแบบการออกแบบ ดังนั้นจึงไม่ได้ผูกกับภาษาใดภาษาหนึ่ง
- แม้แต่ใน C ก็สามารถใช้ struct และ function pointer เพื่อสร้างแพตเทิร์นเชิงวัตถุได้
- โครงสร้างเชิงวัตถุไม่ได้ดีที่สุดเสมอไป บางครั้งโค้ดเชิงกระบวนการอาจชัดเจนกว่า ดูแลง่ายกว่า และให้ผลลัพธ์ที่เร็วกว่า
- ในช่วงเริ่มพัฒนา SQLite (ราวปี 2000)
- Java ยังไม่สุกงอม
- ส่วน C++ มี ปัญหาความเข้ากันได้ระหว่างคอมไพเลอร์ที่รุนแรง
→ ในเวลานั้น C จึงเป็น ทางเลือกที่ใช้งานได้จริงและปลอดภัยที่สุด
- แม้ในปัจจุบัน ก็ยังมีข้อดีไม่มากพอที่จะคุ้มกับการเขียน SQLite ใหม่
3. ทำไมจึงไม่เขียนด้วย "ภาษาที่ปลอดภัย"
- ช่วงหลังมานี้มีความสนใจใน ภาษาการเขียนโปรแกรมที่ปลอดภัย อย่าง Rust และ Go มากขึ้น แต่ตอนที่ SQLite เริ่มพัฒนาขึ้นมา (รวมถึงในช่วง 10 ปีแรก) ภาษาเหล่านี้ยังไม่มีอยู่
- หากเขียนใหม่ด้วย Go หรือ Rust ก็มีโอกาสที่บั๊กจะมากขึ้น หรือประสิทธิภาพอาจลดลง
- ภาษาเหล่านี้มักแทรกโค้ด branch เพิ่มเติม เช่น การตรวจสอบหน่วยความจำ ขณะที่ในแนวทางคุณภาพของ SQLite นั้น การทำ 100% branch coverage มีความสำคัญมาก และส่วนนี้ยังไม่ตอบโจทย์
- ภาษาที่ปลอดภัยมักยุติโปรแกรมเมื่อเกิดภาวะ out-of-memory แต่ SQLite ถูกออกแบบให้กู้คืนได้แม้อยู่ในภาวะหน่วยความจำไม่พอ
- Rust, Go และภาษาแนวนี้ยังคงเป็นภาษาใหม่ที่ต้องพัฒนาต่อไปอีก
- ด้วยเหตุนี้ ทีมพัฒนา SQLite จึงสนับสนุนการเติบโตของภาษาที่ปลอดภัย แต่ในการพัฒนา SQLite เองก็ยังให้ความสำคัญกับ เสถียรภาพของ C ที่ผ่านการพิสูจน์แล้ว
ถึงอย่างนั้น ก็ยังมีความเป็นไปได้ว่าในอนาคตอาจ มีการเขียนใหม่ด้วย Rust ส่วน Go มีโอกาสน้อย เพราะ Go ไม่ชอบ assert()
- แต่การจะเขียนด้วย Rust ได้นั้น มีเงื่อนไขล่วงหน้าดังนี้:
- Rust ต้องสุกงอมมากขึ้น และมีวงจรการเปลี่ยนแปลงที่ช้าลง จนกลายเป็น “ภาษาที่เก่าและน่าเบื่อ”
- ต้องพิสูจน์ได้ว่าสามารถสร้าง ไลบรารีสากลที่หลายภาษาเรียกใช้ได้
- ต้องสร้างอ็อบเจ็กต์โค้ดที่ ทำงานได้แม้บนอุปกรณ์ฝังตัวหรืออุปกรณ์ที่ไม่มี OS
- ต้องมีเครื่องมือทดสอบ 100% branch coverage สำหรับไบนารีที่คอมไพล์แล้ว
- ต้อง กู้คืนจากข้อผิดพลาด OOM (หน่วยความจำไม่พอ) ได้
- Rust ต้องทำทุกอย่างที่ C ทำใน SQLite ได้ โดยไม่เสียประสิทธิภาพ
- หากผู้ชื่นชอบ Rust (rustacean) คิดว่าเงื่อนไขข้างต้นครบถ้วนแล้ว และ SQLite ควรถูกเขียนใหม่ด้วย Rust ก็ขอแนะนำให้ติดต่อทีมพัฒนา SQLite โดยตรงเพื่อเสนอความเห็น
2 ความคิดเห็น
ความเห็นใน Hacker News
if (i >= array_length) panic("index out of bounds")เข้าไป แต่ตัวโค้ดนั้นเองก็ผ่านการทดสอบมาอย่างดีจากคอมไพเลอร์ Rust อยู่แล้ว จึงน่าจะไม่ต้องกังวล อยากรู้ว่าตัวเองเข้าใจตรรกะนี้ถูกหรือไม่get_unchecked()ที่ทำให้เข้าถึงข้อมูลได้โดยไม่มี bounds check ซึ่งช่วยให้ยังปลอดภัยและเพิ่มประสิทธิภาพได้ เอกสาร get_uncheckedif condition { panic(err) }เป็น assert function ได้หรือไม่ผมสงสัยกับประโยคที่ว่า C ก็เป็นความเสี่ยงด้านความปลอดภัยสำหรับ SQLite เหมือนกัน ถึงจะเขียนการทดสอบไว้อย่างดีเพียงพอ และเป็นนักพัฒนาที่มีความชำนาญมากพอ ก็ยังเป็นแบบนั้นอยู่หรือเปล่าครับ ปัญหาอาจอยู่ที่ตรรกะและกระบวนการพัฒนาก็ได้ แต่จะบอกว่าตัวภาษาเองเป็นช่องโหว่ด้านความปลอดภัยนั้นเข้าใจได้ยากครับ ที่จริงแล้วแทบไม่มีโปรแกรมไหนที่ไม่พึ่งพาโครงสร้างพื้นฐานที่เขียนด้วย C เลยด้วยซ้ำ