Python과 Rust를 연결하여 성능 향상시키기
Olivia Novak
Dev Intern · Leapcell

소개
가독성과 방대한 생태계로 유명한 Python은 계산 집약적인 작업에서 종종 성능 병목 현상에 직면합니다. 데이터 처리, 과학 시뮬레이션 또는 실시간 애플리케이션이든 관계없이 GIL(Global Interpreter Lock)과 해석 언어의 특성은 속도를 저하시킬 수 있습니다. 반면에 Rust는 비교할 수 없는 성능, 메모리 안전성 및 스레드 동시성을 제공하여 Python 코드의 중요한 부분을 최적화하는 데 이상적인 후보입니다. 이 문서는 특히 두 가지 주요 라이브러리인 PyO3와 rust-cpython에 초점을 맞춰 Rust 코드를 Python 애플리케이션에 효과적으로 통합하는 방법을 살펴봅니다. Rust의 성능을 활용함으로써 Python 개발자는 Python이 제공하는 편의성을 희생하지 않고도 더 빠르고 안정적인 애플리케이션을 구축할 수 있습니다.
핵심 개념
PyO3와 rust-cpython의 세부 사항을 자세히 알아보기 전에 이 크로스 언어 통합을 이해하는 데 중요한 몇 가지 핵심 개념을 명확히 하겠습니다.
- FFI(Foreign Function Interface): FFI는 한 프로그래밍 언어로 작성된 프로그램이 다른 프로그래밍 언어로 작성된 루틴을 호출하거나 서비스를 활용할 수 있는 메커니즘입니다. PyO3와 rust-cpython은 모두 Python의 C API(Python의 FFI)를 활용하여 Python과 Rust 간의 통신을 가능하게 합니다.
- Python C API: Python 인터프리터에서 제공하는 C 수준 API로, C(및 FFI를 통한 Rust) 프로그램이 Python 객체와 상호 작용하고, Python 코드를 실행하고, Python 인터프리터를 확장할 수 있도록 합니다.
- 모듈 및 함수 내보내기: Rust 코드를 Python에서 호출 가능하게 만들려면 Rust 함수와 구조체를 Python 모듈 또는 호출 가능한 객체로 내보내야 합니다. 여기에는 신중한 유형 매핑과 Python의 객체 모델 준수가 필요합니다.
- 메모리 관리: FFI의 중요한 측면은 언어 경계를 넘어 메모리를 안전하게 관리하는 것입니다. Rust의 소유권 시스템은 Rust 내에서 메모리 안전성을 보장하지만, Python과 상호 작용할 때는 메모리 누수 또는 충돌을 방지하기 위해 Python의 가비지 컬렉터 및 C API 규칙에 주의를 기울여야 합니다.
Rust와 Python 통합: PyO3 및 rust-cpython
PyO3와 rust-cpython은 모두 Python에서 Rust 코드를 호출하는 강력한 도구이지만, 약간 다른 접근 방식과 기능을 제공합니다.
PyO3: 고급이며 관용적인 접근 방식
PyO3는 Rust에서 Python 모듈을 작성하기 위한 고수준 바인딩을 제공하는 인기 있고 적극적으로 유지 관리되는 라이브러리입니다. 인체 공학적이고 관용적인 것을 목표로 하여 Rust 개발자가 Python 생태계와 상호 작용하는 동안 편안함을 느낄 수 있도록 합니다. PyO3는 Python C API의 많은 복잡성을 추상화하여 Python 모듈, 클래스 및 함수를 정의하기 위한 Rust와 유사한 매크로 및 트레이트를 제공합니다.
원칙: PyO3는 Rust의 매크로 시스템을 활용하여 필요한 C API 보일러플레이트 코드를 생성합니다. Rust 함수와 구조체를 정의하고 PyO3 매크로로 주석을 달면 PyO3가 Python 유형을 Rust 유형으로, 그 반대로 변환하는 작업을 처리합니다.
예: PyO3를 사용하여 두 숫자의 합을 계산하고 Python으로 노출하는 간단한 Rust 함수를 만들어 보겠습니다.
먼저 Cargo.toml
을 설정합니다.
[package] name = "my_rust_module" version = "0.1.0" edition = "2021" [lib] name = "my_rust_module" crate-type = ["cdylib"] [dependencies] pyo3 = { version = "0.21", features = ["extension-module"] }
다음으로 src/lib.rs
에 Rust 코드를 작성합니다.
use pyo3::prelude::*; /// 두 숫자의 합을 문자열로 형식화합니다. #[pyfunction] fn sum_as_string(a: usize, b: usize) -> PyResult<String> { Ok((a + b).to_string()) } /// Rust로 구현된 Python 모듈입니다. #[pymodule] fn my_rust_module(_py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(sum_as_string, m)?) ?; Ok(()) }
이를 빌드하려면 프로젝트 루트에서 maturin develop
을 실행하십시오 ( pip install maturin
으로 maturin
을 설치해야 합니다). 그러면 Rust 코드가 컴파일되고 Python 패키지로 설치됩니다.
이제 Python에서 사용할 수 있습니다.
import my_rust_module result = my_rust_module.sum_as_string(5, 7) print(f"The sum is: {result}") # 출력: The sum is: 12 assert result == "12"
애플리케이션 시나리오: PyO3는 Rust에서 새로운 Python 모듈을 처음부터 만드는 것, 성능이 중요한 루프를 최적화하는 것, 기존 Rust 라이브러리를 Python에 통합하는 것, 경계를 가로질러 복잡한 데이터 구조를 처리하는 데 탁월합니다. 인체 공학적인 디자인으로 간단한 함수와 복잡한 클래스 계층 구조 모두에 적합합니다.
rust-cpython: 저수준의 명시적 접근 방식
rust-cpython
은 Python C API에 대한 보다 직접적이고 저수준 래퍼를 제공합니다. Python의 내부 객체 모델과 C API에 대한 더 깊은 이해가 필요하지만, 상호 작용에 대한 더 세밀한 제어를 제공합니다. PyO3보다 덜 성향적이며 Python 객체에 더 가까이 접근할 수 있게 해줍니다.
원칙: rust-cpython
은 Python C API 객체(예: PyObject
, PyDict
, PyList
)를 미러링하는 Rust 유형을 노출합니다. 참조 카운팅 및 유형 변환을 수동으로 처리하여 메모리 관리 및 객체 상호 작용에 대한 명시적인 제어를 제공합니다.
예: rust-cpython
을 사용하여 sum_as_string
함수를 다시 구현해 보겠습니다.
먼저 Cargo.toml
을 업데이트합니다.
[package] name = "my_rust_module_cpython" version = "0.1.0" edition = "2021" [lib] name = "my_rust_module_cpython" crate-type = ["cdylib"] [dependencies] cpython = "0.7" # 또는 최신 호환 버전
다음으로 src/lib.rs
에 Rust 코드를 작성합니다.
extern crate cpython; use cpython::{PyResult, Python, PyModule, PyDict, PyObject, ToPyObject}; fn sum_as_string_cpython(py: Python, a: PyObject, b: PyObject) -> PyResult<String> { let a_int: usize = a.extract(py)?; let b_int: usize = b.extract(py)?; Ok((a_int + b_int).to_string()) } // 모듈 내보내기를 위해 cpython에서 제공하는 특수 매크로 py_module_initializer!(my_rust_module_cpython, initmy_rust_module_cpython, PyInit_my_rust_module_cpython, |py, m| { m.add(py, "__doc__", "rust-cpython으로 구현된 Python 모듈입니다.")?; m.add_wrapped(py, "sum_as_string", sum_as_string_cpython)?; Ok(()) });
이를 빌드하려면 이전과 같이 maturin develop
을 사용하십시오.
이제 Python에서:
import my_rust_module_cpython result = my_rust_module_cpython.sum_as_string(10, 20) print(f"The sum is: {result}") # 출력: The sum is: 30 assert result == "30"
애플리케이션 시나리오: rust-cpython
은 Python 객체에 대한 세분화된 제어가 필요한 시나리오, 예를 들어 매우 낮은 수준에서 성능이 중요한 상호 작용 또는 내부 Python 구조와 직접 인터페이스할 때 적합합니다. Python C API에 이미 매우 익숙하며 Rust에서 더 가까운 매핑을 원하는 개발자가 선호할 수 있습니다.
Pyo3 vs. Rust-cpython 비교
특징 | PyO3 | rust-cpython |
---|---|---|
사용 편의성 | 고급, 인체 공학적, Rust 관용적 | 저수준, 더 명시적, C API 중심 |
추상화 | Python C API에 대한 높은 추상화 | 낮은 추상화, 직접 C API 래퍼 |
매크로 | 바인딩을 위해 매크로를 많이 사용함 | 매크로에 덜 의존적, 더 수동적 |
오류 처리 | Rust Result 및 PyErr | PyResult 및 수동 오류 발생 |
유형 변환 | FromPyObject /ToPyObject 로 대부분 자동 | extract /to_py_object 를 사용한 명시적 변환 |
커뮤니티/활동 | 매우 활동적, 현대적 | 덜 활동적이지만 안정적 |
결론
PyO3와 rust-cpython 모두 고성능 Rust 코드를 Python 애플리케이션에 통합하는 효과적인 경로를 제공하여 개발자가 중요한 작업에 대해 Python의 해석 오버헤드를 극복할 수 있도록 합니다. PyO3는 인체 공학적인 디자인과 고수준 추상화를 통해 생산적이고 관용적인 Rust 경험을 추구하는 대부분의 사용자를 위한 선택입니다. Rust-cpython은 Python C API를 더 깊이 파고들어야 하는 사람들에게 더 세밀한 제어를 제공합니다. 이러한 강력한 도구를 활용함으로써 개발자는 상당한 성능 향상을 얻을 수 있으며, Python 애플리케이션이 까다로운 워크로드에 대해 강력하고 반응성이 뛰어나도록 보장합니다.