본 글은 Python의 JIT(Just-In-Time) 컴파일러 개발 방향을 비판하며, PEP를 통한 진행이 과도한 복잡성과 위험성을 내포한다고 지적합니다. 저자는 Cython 데코레이터 활용이나 Julia처럼 변경된 부분만 재컴파일하는 방식 등 대안적인 성능 최적화 접근법을 제시했습니다.
핵심 포인트
JIT 개발은 본질적으로 복잡하며, PEP를 통한 진행은 과도한 위험성을 가집니다.
성능 최적화는 모든 프로그램에 이롭지 않으므로 신중하게 측정해야 합니다.
Cython을 데코레이터로 활용하면 Python의 개발 흐름을 유지하면서 성능 향상을 노릴 수 있습니다.
Julia처럼 함수 변경 시 해당 부분만 재컴파일하는 접근법이 이상적입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 RSS: GeekNews (한국어)의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
가끔 개발 과정을 지켜봤는데 아쉬움. 운영위원회(SC)가 새 PEP를 요구하기 전까지 JIT 개발은 상당히 빠르게 진행되는 듯했으며, 자유 스레딩의 성능 손실을 상쇄하리라는 기대도 있었음.
다만 참조 카운트 연산 자동 제거, 타입별 특수화, 제자리 산술 연산 같은 멋진 최적화에도 불구하고, 실제 응용 벤치마크에서의 개선은 JIT치고 인상적이지 않았음. 정수 언박싱 같은 추가 최적화의 여지는 있지만 복잡도도 계속 커짐.
JIT는 원래 인터프리터 루프와 같은 소스에서 생성되므로 동작 차이가 생길 가능성이 극히 낮았지만, JIT 전용 경로와 최적화가 추가되면서 위험도 커졌을 것으로 보임.
운영위원회 입장에서는 이 부가 프로젝트가 거의 우연히 주 브랜치에 들어와 감독 없이 커졌고, 안정화 전망도 불분명하다고 느꼈을 듯함. 최신 PEP 역시 구체적인 계획보다 추가할 기능에 치우쳤다면 만족하지 못한 것도 이해가 됨.
성능 최적화에 PEP를 요구하는 것 자체가 큰 문제라고 봄. JIT는 본질적으로 크고 복잡해서, 사실상 새로운 Python 인터프리터와 컴파일러 전체의 PEP를 요구하는 셈임.
최적화는 모든 프로그램에 이롭지 않으므로 측정하고, 성능 회귀를 찾고, 확보한 응용 프로그램에서 가장 큰 병목부터 해결하는 반복 작업임. Django는 빨라져도 Flask는 느려지는 식의 과적합 위험도 있음.
이런 작업은 런타임 구현 세부 사항이므로 PEP 절차를 거치지 않아야 한다고 봄. JVM의 JIT 개선마다 JEP가 필요하거나 JavaScript 엔진의 최적화에 ECMA 승인이 필요하다면 제대로 진행되기 어려울 것임.
Python으로 시제품을 만든 뒤 LLM으로 C로 변환하는 방식에서 좋은 성과를 얻고 있음. 내 3090에서 Gemma4:31b에 몇 가지 지침을 주면 아주 잘 처리함. 이제 언어 자체의 최적화에 얼마나 투자해야 하는지 의문임.
이미 있을지도 모르지만, 데코레이터로 인라인 Cython을 작성할 수 있게 하면 어떨까? 표시한 함수를 처음 실행할 때나 미리 컴파일하고, 사용자가 명시적으로 활성화하는 JIT 역할을 Cython이 맡으면 문제의 90%는 해결할 수 있을 듯함.
Cython을 잘 쓰지 않는 이유는 수정하지 않은 Python 코드의 속도 향상이 대략 50% 정도에 그치는 반면, 개발 방식은 크게 달라지기 때문임. 다시 불러오기가 잘 안 되고, 프로파일링도 다르며, 배포에 빌드 디렉터리와 단계까지 필요함. 문법도 충분히 다르고 직관적이지 않아 그 정도 준비가 필요하다면 차라리 Rust로 함수를 감싸고 싶어짐.
데코레이터라면 Python다운 개발 흐름을 유지하면서 필요한 부분만 쉽게 가속하고, Cython의 여러 기능으로 더 큰 성능 향상도 노릴 수 있음. 다만 Cython은 Python의 상위 집합이므로 일부 언어 변경이 필요할 수 있음.
운영 코드에서 잘 쓰이지 않는 주된 이유는 세그멘테이션 오류를 만들 가능성이 크게 높아져 런타임 전체가 죽거나 주변 시스템에 예상 밖 동작을 일으킬 수 있기 때문이라고 봄.
자기 응용 코드에는 괜찮지만 의존 라이브러리는 빨라지지 않음. 특히 성능이 주목적이 아닌 라이브러리까지 Cython 코드를 곳곳에 넣는 해법은 널리 받아들여지기 어려움. JIT의 취지는 기존 코드를 바꾸지 않아도 전반적인 성능을 높이는 것임.
처음 실행할 때 모두 컴파일하고 이후에는 속도 손실 없이 재사용하며, 함수가 바뀌면 해당 함수만 다시 컴파일하는 Julia의 접근법이 마음에 듦.
별개로 큰 Python PEP를 준비 중인데, 규정에 필요한 유지보수 담당자와 후원자를 어디서 찾을 수 있을까? 코드는 이미 있고 Cython, GraalVM, PyPy에서 동작함. 큰 변경이지만 언어에 많은 도움이 되리라고 봄.
기술적으로 흥미로운 기능이 부분적으로는 행정 절차와 개발자의 여력 부족 때문에 사라지는 것이 아쉬움. 한편 PEP 정책과 그에 따르는 절차를 엄격히 지키는 것이 좋든 싫든 Python을 훌륭한 언어로 만든 요인이기도 함.
기술적 반대가 근거 없었다는 뜻은 아님. 디버거와 계측 지원, 유지보수만으로도 상당히 고된 작업일 것으로 보임.
운영위원회가 어쩔 수 없이 경직된 규정에 끌려갔다고 해석하면 잘못임. 절차는 이 기능에 유지보수성, 호환성, 명확한 성공 지표가 부족하다는 것을 드러내기 위해 사용한 도구였음. 세부 사정은 어떻든, PEP는 운영위원회의 우려를 충분히 해소하지 못한 뒤 철회된 것임.
V8에서 타입 혼동 등 JIT 관련 보안 버그가 발생하는 양을 보면, 다른 개발자들이 도입을 경계하는 것도 놀랍지 않음.
거대한 코드베이스도 그 자체로 관료 조직과 비슷함. 계층이 추가될수록 복잡성이 쌓여 어느 순간 진행을 방해하게 됨.
이번에는 사람이 만든 절차가 소프트웨어의 복잡성을 억제하는 현명하고 신중한 역할을 한 듯함. 핵심 대형 기능의 범위와 영향을 명확히 해 달라는 합리적인 요청에 제안자들이 대응하기 어렵다고 판단하면서, 복잡성 폭탄을 피한 셈임.
평범한 JIT를 좋은 JIT로 만들려면 기존 구현의 걸림돌을 제거하는 장기적인 반복 개선이 필요함. Ruby에는 좋은 JIT가 있지만, Rails에서 비 JIT 버전보다 빨라지기까지 여러 릴리스와 막대한 자원이 들어감. 단기간의 대약진을 기대해서는 안 되며 투자가 꾸준히 이어져야 함.
Python처럼 인기 있는 언어가 여기에 필요한 자원을 확보하지 못한다는 것이 이해하기 어려움.
CPython에는 슬픈 날이지만 Python 전체까지 그럴 필요는 없음. GraalPy는 고급 JIT를 갖추고 활발히 유지보수되는 Python 3.13 구현이며 JVM을 기반으로 함. 준비 실행을 거치면 작업에 따라 4배 이상 빨라질 수 있음. https://graalpy.org/
Chrome처럼 완전한 오픈 소스 버전과 기능이 조금 더 있는 독점 버전이 있으며, 후자도 공개·상업적 용도 모두 무료임. 다만 유료 재배포는 불가능함.
확장성 있는 무중단 가비지 컬렉션, Go 같은 단일 바이너리 배포, Java·JavaScript·Kotlin·Ruby 등 Truffle 언어와의 최적화된 상호 운용을 지원함. 네이티브 확장도 지원하며 SciPy, NumPy, PyTorch, Pandas 등과의 동작을 검증했고, GIL 없는 모드도 개발 중임. https://graalpy.org/python-developers/compatibility/.
최근에는 GraalPy 전용 웹 프레임워크 Pyronaut도 출시됨(https://pyronaut.io/). JVM 상호 운용을 통해 Spring과 비슷한 Micronaut를 사용할 수 있게 하며, JIT와 최적화된 기반 라이브러리 덕분에 FastAPI+Granian보다 약 3배, Flask+Gunicorn보다 약 7배 빠르면서 기능도 더 많음.
Devoxx에서 발표한 내용이며 개발자들은 곧 Python 전문 콘퍼런스에서도 발표할 예정임. CPython이 JIT라는 이정표를 넘지 못했다고 언어나 커뮤니티까지 실패할 필요는 없음. 대안 구현을 받아들여야 하지만, Python 커뮤니티는 PyPy가 더 활발하던 시절에도 이를 잘 수용한 편임. Oracle 제품이라는 점을 받아들여야 하겠지만 오픈 소스이므로 괜찮을 것임. 이해관계를 밝히자면, 이 작업을 하는 몇몇 사람들과 옆자리에서 일하고 있음.
준비 실행에는 얼마나 걸리나?
Python은 사용자가 많은 성숙한 실무 언어임. 20년 전 대학생 시절에도 gnu readline 확장 모듈에 하위 호환되는 작은 API 변경을 넣으려면 Guido를 직접 설득해야 했음. 그런데 지금까지 본 Python JIT 시도는 모두 첫 단계부터 너무 깊이 개입하고 범위를 크게 잡으려 함.
성공하려면 먼저 기존 의미론과 100% 호환되는 숨겨진 타입 모델로 객체 속성 접근과 메서드 디스패치를 최적화해야 한다고 봄. 동적 언어 JIT의 가장 큰 성능 개선은 바이트코드의 가드 제거나 기계어 생성보다, 해시 테이블 조회를 상수 시간의 가드 검사와 오프셋 조회로 바꾸는 데서 나옴.
이 모델을 만드는 것 자체도 어려움. 여러 * 메서드의 동적인 동작을 비롯한 딕셔너리 의미론을 보존해야 하고, 가장 흔한 조회인 스코프 내 이름 조회의 특수한 동작도 처리해야 함. Python에는 호출된 프레임이 호출자 스코프를 제한적으로 들여다보는 기능도 있었던 것으로 기억함.
Python은 이미 많은 경우에 그렇게 처리함. LOAD_ATTR_INSTANCE_VALUE, LOAD_ATTR_SLOT 같은 연산 코드 특수화를 보면 됨.
하위 호환성을 깨겠다고 했던 Python 3000 릴리스에서 이런 최적화나 다른 구조적 문제 해결의 기반조차 마련하지 않았다는 것이 여전히 놀라움.