#encode #decode #파이썬vs러스트 #orjson짱
Python 내장 함수가 가끔은 Rust 로 만든 라이브러리보다 빠를 때가 있다는 사실...
큰 String 데이터를 encode, decode 할때
encode, decode 함수를 Rust 로 만들어서
Python 에서 import 하고 성능을 비교(cProfiler)해보자.
추가로 orjson 과도 비교해보자.
사실 결과적으로는 orjson 을 사용하는게 가장 좋다.
먼저 간단하게 Rust 를 작성하기 위한 directory 구조를 만들면,
target directory 와 Cargo.lock 은 build 할 때 자동 생성된다.
lib.rs 와 Cargo.toml, pyproject.toml 파일을 수동으로 생성한다.
먼저 lib.rs
use pyo3::prelude::*;
#[pyfunction]
fn encode_string(input: &str) -> PyResult<Vec<u8>> {
Ok(input.as_bytes().to_vec())
}
#[pyfunction]
fn decode_string(input: Vec<u8>) -> PyResult<String> {
match String::from_utf8(input) {
Ok(s) => Ok(s),
Err(e) => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("Invalid UTF-8 sequence: {}", e))),
}
}
#[pymodule]
fn rust_encoding(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(encode_string, m)?)?;
m.add_function(wrap_pyfunction!(decode_string, m)?)?;
Ok(())
}
그 다음 Cargo.toml 파일을 작성한다.
[package]
name = "rust_encoding"
version = "0.1.0"
edition = "2018"
[dependencies]
pyo3 = { version = "0.15", features = ["extension-module"] }
[lib]
crate-type = ["cdylib"]
마지막으로 pyproject.toml 파일을 만들어준다.
[build-system]
requires = ["maturin>=0.14.0"]
build-backend = "maturin"
[tool.maturin.metadata]
name = "rust_encoding"
requires-python = ">=3.6"
이렇게 하면 rust_encoding 이라는 directory 에서 작업을 진행할 수 있는데,
>> pip install maturin
>> maturin build
build 가 끝나면 target 밑에 wheels 가 생성되는데 해당 파일을 pypi 레파지토리에 업로드 해보자.
>> pip install twine
>> twine upload target/wheels/*
이때 pypi 계정 정보(API TOKEN, PASSWORD)가 필요하다.
https://pypi.org/account/register/
계정이 없다면 하나 만들어서 인생 첫(?) pypi 모듈을 생성해보자!
여차여차 업로드를 완료하면
rust_encoding 라이브러리를 확인해볼 수 있다.
https://pypi.org/project/rust-encoding/
이제 실제로 로컬에 설치해서 성능을 비교해보면,
>> pip install rust-encoding
>> pip install orjson
이제 쥬피터 노트북에서 함수를 작성해보자.
# 먼저 데이터를 대략 크게(?) 만들고,
data = [str(i) + " 번째 데이터 입니다." for i in range(1_000_000)]
# 모듈을 import 하고,
import rust_encoding
import json
import orjson
import cProfile
import contextlib
# Rust 모듈에서 encode_string, decode_string 함수를 가져옴
rust_encode_string = rust_encoding.encode_string
rust_decode_string = rust_encoding.decode_string
# 파이썬 내장 모듈의 인코딩 및 디코딩 함수
py_encode_string = json.dumps
py_decode_string = json.loads
# Rust 로 작성된 공식 인코딩 및 디코딩 함수
rust_encode_string = orjson.dumps
rust_decode_string = orjson.loads
# 큰 데이터를 문자열로 변환 > 안전장치(data 가 list, dict 일 때도 작동하도록)
data2 = json.dumps(data)
@contextlib.contextmanager
def cprofile_context(func_name):
pr = cProfile.Profile()
pr.enable()
yield
pr.disable()
print(func_name)
ps = pstats.Stats(pr)
ps.print_stats()
def profile_encode():
# Rust encode_string 함수를 사용하여 인코딩
with cprofile_context("myrust_encoding.prof"):
rust_encode_string(data2)
def profile_decode():
# Rust decode_string 함수를 사용하여 디코딩
with cprofile_context("myrust_decoding.prof"):
rust_decode_string(rust_encode_string(data2))
def profile_py_encode():
# Python 내장 모듈을 사용하여 인코딩
with cprofile_context("py_encoding.prof"):
py_encode_string(data)
def profile_py_decode():
# Python 내장 모듈을 사용하여 디코딩
with cprofile_context("py_decoding.prof"):
py_decode_string(py_encode_string(data))
def profile_rust_encode():
# Python 내장 모듈을 사용하여 인코딩
with cprofile_context("rust_encoding.prof"):
rust_encode_string(data)
def profile_rust_decode():
# Python 내장 모듈을 사용하여 디코딩
with cprofile_context("rust_decoding.prof"):
rust_decode_string(rust_encode_string(data))
# 함수 호출
profile_encode()
profile_decode()
profile_py_encode()
profile_py_decode()
profile_rust_encode()
profile_rust_decode()
이대로 실행하면
그냥 orjson 쓰자!