[2023] ทำให้ Python เร็วขึ้น 100 เท่าด้วย PyO3
(ohadravid.github.io)ช่วงนี้กำลังศึกษา free-threading Python เลยเริ่มสนใจ PyO3 แม้จะเป็นบทความเมื่อ 2 ปีก่อน แต่ก็ขอนำมาแชร์ครับ
Making Python 100× Faster with <100 Lines of Rust – สรุป
ภูมิหลัง
- ไลบรารี Python แกนหลักของไปป์ไลน์ประมวลผล 3-D ภายในบริษัทกลายเป็นคอขวดจากจำนวนผู้ใช้พร้อมกันที่เพิ่มขึ้น
- การเขียนใหม่ทั้งหมดด้วย Rust มีความเสี่ยงและใช้เวลามาก จึงเลือกแนวทาง เพิ่มประสิทธิภาพเฉพาะบางส่วน
วิธีการ
- เริ่มจากการวัดผล: ใช้ sampling profiler
py-spyเพื่อระบุคอขวด - นำ Rust เข้ามาแบบค่อยเป็นค่อยไป
- ใช้
PyO3+maturinเพื่อเชื่อม Python ↔ Rust - ย้ายเฉพาะฟังก์ชัน
find_close_polygonsไปเป็น Rust ก่อน - จากนั้นย้ายโครงสร้างข้อมูล
Polygonไปเป็น Rust แล้วให้ subclass จากฝั่ง Python
- ใช้
- โปรไฟล์-ปรับปรุงซ้ำอย่างต่อเนื่อง
- ลดการแปลง NumPy → Rust ที่ไม่จำเป็น
- ลดการจัดสรรหน่วยความจำและการคัดลอก พร้อมทำ micro-optimization ด้วยการคำนวณระยะทางโดยตรง
การเปลี่ยนแปลงด้านประสิทธิภาพ
| ขั้น | เวลาเฉลี่ยในการรัน (ms) | อัตราการปรับปรุง |
|---|---|---|
| Python ล้วนเริ่มต้น | 293.41 | 1× |
v1 – เฉพาะฟังก์ชันเป็น Rust (--release) |
23.44 | 12.5× |
v2 – Polygon ก็เป็น Rust ด้วย |
6.29 | 46.5× |
| v3 – ตัดการจัดสรรออก·คำนวณโดยตรง | 2.90 | 101× |
เทคโนโลยีหลัก
- PyO3 : FFI ระหว่าง Python ↔ Rust ที่ปลอดภัย
- maturin : ทำ build และ deploy อัตโนมัติ
- ndarray / numpy crate : อาร์เรย์และพีชคณิตเชิงเส้นฝั่ง Rust
- py-spy : profiler ที่มองเห็นได้ถึง native stack
บทเรียน
- ถ้าเริ่มจากการทำ profiling ก่อน ก็สามารถได้ผลลัพธ์มหาศาลจากการเปลี่ยนโค้ดเพียงเล็กน้อย
- แม้จะคง Python API เดิมไว้ แค่สลับเฉพาะโมดูล Rust ก็สามารถนำไปใช้กับบริการจริงได้ทันที
- Rust มีประสิทธิภาพมากพอแม้นำมาใช้แบบบาง ๆ เฉพาะใน “ส่วนที่ต้องการความเร็ว”
3 ความคิดเห็น
การสร้างส่วนขยาย Python ด้วย c/c++ ทำให้ประสิทธิภาพในการพัฒนาตกลงมากเกินไป แต่ pyo3 สะดวกมากเพราะอย่างน้อยก็มี
maturin,cargoให้ใช้และโมดูล Python ก็จำเป็นต้องรองรับการคอมไพล์ข้ามแพลตฟอร์มด้วย ซึ่ง rust ก็ทำครอสคอมไพล์ได้สะดวกเช่นกัน.
maturin... ทรมาน...
พยายามไปให้สุดด้วยการเวกเตอร์ไรซ์ด้วย NumPy ก่อน ถ้าไม่ได้ก็ค่อยเสียบ GPU แล้วเปลี่ยนไปใช้ cupy หรือ torch และถ้ายังไม่ได้อีกก็ค่อยเขียนเนทีฟด้วย cython อะไรแบบนั้น... แต่ดูเหมือนว่าถ้าเลี่ยงการเขียนเนทีฟได้ก็ควรเลี่ยงนะครับ มันเหนื่อยมาก