PyO3를 활용한 파이썬 모듈의 병렬 처리 방법

파이썬은 유연하고 편리한 언어로 알려져 있지만, 실행 속도 측면에서는 다른 언어들과 비교하면 다소 느리다는 단점이 있습니다. 이 때문에 파이썬 개발자들은 종종 C나 C++과 같은 저수준 언어로 작성된 모듈을 사용하여 실행 성능을 향상시키기도 합니다. PyO3는 파이썬과 Rust를 연결하는 훌륭한 도구로, C 확장이 아닌 Rust 확장을 작성할 수 있게 해줍니다. 따라서 PyO3를 활용하여 병렬 처리를 하는 파이썬 모듈을 개발할 수 있습니다.

1. 병렬 처리 개요

병렬 처리는 여러 작업을 동시에 실행하여 속도를 향상시키는 기술입니다. 파이썬에서 병렬 처리를 위해 threading이나 multiprocessing과 같은 내장 모듈을 사용할 수 있습니다. 그러나 이러한 모듈은 GIL(Global Interpreter Lock)에 의해 제한되어 실제로 여러 CPU 코어를 활용하기 어렵습니다. PyO3를 사용하면 실제로 다중 스레드를 통해 병렬 처리를 할 수 있습니다.

2. PyO3 설치

PyO3를 사용하기 위해서는 Rust가 시스템에 설치되어 있어야 합니다. Rust 설치는 공식 웹사이트(https://www.rust-lang.org/)에서 다운로드하여 설치할 수 있습니다. PyO3를 설치하기 위해서는 파이썬 패키지 관리자인 pip을 사용합니다. 터미널에서 다음 명령을 실행하여 PyO3를 설치할 수 있습니다.

$ pip install pyo3

3. 병렬 처리 모듈 개발하기

PyO3를 사용하여 병렬 처리를 위한 파이썬 모듈을 개발하는 방법은 다음과 같습니다.

  1. Rust로 원하는 작업을 수행하는 함수를 작성합니다. 이 함수는 PyO3의 #[pyfunction] 데코레이터를 사용하여 Python 함수로 사용될 수 있도록 합니다.
  2. 파이썬 모듈을 위한 Rust 확장 모듈을 작성합니다. 이때 #[pymodule] 데코레이터를 사용하여 모듈로서 인식될 수 있도록 합니다.
  3. 병렬 처리를 위한 함수를 호출하고 결과를 반환하는 Python 함수를 정의합니다.

예를 들어, 다음은 PyO3를 사용하여 병렬로 계산하는 간단한 예제 코드입니다.

use pyo3::types::IntoPyDict;
use pyo3::prelude::*;

#[pyfunction]
fn parallel_sum(py: Python, data: Vec<i32>) -> PyResult<i32> {
    let sum: i32 = data.into_iter().sum();
    Ok(sum)
}

#[pymodule]
fn parallel_processing(_py: Python, module: &PyModule) -> PyResult<()> {
    module.add_function(wrap_pyfunction!(parallel_sum, module)?)?;
    Ok(())
}

#[pymodule]
fn my_module(py: Python, module: &PyModule) -> PyResult<()> {
    let parallel_processing_module = PyModule::new(py, "parallel_processing")?;
    parallel_processing_module.add_function(
        wrap_pyfunction!(parallel_sum, parallel_processing_module)?)?;
    module.add_submodule(parallel_processing_module)?;
    Ok(())
}

#[pymodule]
fn my_package(_py: Python, module: &PyModule) -> PyResult<()> {
    let my_module = PyModule::new(module, "my_module")?;
    my_module.add_module(parallel_processing)?;
    module.add_module(my_module)?;
    Ok(())
}

#[pymodule]
fn my_package_in_file(py: Python, module: &PyModule) -> PyResult<()> {
    let my_package = PyModule::new(py, "my_package")?;
    my_package.add_module(my_package)?;
    module.add_module(my_package)?;
    Ok(())
}

#[pymodule]
fn my_package_in_dir(_py: Python, module: &PyModule) -> PyResult<()> {
    let my_package = PyModule::new(module, "my_package")?;
    my_package.add_module(my_package_in_file)?;
    module.add_module(my_package)?;
    Ok(())
}

#[pymodule]
fn my_package_in_dir_with_init(py: Python, module: &PyModule) -> PyResult<()> {
    let my_package = PyModule::new(py, "my_package")?;
    my_package.add_module(my_package_in_file)?;
    my_package.add_module(my_package_in_file)?;
    module.add_module(my_package)?;
    Ok(())
}

#[pymodule]
fn my_package_in_dir_with_init_and_submodules(_py: Python, module: &PyModule) -> PyResult<()> {
    let my_package = PyModule::new(module, "my_package")?;
    my_package.add_module(my_package_in_file)?;
    my_package.add_module(my_package_in_dir)?;
    module.add_module(my_package)?;
    Ok(())
}

#[pymodule]
fn my_package_in_dir_with_underscore(py: Python, module: &PyModule) -> PyResult<()> {
    let my_package = PyModule::new(py, "_my_package")?;
    my_package.add_module(my_package_in_file)?;
    module.add_module(my_package)?;
    Ok(())
}

#[pymodule]
fn my_package_in_dir_with_init_and_underscore(_py: Python, module: &PyModule) -> PyResult<()> {
    let my_package = PyModule::new(module, "_my_package")?;
    my_package.add_module(my_package_in_file)?;
    my_package.add_module(my_package_in_file)?;
    module.add_module(my_package)?;
    Ok(())
}

4. 모듈 사용하기

위에서 작성한 Rust 코드를 다음과 같이 파이썬에서 사용할 수 있습니다.

import my_package.my_module.parallel_processing as pp

data = [1, 2, 3, 4, 5]
result = pp.parallel_sum(data)
print(result)

위의 예제 코드에서는 병렬로 주어진 리스트의 합을 계산하는 parallel_sum 함수를 사용하고 있습니다. 이를 통해 복잡한 계산 작업을 효율적으로 처리할 수 있습니다.

References

#hashtags: #PyO3 #병렬처리