>_ DevTrendspl

Język

Strona główna

Języki

Sekcje

Frontend Backend Mobilne DevOps AI / ML GameDev Blockchain Systemy wbudowane Bezpieczeństwo
Rust

Jak przyspieszyć Pythona za pomocą Rust i PyO3

Python zachwyca zwięzłością i bogatym wyborem pakietów zewnętrznych, ale gdy przychodzi do intensywnych obliczeń, parsowania gigabajtów danych czy walidacji milionów obiektów, interpreter zaczyna zwalniać. Wcześniej w takich sytuacjach programiści musieli ręcznie pisać rozszerzenia C API. Ten proces jest pracochłonny: debugowanie wycieków pamięci i ręczne zarządzanie referencjami szybko pozbywają się całej radości z programowania.

Kilka lat temu pojawiła się bezpieczniejsza ścieżka. Biblioteka PyO3 umożliwia pisanie natywnych modułów w Rust, zachowując rygorystyczne gwarancje bezpieczeństwa pamięci i przejrzyste abstrakcje.

Jeśli korzystałeś z walidatora Pydantic v2, parsera JSON orjson, frameworka Polars lub tokenizera Tiktoken od OpenAI, to już uruchamiałeś kod stworzony z PyO3.

Python wyróżnia się w logice biznesowej i szybkim prototypowaniu. Rust wygrywa tam, gdzie potrzebna jest maksymalna szybkość, niskie zużycie zasobów i współbieżność.

PyO3 rozwiązuje dwa problemy:

  1. Odciążanie wąskich gardeł aplikacji Python do oddzielnych funkcji lub struktur Rust.
  2. Osadzanie interpretera Python w pliku binarnym Rust na potrzeby wykonywania skryptów.

Najczęściej projekt jest wykorzystywany w pierwszym scenariuszu.

Jak napisać moduł w praktyce

Do budowania kodu Rust w pakiet Python autorzy zalecają narzędzie maturin. Obsługuje ono kompilację i tworzy pliki wheel.

Stwórzmy prosty moduł, który dodaje dwie liczby i zwraca wynik jako ciąg znaków.

Najpierw skonfigurujmy środowisko wirtualne:

mkdir string_sum && cd string_sum
python -m venv .env
source .env/bin/activate
pip install maturin
maturin init --bindings pyo3

Logika po stronie Rust w pliku src/lib.rs:

use pyo3::prelude::*;

#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
    Ok((a + b).to_string())
}

#[pymodule]
mod string_sum {
    use super::*;

    #[pymodule_export]
    fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
        m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
        Ok(())
    }
}

Makra #[pyfunction] i #[pymodule] automatycznie generują kod dla C API Pythona. Programiści nie muszą ręcznie konwertować typów PyObject na struktury Rust i z powrotem.

Rozpocznijmy budowanie:

maturin develop

Teraz funkcję można wywoływać bezpośrednio z Pythona:

import string_sum

result = string_sum.sum_as_string(5, 20)
print(result)  # '25'

Jeśli chcesz zmierzyć rzeczywistą wydajność w produkcji, po prostu zbuduj moduł z flagą optymalizacji: maturin develop --release.

Przykłady zastosowań w rzeczywistych projektach

W Pydantic V2 cała logika sprawdzania typów i walidacji została przepisana w Rust w osobnym pakiecie pydantic-core. Wydajność wzrosła dramatycznie.

Biblioteka Polars wypiera Pandas przy pracy z dużymi zbiorami danych dzięki równoległym obliczeniom w Rust. Użytkownik pisze znajomy kod Python, podczas gdy ciężka praca odbywa się w skompilowanym module.

W uczeniu maszynowym PyO3 jest wykorzystywany przez Hugging Face w pakiecie tokenizers oraz przez OpenAI w tiktoken. Szybkie parsowanie tekstu przed wysłaniem do sieci neuronowej znacząco redukuje opóźnienia usługi.

Narzędzia wspierające

Wokół PyO3 ukształtował się zestaw użytecznych narzędzi:

  • maturin eliminuje potrzebę pisania złożonych konfiguracji dla setup.py.
  • rust-numpy wiąże tablice NumPy z Rust bez kopiowania danych w pamięci.
  • pyo3-async-runtimes łączy asyncio z asynchronicznym środowiskiem wykonawczym Tokio.
  • pythonize transformuje struktury danych za pomocą Serde.

Podsumowanie

Jeśli Twój projekt ma wąskie gardło wydajności, PyO3 pomoże rozwiązać problem bez przełączania się na C lub C++. Główna trudność tutaj tkwi w nauce samego Rusta. Zrozumienie koncepcji własności i typów danych będzie wymagane, ale biblioteka w pełni obsługuje rutynową pracę z CPython.

Powiązane projekty