[2023] PyO3 से Python को 100 गुना तेज़ बनाना
(ohadravid.github.io)हाल ही में free-threading Python का अध्ययन करते हुए मेरी रुचि PyO3 में हुई, इसलिए 2 साल पुराना लेख होने के बावजूद इसे साझा कर रहा हूँ.
Making Python 100× Faster with <100 Lines of Rust – सारांश
पृष्ठभूमि
- कंपनी के अंदर उपयोग होने वाली 3-D processing pipeline की मुख्य Python लाइब्रेरी, समवर्ती उपयोगकर्ताओं की बढ़ती संख्या के कारण bottleneck बन रही थी.
- पूरे सिस्टम को Rust में फिर से लिखना जोखिमभरा और समय-साध्य था, इसलिए आंशिक optimization का रास्ता चुना गया.
तरीका
- पहले मापें:
py-spysampling profiler से bottleneck की पहचान की गई. - Rust को चरणबद्ध तरीके से अपनाना
PyO3+maturinके ज़रिए Python ↔ Rust कनेक्शन.- पहले केवल
find_close_polygonsफ़ंक्शन को Rust में port किया गया. - इसके बाद
Polygondata structure को भी Rust में ले जाकर Python में subclassing की गई.
- दोहराया गया profiling और सुधार
- अनावश्यक NumPy → Rust conversion को न्यूनतम किया गया.
- allocation और copy घटाकर, direct distance calculation से micro-optimization की गई.
प्रदर्शन में बदलाव
| चरण | औसत execution time (ms) | सुधार गुणांक |
|---|---|---|
| शुरुआती pure Python | 293.41 | 1× |
v1 – केवल फ़ंक्शन Rust में (--release) |
23.44 | 12.5× |
v2 – Polygon भी Rust में |
6.29 | 46.5× |
| v3 – allocation हटाना और direct calculation | 2.90 | 101× |
मुख्य तकनीकें
- PyO3 : सुरक्षित Python ↔ Rust FFI.
- maturin : build और deployment automation.
- ndarray / numpy crate : Rust-पक्ष array और linear algebra.
- py-spy : ऐसा profiler जो native stack तक दिखाता है.
सीख
- पहले profiling करने पर, छोटे code changes से बड़ा लाभ मिल सकता है.
- Python API को बनाए रखते हुए केवल Rust मॉड्यूल बदलना भी production service में तुरंत लागू किया जा सकता है.
- Rust को केवल “performance-critical” हिस्सों में पतले तौर पर जोड़ना भी काफ़ी प्रभावी हो सकता है.
3 टिप्पणियां
c/c++ से Python extension बनाना बहुत कम प्रोडक्टिव होता है, लेकिन PyO3 में कम-से-कम maturin और cargo होने की वजह से यह बहुत सुविधाजनक है.
और Python modules के लिए cross compilation भी अनिवार्य है, लेकिन Rust में cross compilation भी आसान है.
maturin... दर्द...
जहाँ तक हो सके NumPy vectorization से काम चलाते हैं, और अगर उससे नहीं होता तो GPU लगाकर cupy या torch पर स्विच कर लेते हैं, और फिर भी नहीं होता तो cython से native में लिखते हैं... लेकिन native का काम जहाँ तक संभव हो, न करना ही बेहतर लगता है। बहुत मुश्किल होता है।