[2023] Python mit PyO3 100× schneller machen
(ohadravid.github.io)Beim jüngsten Lernen zu free-threading Python bin ich auf PyO3 aufmerksam geworden, daher teile ich diesen zwei Jahre alten Artikel.
Making Python 100× Faster with <100 Lines of Rust – Zusammenfassung
Hintergrund
- Die zentrale Python-Bibliothek einer internen 3-D-Verarbeitungspipeline wurde durch die steigende Zahl gleichzeitiger Nutzer zum Flaschenhals.
- Da ein komplettes Neuschreiben in Rust zu riskant und zeitaufwendig gewesen wäre, fiel die Wahl auf eine partielle Optimierung.
Vorgehensweise
- Zuerst messen: Mit dem Sampling-Profiler
py-spywurde der Flaschenhals identifiziert. - Schrittweise Einführung von Rust
- Verbindung zwischen Python und Rust mit
PyO3+maturin. - Zunächst wurde nur die Funktion
find_close_polygonsnach Rust portiert. - Anschließend wurde auch die Datenstruktur
Polygonnach Rust verschoben und in Python per Subclassing genutzt.
- Verbindung zwischen Python und Rust mit
- Wiederholtes Profiling und Verbessern
- Unnötige NumPy → Rust-Konvertierungen minimiert.
- Zuweisungen und Kopien reduziert sowie durch direkte Distanzberechnung feinoptimiert.
Leistungsentwicklung
| Phase | Durchschnittliche Laufzeit (ms) | Verbesserungsfaktor |
|---|---|---|
| Ursprünglich reines Python | 293.41 | 1× |
v1 – nur Funktion in Rust (--release) |
23.44 | 12.5× |
v2 – auch Polygon in Rust |
6.29 | 46.5× |
| v3 – Zuweisungen entfernt, direkte Berechnung | 2.90 | 101× |
Zentrale Technologien
- PyO3 : sicheres Python ↔ Rust-FFI.
- maturin : Automatisierung von Build und Deployment.
- ndarray / numpy crate : Arrays und lineare Algebra auf Rust-Seite.
- py-spy : Profiler mit Einblick bis in den nativen Stack.
Erkenntnisse
- Wer zuerst profiliert, kann mit kleinen Codeänderungen große Gewinne erzielen.
- Selbst wenn nur das Rust-Modul ausgetauscht wird und die Python-API erhalten bleibt, lässt sich das sofort in produktiven Services einsetzen.
- Rust ist auch dann sehr effektiv, wenn es nur dünn für den „Performance-Bereich“ eingeführt wird.
3 Kommentare
Python-Erweiterungen in C/C++ zu erstellen, ist produktivitätsmäßig viel zu ineffizient, aber PyO3 ist dank
maturinundcargovor allem sehr komfortabel.Außerdem ist Cross-Compilation für Python-Module unverzichtbar, und auch dabei ist Rust praktisch.
maturin... Qual...
Ich halte mich so lange wie möglich mit NumPy-Vektorisierung über Wasser, und wenn das nicht reicht, stecke ich eine GPU rein und wechsle zu CuPy oder torch. Wenn selbst das nicht reicht, schreibe ich Native-Code mit Cython oder so ... aber auf Native zu gehen sollte man, wenn möglich, lieber vermeiden. Das ist hart.