Python이 AI, 데이터 분석 등에서 많이 쓰이고 빠르게 코딩할 수 있다는 장점이 있지만, 인터프리터 언어의 고질적인 속도 저하 문제 때문에 실무에서 골치를 썩히기도 하죠.
실무에서는 Python 속도 문제를 개선하기 위해 여러 가지 대안들을 고민합니다. Python 코드는 초기 프로토타입으로만 쓰고 production으로 이행하면서 Rust나 C/C++로 다시 개발하는 경우도 있죠.
저는 Python을 버리지 않고도 속도 문제를 해결하기 위한 새로운 대안으로 Rextio라는 도구를 실험적으로 개발하고 있습니다.
기본적인 컨셉은 Python 코드 중 네이티브로 변환이 가능한 부분(타입 힌팅된 정적인 코드 등)을 Rust 코드로 자동 변환 후 바이너리로 컴파일해 성능을 높이고, 네이티브로 자동 변환이 어려운 부분(동적인 코드 등)을 그대로 Python 코드로 남겨(fallback) CPython으로 실행하게 해서 원본 Python 코드의 로직을 보존하는 것입니다.
대략적으로는 다음과 같습니다.
타입이 지정된 Python 프로젝트
-> 지원되는 native 후보 분석
-> 안전하지 않거나 미지원인 함수는 거부
-> 수락된 함수는 Rust + PyO3 생성
-> 나머지는 Python fallback wrapper 생성
-> import 호환 artifact 빌드
자세한 내용은 링크 Git repository의 문서를 참고해 주세요.
Rextio로 변환한 native 바이너리를 포함해서 실행했을 때와 원본 Python 코드를 CPython으로 실행했을 때를 비교하면 성능 향상도 확인이 됩니다.
- 기본적인 Python 코드를 native로 변환해서 실행 시: 57.73×
- NumPy을 native로 변환해서 실행 시: 2.52×
- NetworkX을 native로 변환해서 Dijkstra 알고리즘 실행 시: 3.68×
- pandas의
Series.map을 native로 변환해서 실행 시: 66.14× - PyTorch deep MLP를 native로 변환해서 실행 시: 1.02× (기존 Python 패키지에서 C 확장 등을 사용해서 속도 향상 제한적)
- TensorFlow eager chain을 native로 변환해서 실행 시: 1.04× (기존 Python 패키지에서 C 확장 등을 사용해서 속도 향상 제한적)
위 결과가 나온 벤치마크에서 사용한 소스 코드와 실행 환경 정보는 rextio-benchmark Git repository를 참조해 주십시오.
참고로 Rextio에는 Python 코드를 변환하고 컴파일을 실행하는 Core CLI가 있고, 거기에 개별 Python 패키지들을 Rust 코드로 변환하는 규칙을 정의한 추가적인 플러그인들을 만들어 붙일 수 있게 개발하고 있습니다. 현재 NumPy 등의 플러그인들을 일부 만들어 두었고(rextio-numpy, rextio-networkx, rextio-pandas, rextio-torch, rextio-tensorflow 등), 위 벤치마크 결과는 이 플러그인들을 활용해서 실행한 결과입니다.
물론 아직 Rextio는 개선할 게 많은 초기 프로젝트이구요, 모든 상황에서 Python 코드를 Rust 기반의 native로 컴파일하는 게 성능 향상을 가져오는 건 아닙니다.
예를 들어 native 부분과 CPython fallback 부분의 데이터 교환이 잦을 경우, 데이터가 변환되어 경계를 넘느라 오히려 속도 저하가 발생할 수 있습니다. 다만 Rextio에서는 이런 경우 해당 부분을 native를 거치지 않고 전부 CPython으로 fallback시켜 속도 저하를 막도록 유도하고는 있습니다.
그 동안 혼자서 개발을 하다가 어느 정도 자리가 잡힌 것 같아 이곳에 소개 드립니다.
질문이나 의견 피드백 주시면 최대한 답변 드리고 반영하도록 노력하겠습니다.
감사합니다.