PEP 836 – JIT Go Brrr: CPython의 공식 지원 JIT 컴파일러로 가는 길
- Author:
- Savannah Ostrowski <savannah at python.org>, Ken Jin <kenjin at python.org>, Brandt Bucher <brandt at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Draft
- Type:
- Standards Track
- Created:
- 02-Jul-2026
- Python-Version:
- 3.16
- Post-History:
- 03-Jul-2026
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
실험적인 JIT(Just-in-Time) 컴파일러는 Python 3.13부터 CPython의 main 브랜치에 포함되어 있습니다. PEP 744는 초기 설계의 일부를 설명했으며, JIT의 장기적인 지위에 관한 여러 질문은 명시적으로 미루었습니다. 그 이후로 JIT는 아키텍처가 재설계되었고 상당히 성숙해졌습니다. Python 3.15에서 JIT는 인터프리터보다 측정 가능하고 재현 가능한 속도 향상을 제공하고(측정한 Tier 1 플랫폼 전반에서 기하 평균 약 4-12%의 성능 향상. 부록을 참조하십시오), 네이티브 디버거가 언와인드할 수 있는 프레임을 생성하며, 3.14에 비해 생성된 코드의 메모리 사용량을 줄입니다. 그 과정에서 우리는 CPython에서 JIT에 무엇이 효과적인지에 대해 많은 것을 배웠습니다.
이 PEP는 JIT가 측정 가능한 성능, 호환성, 도구, 플랫폼, 배포, 보안, 유지 보수 목표를 충족하는 경우 CPython의 공식 지원되는 비실험적 구성 요소가 되기 위한 경로를 제안합니다. 초기 성능 목표는 Python 3.17의 첫 베타 릴리스까지 JIT + 자유 스레드 빌드가 JIT를 사용하지 않는 자유 스레드 빌드의 인터프리터에 비해 pyperformance에서 기하 평균 최소 20%의 향상을 달성하는 것이며, 이는 지원되는 Tier 1 플랫폼 전반의 평균으로 측정합니다. 이 목표는 JIT를 트리 내에서 계속 개발하기 위한 최소 기준으로 설정되었습니다.
제안
이 PEP는 JIT를 즉시 공식 지원 기능으로 선언할 것을 제안하지 않습니다. 대신, 프로젝트가 명시적인 성능, 호환성, 도구, 배포, 보안, 유지 보수 목표를 충족하는 동안 CPython main에서 JIT 개발을 유지하기 위한 기한이 정해진 경로를 제안합니다.
이러한 목표가 충족되면 JIT는 CPython의 비실험적 기능으로 승격될 수 있습니다. 목표가 충족되지 않으면 운영 위원회(Steering Council)와 코어 팀은 JIT가 CPython main에 남아 있어야 하는지 재평가해야 합니다. 승격 이후, 지원되는 플랫폼에서 JIT를 기본으로 활성화하려면 릴리스 관리자의 별도 최종 승인이 필요합니다.
개략적으로, 향후 2.5년 동안 JIT의 마일스톤과 목표는 다음과 같습니다:
- 1년 차(Python 3.16의 첫 베타까지) - 개발자 경험 개선
- 프런트엔드를 트레이스 기록 방식에서 메서드 기반 방식으로 발전시킵니다. 우리는 메서드 프런트엔드가 유지 보수, 교육, 디버깅 등을 더 쉽게 해 주는 방향으로 우리를 이끌 것이라고 믿습니다. 첫 구현은 최소한이어야 하며, 초기에는 메모리를 더 많이 사용하거나 성능이 약간 떨어질 수 있고, 첫해에 이 접근 방식이 프로젝트의 목표를 충족하지 못하면 현재의 트레이싱 프런트엔드로 되돌릴 수 있습니다. JIT의 나머지 부분(중간 표현, 미들엔드/최적화기, Copy and Patch(복사 후 패치) 백엔드)은 CPython 3.15에서 거의 그대로 유지됩니다. 다시 말해, JIT가 컴파일 대상으로 선택하는 단위만 트레이스에서 메서드로 바뀌며, 그 밖의 어떤 것도 CPython 3.15에서 달라지지 않습니다.
- JIT를 자유 스레딩과 호환되도록 만듭니다. 자유 스레딩 채택이 빠르게 확대되고 있으므로, 우리는 JIT의 다음 단계에서 이를 초기에 우선시하는 것이 중요하다고 믿습니다.
- 네이티브 및 Python 프로파일러와 디버거에 대한 테스트를 추가합니다(그리고 발견되는 남은 커버리지 공백을 해결합니다). 최소한 프레임 포인터를 사용해 언와인드하는 모든 도구가 여기에 포함되며, Python 프레임을 심볼화하는 도구도 지원하도록 범위를 확장해야 합니다. 기존 동작을 정확히 보존할 수 없는 경우, 서드파티 도구를 위한 문서화된 해결 경로가 있어야 합니다.
- 더 나은 JIT 배포 방안. 정확히 하나의 LLVM 버전에 대한 장기적인 의존을 요구하지 않으면서, 재배포자에게 JIT 스텐실을 빌드하거나 검증할 수 있는 문서화되고 재현 가능한 방법을 제공합니다.
- JIT + GIL은 GIL 인터프리터 단독 대비 최소 5%의 성능 향상을 유지합니다. 다시 말해, 장기적인 목표를 추구하는 동안 기존의 성능 향상을 크게 후퇴시키지 않습니다. 또한 이 단계 동안 다른 기여자가 성능 개선에 기여하는 것을 막지 않습니다. 다만, 우리의 주된 초점은 개발자 경험 개선입니다.
- 2년 차(Python 3.17의 첫 베타까지) - 성능 향상
- pyperformance에서 JIT + 자유 스레딩이 자유 스레딩 인터프리터 단독 대비 기하 평균 최소 20%의 성능 향상을 달성합니다. 이는 JIT를 CPython
main에 유지하기 위한 최소 목표이며, 자유 스레딩을 주된 성능 초점으로 삼습니다.
- pyperformance에서 JIT + 자유 스레딩이 자유 스레딩 인터프리터 단독 대비 기하 평균 최소 20%의 성능 향상을 달성합니다. 이는 JIT를 CPython
- 2.5년 차(Python 3.17의 첫 릴리스 후보까지) - 채택과 호환성
- 호환성 검토. 선정된 인기 PyPI 패키지들과 대표적인 실제 워크로드의 테스트 스위트를 JIT 환경에서 실행합니다. 회귀는 사례별로 분류해야 하며, 실제 호환성 문제에 해당하는 이슈에는 수정을 적용하거나 문서화된 설명을 제공해야 합니다.
동기
CPython의 성능 향상은 Python의 미래에 필수적입니다. JIT 컴파일러는 사용자, 확장 모듈 작성자, 임베딩하는 쪽, 배포자, 디버거와 프로파일러가 이미 대상으로 삼고 있는 런타임을 유지하면서 CPython을 개선할 수 있는 몇 안 되는 성능 전략 중 하나입니다. Ruby, PHP, JavaScript 같은 다른 동적 언어들은 JIT 컴파일러를 성공적으로 활용하여, 기존의 거대한 생태계와의 호환성을 유지하면서도 상당한 성능 향상을 이루어 냈습니다. CPython에는 이들과 다른 제약 조건이 있지만, JIT는 명시적으로 그 제약 조건 안에서 성능을 향상시키는 것을 목표로 합니다.
PyPy와 GraalPy 같은 대체 Python 구현들은 일부 Python 프로그램에서 더 큰 속도 향상이 가능하다는 것을 보여 주며, 저희는 이 프로젝트들을 매우 소중하게 생각합니다. 하지만 많은 사용자는 지원되는 Python 버전, 확장 모듈 호환성, 임베딩 요구 사항, 배포 제약, 도구 지원 같은 요인 때문에, 대체 런타임이 자신의 코드에서 더 나은 성능을 낼 수 있는 경우에도 이를 채택하지 못합니다.
PEP 744는 JIT의 복사 후 패치(copy-and-patch) 방식을 설명하고, 더 많은 자원봉사자가 유지 관리할 수 있도록 구현을 CPython의 main 브랜치에 두어야 한다는 주장을 펼쳤으며, JIT가 언젠가 실험 단계를 졸업할 수 있는 몇 가지 기준을 개략적으로 제시하는 등 가치 있는 작업을 했습니다. 하지만 JIT에 관한 최초의 PEP는 보장 사항, 유지 보수 약속, 성공 지표, 일정, 도구 호환성, 재배포자에 대한 영향, 다른 JIT들과의 관계, 예상되는 아키텍처 발전에 관해 많은 질문을 미해결 상태로 남겼습니다.
현재의 CPython JIT는 특히 지난 9~12개월 동안 몇 가지 유망한 결과를 보여 주었습니다(부록 참조). 하지만 여느 좋은 실험과 마찬가지로, 현재의 접근 방식을 평가하고 지금까지 배운 것을 바탕으로 계획을 발전시키는 것이 중요합니다.
현재 상태
CPython 3.15 기준으로, 현재의 JIT 컴파일러는 측정된 Tier 1 플랫폼 전반에서 pyperformance 벤치마크 스위트의 기하 평균 기준으로 인터프리터보다 약 4~12% 빠릅니다(부록 참조). 이를 달성하기 위해 JIT와 이를 뒷받침하는 인프라는 Python의 최근 네 개 메이저 버전에 걸쳐 여러 차례 개정되었습니다.
- 3.12: CPython 바이트코드 DSL 도입, 인터프리터 바이트코드를 마이크로 연산(“uops”)으로 리팩터링.
- 3.13: JIT 트레이스 프로젝터, 최적화기, Copy and Patch(복사 후 패치) 백엔드가 도입되었고, PEP 744가 작성되었습니다.
- 3.14: 인터프리터 바이트코드의 추가 리팩터링과 최적화기 작업.
- 3.15: JIT 트레이서를 기록 방식으로 재작성, JIT 최적화기 개선, 커뮤니티 참여와 관여.
현재 JIT는 CPython에서 실험적이며 사용자가 직접 활성화해야 하는(옵트인) 부분입니다. Windows와 macOS용 공식 Python 바이너리는 JIT가 빌드된 상태로 배포되지만 기본적으로는 비활성화되어 있습니다(최종 사용자는 PYTHON_JIT=1로 활성화할 수 있습니다). 다른 배포자들과 Fedora, Gentoo 같은 일부 Linux 배포판도 같은 방식을 따르는 것으로 알려져 있습니다. 현재로서는 JIT가 스텐실 생성을 위해 빌드 시점에 LLVM을 필요로 합니다.
JIT에는 훌륭한 커뮤니티 기여자가 많이 모였으며, 최근 몇 달 사이에 그 흐름에 더욱 탄력이 붙었습니다. 저희는 이 자원봉사자분들께 깊이 감사드립니다. CPython 3.15의 JIT에 대한 What’s New 항목에 있는 기여자 목록에서 볼 수 있듯이, 현재 상당한 규모의 활발한 커뮤니티가 존재합니다. JIT 팀은 공개 이슈 트래커에 접근하기 쉬운 작업 단위를 마련하는 것과 멘토링처럼, 새로운 기여자를 끌어들이기 위한 중요한 교훈들을 얻었습니다.
교훈
JIT 프로젝트는 수명 주기 동안 계속 발전합니다. 예를 들어 CRuby는 여러 JIT 컴파일러를 거쳐 왔습니다. CPython의 JIT도 다르지 않습니다.
CPython의 JIT 컴파일러에는 개선할 부분이 있습니다. 장기적으로 지속 가능하고, 성능 목표를 달성하며, 커뮤니티 참여를 계속 촉진하려면 몇 가지 트레이드오프가 필요합니다. 도구 지원, 다른 JIT 프로젝트와의 호환성, 자유 스레딩이 일급 시민으로 다뤄져야 합니다.
저희 경험상 성공적이었다고 생각하는 부분이 몇 가지 있습니다:
- 바이트코드 DSL과 uop. 이 접근 방식은 반복되는 코드 단위를 해석 오버헤드 없이 공유할 수 있으므로 인터프리터에서도 유지 보수 부담을 줄입니다. 또한 바이트코드 검증을 통해 인터프리터를 수정할 때 오류가 생길 가능성도 줄일 수 있습니다. 현재 JIT가 성공하지 못해 제거 요청을 받더라도 이것들은 CPython에 남아 있어야 합니다. 또한 uop 자체가 자동으로 JIT의 중간 표현이 됩니다.
- 자체 도구를 사용한 JIT 변환기 자동 생성. CPython의 JIT는 바이트코드 DSL을 사용하여 바이트코드를 JIT 중간 표현(IR)으로 변환하는 규칙을 자동으로 생성할 수 있습니다. 이 역시 대부분의 JIT 변환이 구성상 올바르다는 뜻이며, 따라서 오류 가능성과 유지 보수 부담이 줄어듭니다. 이는 또한 JIT의 복잡성이 JIT 내부에 격리되어 있다는 뜻입니다. CPython에 추가되는 새 기능은 구현자가 JIT로 해당 기능을 최적화하려는 경우가 아니면 일반적으로 JIT 지원이 필요하지 않습니다. 예를 들어 초기 지연 임포트 풀 리퀘스트는 C에서 포함할 새 헤더를 추가한 것 외에는 JIT 파일을 전혀 건드릴 필요가 없었습니다.
- CPython 인터프리터와 닮은 JIT 최적화기. 현재 JIT 최적화기(미들엔드)는 CPython uop을 대상으로 타입 정보를 분석합니다. 여기서 유지 보수 측면의 핵심 이점은 미들엔드가 일반 CPython 인터프리터와 비슷한 방식, 즉 인터프리터 위의 바이트코드 DSL로 작성된다는 점입니다. 다만 객체를 해석하는 대신 객체의 타입을 해석합니다. 따라서 CPython 인터프리터에 대한 지식을 JIT 최적화기에도 그대로 활용할 수 있으며, 인터프리터의 바이트코드를 다룰 줄 아는 기여자라면 JIT의 미들엔드도 다룰 수 있습니다.
- 자체 도구를 사용한 JIT 기계어 코드 백엔드 자동 생성. JIT 기계어 코드가 인터프리터로부터 자동으로 생성되므로 CPython의 JIT에는 사람이 직접 작성한 별도의 연산이 필요하지 않습니다. 이는 JIT의 유지 보수 부담을 더욱 줄여 주며, 작은 팀으로도 매우 다양한 플랫폼에서 JIT를 유지 보수할 수 있게 해 줍니다.
- 트레이스 기록은 몇 가지 이점을 자연스럽게 제공합니다. 예를 들어 다형성, 추측, 죽은 코드 제거, 값 기록은 트레이스 기록 JIT에서 잘 처리됩니다.
- 트레이스는 시작하기 쉬운 출발점입니다. 트레이스 내부에는 제어 흐름이 없으므로 분석이 더 간단합니다.
- 커뮤니티가 유지 보수하는 JIT. 기업으로부터 일부 자금 지원을 받고 있지만(이에 감사드립니다), JIT 작업의 상당 부분은 자원봉사자들이 수행합니다. JIT를 기여자가 작업할 수 있는 이해하기 쉬운 단위로 나누는 것은 복잡성을 구획화하고 주인 의식을 북돋는 효과적인 방법입니다.
반대로, CPython에서 효과가 없었던 점과 개선할 수 있는 점에 대해서도 꽤 많이 배웠습니다:
- 커뮤니티 홍보와 참여를 계속 개선할 수 있습니다. 저희는 새로운 구성원의 합류를 돕기 위해 적극적인 조치를 취하고 있습니다. 그러나 더 넓은 커뮤니티와 지속적으로 소통하고 그들의 요청과 필요에 귀 기울이는 것이 프로젝트에 매우 중요합니다. 예를 들어 시스템 배포자나 서드파티 도구의 유지 관리자와 대화하고, 그들의 우려를 이해하며, 그들의 요구를 더 잘 수용하는 것이 여기에 포함됩니다.
- 현재 JIT 프런트엔드가 CPython에 적합한지 재검토. 현재의 트레이싱 JIT에는 몇 가지 큰 이점이 있습니다(배운 점 참조). 그러나 성공적인 JIT는 단순히 좋은 성능 이상의 것이며, 유지 보수성, 테스트 용이성, 교육 용이성 같은 다른 요소도 고려해야 합니다. 게다가 JIT가 성숙해짐에 따라 CPython에서 트레이싱이 주는 비용 대비 이점도 달라집니다. 분명히 말씀드리면, 이는 트레이싱이라는 접근 방식 자체에 대한 가치 판단이 아니라 CPython 내에서 트레이싱이 처한 상태에 대한 평가입니다. 다음은 저자들의 관찰이며, 저자 중 일부는 CPython 3.15의 현재 트레이싱 프런트엔드를 구현했습니다:
- CPython의 경우 트레이싱의 초기 용이성이 중기적으로는 이어지지 않는 것으로 보입니다. 배운 점에서 언급했듯이, 트레이싱은 시작하기 쉽습니다. 그러나 CPython에서 트레이싱을 단순하게 구현했을 때 3.13과 3.14에서는 처음에 속도 향상이 전혀 없었습니다. 더 복잡한 트레이싱 런타임으로 전환하고 인터프리터를 수정한 뒤에야 비로소 성능 향상을 얻을 수 있었습니다. 저희는 트레이싱 런타임이 성숙해 갈수록 트레이싱의 초기 용이성이 약해질 것이라고 생각합니다.
- 저희 경험상 성숙한 트레이싱 런타임은 복잡하기 때문에 관례에서 벗어난 영리한 “트릭”이 많이 필요한 것으로 보입니다. 예를 들어, 현재의 트레이스 기록 메커니즘은 인터프리터 실행을 효율적이고 효과적으로 기록하기 위해 이러한 트릭에 의존합니다. 트레이스 그래프와 그 수명을 관리하는 일도 복잡성을 더합니다. 저희는 JIT를 유지 보수하고 가르치기 쉽게 만들기 위해 이러한 트릭을 줄이고자 합니다. 메서드 기반 프런트엔드 같은 다른 프런트엔드도 트릭을 구현합니다. 그러나 이러한 프런트엔드는 다른 동적 언어 런타임에서 널리 쓰이기 때문에 최근 몇 년 동안 더 많이 연구되었고, 그만큼 문서화도 더 잘 되어 있는 것으로 보입니다.
- CPython에서 트레이싱이 일으키는 상호 작용은 전통적인 방식으로 가르치거나 분석하기 어렵습니다. 트레이싱은 AI/ML 컴파일러에서 흔히 볼 수 있지만, 전통적인 컴파일러 문헌에서는 덜 사용되고 덜 다루어집니다. 저희는 이 점이 컴파일러는 알지만 CPython은 모르는 새 기여자의 진입 장벽을 높인다고 생각합니다. 게다가 트레이스의 성능이 나쁠 때 트레이스와 CPython 인터프리터 사이의 상호 작용은 분석하기 어려울 수 있습니다. 성능을 예측하기 어려운 이유는 무수히 많을 수 있으며, 이를 분석하려면 인터프리터에 대한 깊은 이해도 필요합니다. 예를 들어, 현재의 트레이스 기록 런타임에는 트레이스를 계속할지 종료할지 결정하는 복잡한 트레이싱 휴리스틱이 있습니다. 한 기여자와 이 PEP의 저자 중 한 명은 이 휴리스틱을 제대로 만들기까지 여러 번 시도해야 했습니다(그들의 잘못은 전혀 아닙니다). 저희는 새 기여자에게 인터프리터를 깊이 분석하도록 요구하지 않고도 더 쉽게 가르치고 합류시킬 수 있기를 바랍니다.
- 현재 JIT가 더 큰 실제 워크로드에 도움이 되는지는 잘 파악하지 못하고 있습니다. 현재 JIT는 주로 pyperformance 벤치마크 스위트 실행을 통해 측정되고 평가됩니다. 저희는 실제 최종 사용자 코드에 JIT가 미치는 영향을 평가하는 데 더 많은 시간을 들이고자 합니다(호환성 검토를 참조하십시오).
- 배포 방식을 개선하고 명문화해야 합니다. 지금까지 배포자들의 피드백을 보면, 대부분이 최신 LLVM 툴체인을 제공할 수 있으므로 LLVM 자체가 항상 주된 걸림돌인 것은 아닙니다. 더 어려운 문제는 Python 릴리스의 수명 동안 정확히 한 LLVM 버전에 의존해야 한다는 점이며, 이 때문에 재배포자는 여러 LLVM 버전을 함께 유지하거나, 지원되지 않는 툴체인에 의존하거나, JIT를 비활성화하거나, 자체 스텐실 생성 워크플로를 유지해야 할 수 있습니다. 배포자가 실제로 활용할 수 있는 해결책을 명문화해야 합니다. PEP 774는 그러한 해결책 중 하나이지만, 각 Linux 배포판이 저마다 독자적인 해결책을 만들어 쓰는 일을 막으려면 더 많은 연구가 필요합니다.
근거
앞서 언급했듯이, JIT는 측정한 Tier 1 플랫폼에서 pyperformance 벤치마크 스위트 기준 기하 평균으로 대략 4-12% 빨라졌습니다(부록을 참조하십시오). 다만 몇 가지 한계와 과제, 개선할 부분이 있습니다. JIT의 다음 단계에서 저희는 앞으로 2.5년 안에(다시 말해 Python 3.17까지) 자유 스레드 빌드에서 인터프리터 대비 최소 20%의 성능 향상을 달성한다는, 야심 차지만 달성 가능한 초기 목표를 세우고자 합니다.
그러나 성능만을 위한 성능, 그리고 도구 비호환성을 대가로 얻은 성능은 프로젝트에 의미가 없고 매력적이지도 않다는 것을 저희는 알고 있습니다. 따라서 저희는 아래 명세 섹션에 자세히 열거한 명확한 계획을 가지고 의도적으로 이 다음 단계에 들어가고자 합니다.
명세
앞으로 2.5년 안에 도구 호환성을 완전히 유지하면서 지속 가능하고 유지 보수 가능한 20% 이상의 성능 향상을 달성하려면, 논의할 만한 영역이 몇 가지 있습니다:
- JIT 프런트엔드의 발전을 포함한 핵심 JIT 인프라
- 최적화
- 자유 스레딩에 대한 일급 지원
- 더 나은 배포 방식
- 호환성
- 도구 지원
핵심 JIT 인프라
전통적으로 컴파일러는 프런트엔드, 미들엔드, 백엔드로 나뉩니다. 이 문서의 맥락에서 각각은 다음과 같은 의미를 가집니다.
- 프런트엔드: 무엇을 컴파일할지 선택합니다. 선택 대상은 메서드일 수도 있고 CPython 특수화 바이트코드의 트레이스일 수도 있습니다.
- 미들엔드: 명령어를 최적화합니다. 특수화 바이트코드를 uop으로 변환하고 이를 최적화합니다.
- 백엔드: 기계어 코드를 생성합니다.
현재 프런트엔드는 트레이스 기록 방식을 사용합니다. 좀 더 자세히 설명하면, 트레이스 기록은 프로그램의 바이트코드를 따라 실제로 실행되는 흐름을 실행 중의 라이브 값과 함께 기록합니다. 이를 위해 인터프리터에 계측 코드를 추가했습니다. 이 프런트엔드는 3.13에서 처음 도입된 프런트엔드가 아닙니다. 3.13에서 도입된 프런트엔드는 여러 가지 이유로 당시에는 효과적이지 않은 것으로 보였습니다.
유지 보수 부담을 줄이고, JIT와 인터프리터를 분리하며, 향후 최적화를 지속 가능한 방식으로 가능하게 하기 위해 3.16까지 프런트엔드를 메서드 방식으로 변경할 것을 제안합니다. 메서드 프런트엔드가 목표를 달성하지 못하면 도중에 트레이스 기록 방식으로 되돌릴 수 있습니다.
프런트엔드 변경에는 대가가 따릅니다. 이 작업에 쓰는 시간은 현재의 트레이싱 프런트엔드에 최적화를 직접 추가하는 데 쓰지 못하는 시간이며, 일부 트레이스 고유의 성능 향상은 전환 후에 다시 확보해야 할 수도 있습니다. 그럼에도 이 기회비용이 정당하다고 보는 이유는 단 하나입니다. 현재 프런트엔드는 성숙해 갈수록 유지 보수, 교육, 디버깅, 최적화 비용을 점점 더 많이 요구할 것으로 보이며, 그 비용이 메서드 프런트엔드의 초기 구현 비용을 넘어설 것이기 때문입니다.
차이를 자세히 설명하면, 트레이스 기록은 코드를 관통하는 직선형 시퀀스를 기록하는 반면, 메서드 방식은 일반적으로 컴파일할 Python 함수를 하나 이상 선택합니다.
미들엔드와 백엔드는 큰 변경이 필요하지 않습니다. 현재 코드의 거의 전부를 메서드 프런트엔드에 재사용할 수 있습니다. Copy and Patch(복사 후 패치) 컴파일을 사용하는 현재 백엔드는 이미 제어 흐름의 브랜치와 점프를 지원합니다. uop에 대해 타입을 분석하는 미들엔드는 제어 흐름 합류 지점에서 타입 정보를 병합하는 기능만 지원하면 됩니다.
지난 몇 년간 얻은 교훈을 바탕으로 정한 메서드 프런트엔드의 목표는 다음과 같습니다.
- CPython의
main브랜치에서 불필요한 실험을 하지 않도록 최적화를 최대한 단순하고 전통적인 방식으로 만드는 것입니다. - JIT를 더 쉽게 유지 보수할 수 있도록 만드는 것입니다. 이는 코드 줄 수가 아니라 개념적 부담을 뜻합니다. 유지 관리자 한 사람이 시스템 전체를 머릿속에 “담아” 둘 수 있어야 하며, 상당히 복잡한 프로그램에서도 그 동작을 정확히 예측하고 이해할 수 있어야 합니다. 현재의 트레이싱 프런트엔드는 아주 단순한 벤치마크 프로그램에서조차 고개를 갸웃거리게 하는 결과를 낼 수 있습니다.
- 더 높은 JIT 티어(그 위에 또 다른 JIT를 덧붙여야 함)나 트레이스 간 지식 없이도 더 높은 수준의 최적화를 더 쉽게 할 수 있도록 하는 것입니다.
다음은 발전된 프런트엔드의 참조 설계입니다. 이 설계는 코드가 발전함에 따라 변경될 수 있습니다:
- 한 번에 하나 이상의 메서드를 컴파일합니다. 이름 그대로의 의미입니다.
- 스택에 대한 몇 가지 정적 단일 대입(SSA) 형식 속성. 현재 CPython 3.15 JIT 최적화기는 이미 이를 거의 지원하고 있으며, SSA 속성을 갖추는 데 최소한의 변경만 필요합니다. CPython 3.15 Uop IR도 SSA 형식과 유사한 유용한 속성을 이미 많이 갖추고 있습니다. 저희는 Uop IR에 SSA 속성을 몇 가지 더 도입하고자 합니다. 저희는 이것이 다른 컴파일러(예: Cinder, PyPy, Chrome의 V8, CRuby의 YJIT/ZJIT)와 더 가깝게 맞춰지며, JIT에서 어떻게 최적화할지 이해하는 일을 더 쉽고 강력하게 만든다고 믿습니다.
- 고수준 구조를 표현하는 간단한 방법. 메서드 프런트엔드에서 고수준 제어 흐름을 표현하기 위해, 저희는 MLIR(LLVM 프로젝트)의 같은 이름의 개념에서 영감을 받아 영역 (기본 블록의 그룹)을 형성하는 구현을 갖추고 있습니다. 프로그램을 서로를 가리키는 단일 기본 블록들로 격하시키는 대신, 저희는 고수준 구조 정보를 유지하는 방식을 택합니다. MLIR에서는 더 나은 루프 분석과 최적화가 그 동기였습니다. CPython의 JIT에서는 제너레이터/코루틴/루프 등(고수준 구조)에 대한 더 나은 분석과 최적화가 그 동기입니다.
다시 말해, 메서드 분석 지원이 늘어난 것을 제외하면 CPython 3.15에서 달라진 것은 없습니다. 위의 모든 사항을 바탕으로 JIT의 대부분의 최적화는 지역적 재작성(local rewrite)으로 구현할 수 있습니다. 이 역시 다른 런타임의 중간 표현이 가진 특정 속성에서 영감을 받은 것입니다. 저희의 목표는 최적화할 수 있는 범위를 희생하지 않으면서 JIT를 더 전통적이고 가르치기 쉽게 만드는 것입니다. 메서드 JIT에는 제어 흐름 병합이 필요하다는 점을 저희도 인정합니다. 그러나 트레이싱 JIT도 가장 기본적인 최적화 이외의 것을 구현하는 순간 제어 흐름 병합이라는 개념을 가르쳐야 하므로, 저희는 이것이 큰 개념적 부담이 아니라고 믿습니다.
이 프런트엔드를 구현하는 데 필요한 코드 측면에서 보면, 필요한 인프라의 대부분은 이미 존재합니다. 필요한 주요 코드 수정 사항은 제어 흐름 그래프를 표현하는 자료 구조와, 기존 최적화기/분석 패스를 구동하는 작업 목록(worklist) 알고리즘입니다. 현재의 트레이싱 프런트엔드 대부분을 JIT와 인터프리터에서 제거할 수 있으며, 이를 통해 인터프리터의 핵심 디스패치 메커니즘과 메인 인터프리터 루프가 단순해집니다. 저희는 이것들이 CPython에 낯선 개념이 아니라고 믿습니다 – CPython의 현재 바이트코드 컴파일러는 이미 제어 흐름 그래프를 표현하고 작업 목록 알고리즘을 갖추고 있습니다.
마지막으로, 메서드 JIT와 트레이싱 JIT 모두 뛰어난 성능을 달성하는 과정에서 복잡성이 늘어나며 다양한 트레이드오프를 가집니다. 트레이싱은 값 기록과 프로파일링이 더 단순한 반면, 메서드 방식에는 더 발전된 다형적 인라인 캐시가 필요합니다. 트레이싱은 더 높은 수준의 최적화를 얻기 위해 트레이스 간 최적화가 필요한 반면, 메서드 JIT는 더 많은 코드를 볼 수 있으므로 이를 더 간단하게 해결합니다. 두 방식 모두 뛰어난 성능을 달성하려면 인터프리터와 긴밀하게 결합되어야 합니다. 저희는 두 기술 모두 트레이드오프가 있음을 이해하며, 다시 한번 말씀드리지만 어느 쪽이 궁극적으로 더 나은지 판단하려는 것이 아닙니다. 저희의 주장은 단지 CPython에 필요한 최적화, 가르치고 디버깅하고 분석하기 쉬운 정도, 그리고 저희 문제에 대한 해결책을 유사한 언어 런타임에서 찾을 수 있다는 점을 고려할 때, 이 경우에는 궁극적으로 메서드 기반 JIT를 선택한다는 것입니다. 솔직하게 말씀드리고 추가로 필요할 수 있는 복잡성을 이해하실 수 있도록 덧붙이자면, 제안된 메서드 JIT에는 별도의 사이드 테이블에 추가 타입 프로파일링 데이터를 기록하는 것과 같은 (문헌에 나오는) 특정 추가 기능이 필요할 수도 있습니다. 그러나 현재의 트레이싱 JIT가 이미 하고 있는 방식과 유사하게, 현재의 바이트코드 DSL과 프로파일링 연산의 자동 생성을 통해 이 복잡성을 크게 완화할 수 있습니다. 또한 최적화에서 이를 완화하기 위한 해결책을 제안합니다. 따라서 저희는 개념적 측면과 유지보수 측면의 도약이 크지 않다고 믿습니다.
최적화
메서드 JIT는 현재의 트레이스 기록 JIT에 이미 있는 기존 최적화를 기반으로 합니다. 즉, 기존 JIT 미들엔드 덕분에 다음과 같은 최적화를 갖추게 됩니다:
- 타입 추측(특수화 적응형 인터프리터의 타입이 지정된 바이트코드를 통해)
- 불필요한 검사/가드 제거
- 중복 참조 카운팅 제거
- 상수 폴딩
현재 미들엔드에 대한 지식은 그대로 옮겨 갈 수 있으며, JIT 미들엔드는 최소한의 변경만으로 메서드 프런트엔드와 함께 동작할 수 있으므로 현재 미들엔드 작업에 참여해 온 기여자들이 새로 배울 것은 많지 않습니다.
프런트엔드를 전환하면 단기적으로 성능 면의 기회비용이 발생합니다. 트레이스 기록 프런트엔드는 거의 1년에 걸친 집중적인 작업의 혜택을 받아 왔으며, 그 성과 중 일부, 특히 트레이스 고유의 동작이나 자유 스레딩에서 안전하지 않은 최적화와 관련된 성과는 전환 후에 다시 되찾아야 할 수도 있습니다. 이 비용을 감수하는 이유는 메서드 프런트엔드가 그다음 단계의 더 큰 최적화들을 구현하고, 추론하고, 테스트하고, 유지 보수하기 쉽게 만들어 줄 것이기 때문입니다.
계획의 일환으로, 저희는 제너레이터/코루틴을 더 잘 최적화하고 호출의 효율성을 개선할 계획입니다. 이러한 고수준 최적화가 저희가 메서드 기반 JIT로 방향을 잡게 된 동기입니다. 이러한 최적화가 효과를 거두려면 사용자 코드를 더 많이 볼 수 있어야 하는데, CPython의 현재 트레이싱 JIT는 트레이스 간 최적화나 트레이스 스티칭 없이는 이를 달성할 수 없으며, 이러한 기법은 복잡성과 런타임과의 결합도를 높입니다.
추가적인 최적화도 가능합니다. 하지만 이러한 최적화는 트레이스 기록 프런트엔드를 사용하든 메서드 프런트엔드를 사용하든 크게 다르지 않습니다:
- 자유 스레딩에서의 잠금 제거
- 자유 스레딩에서 JIT의 이스케이프 지점을 줄이기 위한 지연 회수 감지
- 정수, 부동 소수점 수, 짧은 문자열의 보수적 언박싱
트레이싱이 공짜로 제공하는 최적화를 되찾기 위해, 저희는 다음을 탐구할 계획입니다:
- 인터프리터의 특수화기로부터 추가적인 타입 프로파일링 정보 기록
- 경로 분할(제어 흐름 그래프 복제)
- 콜드 코드 제거
- 미들엔드에서의 명령어 재특수화.
이것이 메서드 JIT에 많은 복잡성을 더한다고 주장할 수도 있습니다. 하지만 타입 프로파일링은 인터프리터를 전혀 변경하지 않으며 특수화기도 최소한으로만 변경합니다. 경로 분할은 표준 컴파일러 교과서에서 찾아볼 수 있습니다. 인터프리터가 이미 브랜치 정보를 추적하고 있으므로 현재 CPython에서 콜드 코드 제거는 구현하기가 매우 쉽습니다(한 번도 실행되지 않는 브랜치/블록은 컴파일하지 않기로 선택하면 됩니다). 재특수화는 기존 특수화기의 결정을 활용하여 수행할 수 있습니다. 이 역시 CPython의 경우 메서드 JIT가 인터프리터와 덜 얽힌다고 저희가 느끼는 흐름을 이어 갑니다.
자유 스레딩에 대한 일급 지원
자유 스레딩은 이미 Python의 미래 중 일부입니다. 현재의 JIT가 성능 향상을 위한 실용적인 선택지가 되려면 가능한 한 빨리 자유 스레딩에서 안전하게 동작하도록 만들어야 합니다. 이를 위해서는 프런트엔드와 미들엔드의 최적화를 자유 스레딩에서 안전하게 만들어야 합니다(백엔드는 이미 안전할 것입니다). 메서드 프런트엔드가 트레이싱 프런트엔드보다 자유 스레딩 지원을 더 어렵게 만들 것이라고는 예상하지 않습니다. 또한 지난 1년 동안 기존 JIT에 구현된 주요 최적화는 모두 이미 자유 스레딩을 염두에 두고 설계되었습니다. 다만 자유 스레딩에서 안전하지 않은 최적화를 제거하는 과정에서 처음에는 약간의 성능 저하가 발생할 수 있습니다. 예를 들어, 손봐야 할 주요 최적화는 전역/내장 딕셔너리 감시자와 타입 감시자가 될 것으로 예상합니다. JIT 컴파일 시점에 속성/전역 조회를 해석하는 것은 여전히 가능하겠지만, 그 가드를 완전히 제거하는 것은 자유 스레딩 환경에서 안전하지 않을 수 있습니다. 단순하게 수정하면 처음에는 약간의(기하 평균 1-2%) 성능 저하가 생길 것으로 예상합니다.
JIT 개발을 재개한 뒤에 추가되는 모든 최적화는 병합 전에 자유 스레딩 호환성과 성능 영향에 대한 검토를 반드시 거쳐야 합니다. GIL 빌드에만 의존하여 자유 스레드 빌드에서 깨지는 최적화는 거부됩니다.
게다가 JIT는 결국 자유 스레딩 빌드에서 현재의 GIL 빌드보다 더 나은 성능을 낼 수도 있습니다. JIT의 초기 실험에 따르면 자유 스레드 최적화로 pyperformance에서 몇 퍼센트포인트를 더 얻을 수 있을 것으로 보입니다. 예를 들면 다음과 같습니다:
- 자유 스레딩 빌드에서의 참조 카운팅은 GIL 빌드보다 비용이 크며, JIT는 참조 카운팅의 상당 부분을 제거할 수 있습니다.
- 지연된 참조 카운팅(PEP 703 참고)과 정지 상태 기반 회수(Quiescent State-Based Reclamation, QSBR) 덕분에 JIT는 특정 객체의 수명을 더 유연하게 다룰 수 있습니다. 이를 통해 즉시 회수 방식에서는 불가능한 최적화 기회가 더 많이 열립니다.
- JIT는 객체가 유일하게 참조된다는 것을 감지하면 특수화 적응형 인터프리터의 잠금과 원자적 연산을 제거할 수 있습니다. 원자적 연산의 비용이 더 큰 아키텍처에서는 이것이 속도 저하의 원인이 됩니다.
여기서 올바른 관점은 JIT냐 자유 스레딩이냐가 아니라, JIT와 자유 스레딩이라고 믿습니다. 자유 스레딩의 의미 체계 때문에 JIT가 처음에는 일부 성능 향상 기회를 잃을 수 있다는 점을 이해하고 있습니다. 하지만 JIT와 자유 스레딩 모두 얻을 것이 많습니다. JIT는 자유 스레딩으로 인한 단일 스레드 성능 손실을 모두 회복할 수 있고, 어쩌면 그 이상도 얻을 수 있습니다.
더 나은 JIT 배포 방안
PEP 774의 해결책을 채택하거나, 일정 범위의 여러 LLVM 버전으로 JIT를 빌드할 수 있도록 허용하는 방안 중 하나를 선택할 수 있습니다. 이는 더 많은 논의와 실험이 필요한 사항입니다. 최소한 주요 Linux 배포판 관리자들의 피드백을 수집하고 검토하여 종합적인 해결책에 반영해야 합니다. 현재 상황에 대한 저희의 이해로는 여러 LLVM 버전을 지원해야 합니다. 이로 인해 복잡성이 크게 늘어나지는 않을 것입니다. 다만 테스트를 위해 더 많은 CI 자원이 필요할 수 있습니다. JIT가 성공하려면 서드파티 배포자에게 과도한 부담을 주어서는 안 됩니다.
호환성 검토
로드맵의 일환으로 상위 PyPI 패키지들의 테스트 스위트를 실행하여 JIT 때문에 이 패키지들이 깨지는지 감지할 계획입니다. 이는 Sam Gross가 인기 있는 PyPI 패키지들을 실행하여 버그와 비호환성을 찾아냈던 초기 nogil 저장소의 접근 방식과 비슷합니다(추가적인 선행 사례는 labeille 프로젝트를 참고하십시오).
패키지의 테스트 스위트를 통과하지 못했다고 해서 곧바로 목표를 달성하지 못한 것은 아닙니다. 일부 테스트 스위트는 보장되지 않는 CPython 내부 세부 사항에 의존할 수 있습니다. 따라서 이는 사례별로 검토해야 합니다. 요점은 적어도 현존하는 Python 코드와의 JIT 호환성을 평가하기 위해 집중적인 노력을 기울였어야 하며, “실제” 버그를 수정하기 위해 최선을 다했어야 한다는 것입니다.
도구 지원
JIT는 현재의 네이티브 언와인딩(unwinding) 지원을 퇴보시키지 않을 것입니다. 다시 정리하면, JIT는 현재 모든 프레임 포인터 기반 언와인더와 eh_frame 기반 언와인더(GNU backtrace 등)도 지원합니다.
JIT는 Python 프레임이 필요한 프로세스 외부 프로파일러/디버거를 계속 지원할 것입니다. 프레임 생략(인라이닝)이 유망한 최적화라는 점은 저희도 이해하고 있습니다. 하지만 JIT에서 프레임을 완전히 생략하면 서드파티 도구가 동작하지 않게 됩니다. 생략된 프레임의 메타데이터를 저장하는 등, Python 프레임 언와인더가 생략된 프레임을 복원하는 데 필요한 정보를 얻을 수 있는 대안적인 방법을 신중하게 협의하고 제공할 것입니다. 또한 Python 스택을 검사하는 도구는 JIT C 심(shim) 프레임을 심볼화(즉, Python 함수 호출과 연관 짓기)해야 할 수도 있습니다. 이 경우 이러한 도구를 지원하는 데 필요한 모든 정보는 실행기(executor) 객체나 다른 수단을 통해 CPython 런타임에서 제공되며, 이러한 도구가 JIT 프레임이 포함된 호출 스택을 해석할 수 있도록 디버그 오프셋에서도 제공될 것입니다. 이를 위해 널리 쓰이는 Python 프레임 언와인딩 애플리케이션의 유지 관리자들과 협의할 수 있습니다. 일반 원칙은 다음과 같습니다. JIT를 끈 상태에서 동작하는 것이 있다면, JIT를 켠 상태에서도 동작하도록(또는 사용 가능한 대안이 있도록) 할 수 있는 모든 일을 해야 하며, 순수한 성능을 명목으로 정말로 유용한 관측 가능성 및 디버깅 기능을 망가뜨려서는 안 됩니다.
JIT는 프로세스 내부 도구를 계속 지원할 것입니다. 즉, sys._getframe, pdb 또는 sys.monitoring을 망가뜨리지 않을 것입니다.
다른 JIT 및 컴파일러 도구와의 관계
CPython의 JIT는 CinderX, Numba, PyTorch Compile 또는 기타 도메인 특화 컴파일러와 같은 서드파티 전문 JIT나 컴파일러 프로젝트를 대체하기 위한 것이 아닙니다. 이러한 프로젝트는 대개 다른 워크로드를 최적화하거나, 다른 가정을 사용하거나, 스택의 다른 계층에서 동작합니다. JIT는 현재와 마찬가지로 결국 인터프리터 자체가 책임지게 되는 모든 코드의 실행을 위한 “최후 방어선(backstop)”이 되는 것을 목표로 합니다.
플랫폼 지원
JIT는 이 문서 작성 시점에 PEP 11에 명시된 모든 Tier 1 플랫폼을 지원할 것입니다:
| 대상 트리플 | 참고 사항 |
|---|---|
| aarch64-apple-darwin | clang |
| aarch64-unknown-linux-gnu | glibc, gcc |
| i686-pc-windows-msvc | |
| x86_64-pc-windows-msvc | |
| x86_64-unknown-linux-gnu | glibc, gcc |
그러나 32비트 Windows 성능 개선에 별도의 개발 주기를 집중할 계획은 없습니다. PyPI 통계를 보면 32비트 Windows 빌드의 다운로드 수가 극히 적으므로, 이 플랫폼은 목표에서 제외하고자 합니다. 또한 관례적으로 JIT를 쓰지 않는 다른 플랫폼(예: WASI)이 나중에 Tier 1로 승격되더라도 해당 플랫폼 역시 지원할 계획은 없습니다.
Copy and Patch(복사 후 패치) 백엔드 덕분에 JIT는 저희 쪽에서 추가 작업을 최소한으로만 들이고도 관심 대상 플랫폼을 지원합니다. 핵심 아이디어는 IR에 대응하는 기계어 코드를 손으로 직접 작성하지 않는 것입니다. 그렇게 하면 변경이 너무 잦아지고, CPython의 빠른 바이트코드 변화 속도를 감당할 수 없기 때문입니다.
유지 관리 모델
JIT는 프런트엔드, 최적화기, 코드 생성 백엔드라는 세 단계에 걸쳐 작업하는 CPython 코어 개발자와 기여자 그룹이 유지 관리합니다. JIT의 핵심 목표 중 하나는 각 단계를 잘 아는 활동 중인 유지 관리자를 두 명 이상 확보해 JIT의 어떤 부분도 한 사람에게 의존하지 않도록 하는 것이었습니다. 기여자 기반은 우연이 아니라 의도적으로 성장해 왔습니다. 3.15 주기 동안 최적화 작업을 개별적으로 실행 가능한 작은 작업들로 분해했습니다. 그 결과 기여의 진입 장벽이 낮아졌고, 약 12명이 트레이스 기록 전환 작업에 참여했으며, 동시에 반복적으로 참여하는 옵티마이저 기여자의 수가 늘었습니다. 이러한 작업 분해는 기여자를 유입하고 붙잡아 두기 위해 계속 운영하는 메커니즘이며, 프로젝트가 장기적으로 유지 관리자 풀을 유지하고 넓혀 가려는 방식입니다.
현재 이 프로젝트는 어느 한 후원자에게 의존하지 않습니다. 초기의 주요 기업 후원자가 전담 자금 지원을 줄여 종료한 뒤에도 이 프로젝트는 커뮤니티 주도로 이어져 왔습니다. 현재는 자원봉사 작업과 기업의 지속적인 기여를 함께 받고 있으며, 기업 기여는 주로 Arm, FastAPI Labs, OpenAI에서 옵니다. JIT를 지속하려면 공유되는 핵심 인프라도 필요합니다. 하나는 JIT 빌드를 검증하는 지속적 통합 및 빌드 구성으로, 현재 main에서 일반 CI의 일부입니다. 다른 하나는 야간 결과를 게시하는 자체 호스팅 벤치마킹 머신과 인프라입니다. 현재 Savannah가 유지 관리하며, 머신은 Savannah와 Arm이 제공했습니다.
마지막으로, 그리고 아마도 가장 중요하게, JIT는 JIT 작업을 하지 않는 기여자들도 접근할 수 있는 상태로 유지되어야 합니다. 이를 위해 인터프리터를 이해하기 쉽게 유지하거나 JIT와 분리된 상태로 유지하고, 생성된 코드를 다시 생성하는 방법과 변경 사항을 기여하는 워크플로를 문서화하며, 내부 구조 문서를 최신 상태로 유지하겠다고 약속합니다. 3.15에 포함된 최적화기 연산의 단순화(배운 점 참고)는 이러한 유지 관리 투자가 실제로 이루어진 사례입니다. JIT를 빌드하여 배포하는 재배포자의 의무는 더 나은 JIT 배포 방안 섹션에 설명되어 있습니다.
하위 호환성
JIT는 최적화일 뿐 언어의 변경이 아니므로, 핵심 호환성 보장은 JIT가 활성화된 빌드가 JIT가 없는 빌드와 구별할 수 없이 동작하되 더 빠르기만 해야 한다는 것입니다. 즉, 결과가 같고, 예외와 트레이스백이 같으며, 지원되는 인트로스펙션 가능 상태도 같아야 합니다.
앞서 다룬 바와 같이, CPython에서 JIT를 지원 기능으로 간주하기 위한 요건으로 상위 PyPI 패키지들의 테스트 스위트에 대해 호환성 분석을 수행할 것입니다.
보안 영향
PEP 744에서 언급했듯이, CPython의 JIT는 다른 모든 JIT와 마찬가지로 런타임에 대량의 실행 가능한 데이터를 생성합니다. 이는 모든 JIT 컴파일러에 존재하는 공격 벡터입니다. 이 데이터의 내용에 영향을 줄 수 있는 악의적인 행위자는 임의의 코드를 실행할 수 있습니다.
이러한 위험을 완화하기 위해 JIT는 모범 사례를 염두에 두고 작성되었습니다. 특히 JIT 컴파일러는 해당 데이터가 쓰기 가능한 상태인 동안에는 이를 프로그램의 다른 부분에 노출하지 않습니다. 또한 이 데이터가 쓰기 가능하면서 동시에 실행 가능한 상태가 되는 시점은 없습니다.
또한 템플릿 기반 JIT의 특성상 생성할 수 있는 코드의 종류가 크게 제한되므로 익스플로잇이 성공할 가능성은 더욱 낮아집니다. 추가 예방 조치로 템플릿 자체는 정적인 읽기 전용 메모리에 저장됩니다.
그러나 특히 지금처럼 초기 단계에서 JIT에 어떠한 취약점도 없다고 가정하는 것은 순진한 생각일 것입니다. 저자들은 보안 전문가가 아니지만, 보안 문제가 생기면 Python Security Response Team과 긴밀히 협력하여 문제를 분류하고 수정할 것입니다.
Fedora 유지 관리자들도 CET/BTI 지원을 요청했습니다(python/cpython#149697). 보안 목표를 달성하려면 생성된 스텐실에서 이 옵션을 지원해야 한다고 생각합니다.
마지막으로, PEP 744가 시작된 이후 JIT를 퍼징하기 위한 여러 퍼징 프로젝트가 시작되었습니다. 예를 들어, lafleur는 충돌이나 잘못된 최적화로 이어지는 수많은 JIT 버그를 찾아냈습니다(대부분 백엔드가 아닌 JIT 미들엔드에서 발견되었습니다). 앞으로도 이러한 프로젝트를 사용하여 JIT를 퍼징할 것입니다.
교육 방법
대다수 Python 사용자에게 JIT에 관해 가르쳐야 할 가장 중요한 점은 그들이 해야 할 일도, 주의해야 할 일도 없다는 것입니다. JIT의 이점을 누리기 위해 코드를 다시 작성할 필요가 없어야 하며, 올바르게 동작하도록 유지하기 위해 코드를 변경할 필요도 없어야 합니다.
멘탈 모델을 원하는 사용자에게는 짧고 정확한 설명이면 충분합니다. JIT는 인터프리터 위에 위치하는 최적화 계층으로, 자주 실행되는 코드를 실행 중에 즉석에서 기계어로 컴파일합니다. JIT는 프로그램이 무엇을 하는지가 아니라 프로그램이 얼마나 빠르게 실행되는지를 바꿉니다. 대부분의 교육 맥락에서는 이 정도 설명으로 충분하며, 내부 구조(예: uop, 최적화기, 코드 생성)를 가르칠 필요는 없습니다.
두 부류의 대상에게는 더 구체적인 안내가 필요합니다. 바로 재배포자와 패키저입니다. 이러한 사용자는 빌드 시점 요구 사항과 배포 가능한 산출물로 나아가는 경로를 이해해야 합니다(“더 나은 JIT 배포 방안”을 참조하십시오). 디버거, 프로파일러, 그 밖의 네이티브 도구의 유지 관리자는 지원되는 플랫폼에서 JIT 프레임을 언와인드할 수 있다는 점과, 실행 중인 프로세스를 검사할 때 무엇에 의존할 수 있는지를 알아야 합니다. Python 스택 언와인더는 JIT 프레임 레이아웃을 이해하고 심볼화 과정에서 정보를 복원해야 합니다(“도구 지원” 참고).
마지막으로, 코어 개발자는 자신의 기능이 JIT로 최적화되기를 원하는 경우에만 JIT에 신경 쓰면 됩니다. 그렇지 않은 경우, 현재 JIT 아키텍처에서는 인터프리터나 런타임의 다른 부분을 작업하는 코어 개발자가 가끔 발생하는 CI 실패를 제외하면 JIT가 존재한다는 사실에 신경 쓸 필요가 없습니다. JIT가 지원 대상으로 간주되면, 어떠한 새로운 변경으로도 JIT가 치명적으로 망가져서는 안 됩니다. 그러나 기여자가 JIT가 자신의 기능을 지원하거나 그 기능에 맞춰 최적화하기를 명시적으로 원하는 경우가 아니라면, 거의 모든 경우에 Python에 새 기능을 도입하는 일이 JIT 때문에 가로막히지는 않을 것으로 예상합니다. 다시 한번 예를 들어, 바이트코드를 수정했지만 새로 도입된 헤더를 #include하는 것 외에는 JIT를 수정할 필요가 없었던 지연 임포트 초기 구현을 참조하십시오.
참조 구현
현재 JIT 구현은 CPython의 main 브랜치에서 찾을 수 있으며, 주로 다음 위치에 있습니다.
Tools/jit/README.md: JIT를 빌드하는 방법에 대한 안내입니다.Python/jit.c: JIT 컴파일러의 백엔드 부분 전체입니다.Python/optimizer.c: JIT 컴파일러 프런트엔드의 일부입니다(일부는Python/ceval.c에서 공유됩니다).Python/optimizer_analysis.c: JIT 컴파일러의 미들엔드입니다.Python/optimizer_bytecodes.c: JIT 컴파일러 미들엔드의 최적화 규칙입니다.jit_stencils.h: JIT가 빌드 시점에 생성하는 템플릿의 예시입니다(현재 CPython 저장소에는 체크인되어 있지 않습니다).Tools/jit/template.c: JIT의 템플릿을 생성하기 위해 컴파일되는 코드입니다.Tools/jit/_targets.py: 빌드 시점에 템플릿을 컴파일하고 파싱하는 코드입니다.
이 PEP는 JIT의 발전 방향(기존 코드를 적극적으로 재사용하면서 트레이싱 방식에서 메서드 기반 방식으로 전환하는 것)을 제안하고 개략적으로 설명하지만, 그 설계의 특정 구현을 규정하지는 않습니다. 그렇기는 하지만, main을 대상으로 한 동작하는 개념 증명 구현이 존재하며 곧 공유될 예정입니다.
현재 개발 중이며 불완전함에도 불구하고(예를 들어 아직 제너레이터와 코루틴을 처리하지 못하고 다형성도 지원하지 않는데, 이 두 가지는 기존 트레이싱 프런트엔드에서 부분적으로 지원됩니다), GIL이 활성화된 빌드에서 pyperformance 및 Pyston 매크로벤치마크 모음 기준으로 JIT를 끈 경우보다 여전히 4-5% 더 빠릅니다. 이는 불과 몇 달 만에 개발된 새로운 설계가 기존 트레이싱 설계와 경쟁할 수 있음을 보여 줍니다. 기존 트레이싱 설계는 3년간 발전시켜 온 끝에 동일한 x86-64 Linux 구성에서 7-8% 더 빠릅니다.
테스트를 제외하면, main 대비 현재 메서드 JIT 구현의 규모는 대략 다음과 같습니다:
| 파일 유형 | 변경된 파일 | 추가된 줄 | 삭제된 줄 |
|---|---|---|---|
| 생성된 파일 | 13 | 4200 | 5300 |
| 생성되지 않은 파일 | 38 | 5800 | 2700 |
| 합계 | 51 | 10000 | 8000 |
파일 확장자별로 나누면 다음과 같습니다:
| 확장자 | 변경된 파일 | 추가된 줄 | 삭제된 줄 |
|---|---|---|---|
| .c | 20 | 5400 | 2300 |
| .c.h | 6 | 2400 | 3300 |
| .h | 20 | 2100 | 2300 |
| .py | 5 | 100 | 100 |
| 합계 | 51 | 10000 | 8000 |
기각된 아이디어
CPython main 외부에서 JIT 유지하기
JIT의 역사 전반에 걸쳐, 그리고 최근 논의에서도 이 정도로 복잡한 컴파일러는 CPython의 main 브랜치가 아니라 트리 외부나 별도 프로젝트에서 개발하고 유지 관리하는 편이 더 나을 수 있다는 제안이 있었습니다. 그러나 JIT를 main에 두는 것은 PEP 744에서 처음 밝힌 대로 의도적이고 매우 유익한 선택입니다. 이렇게 하면 JIT를 인터프리터와 함께 개발할 수 있고, 포크에서 작업하는 소수의 전문가가 아니라 더 폭넓은 코어 개발자 및 기여자 집단이 JIT를 유지 관리할 수 있습니다. JIT가 사용하는 uop 역시 인터프리터와 함께 설계되며 인터프리터로부터 재생성됩니다. 트리 외부의 JIT는 브랜치 경계를 넘어 이러한 정의를 추적해야 하며, 이는 정확성이 가장 중요한 바로 그 영역에서 유지 관리 비용과 불일치가 생길 위험을 높입니다. 3.15 주기 동안 기여자 기반이 성장한 것(Maintenance Model 참조) 자체가 트리 내부 개발이 참여 장벽을 높이는 것이 아니라 낮춘다는 증거입니다. 또한 JIT를 CPython의 main 브랜치에 두면 배포자의 요구를 더 잘 파악할 수 있으며, 최종 사용자가 JIT를 더 쉽게 사용해 보고 어떤 동작을 관찰했는지, 어떤 문제를 발견했는지 저희에게 알려 줄 수 있습니다.
플러그인 방식의 JIT 인프라
또 다른 반복적인 아이디어는 CPython이 트리 내부에서 JIT를 유지 관리하는 대신, 임의의 서드파티 JIT 컴파일러를 연결할 수 있는 안정적인 범용 인터페이스를 노출하자는 것입니다. 이 PEP는 CPython main 외부에서 JIT를 유지 관리하자는 아이디어와 같은 이유로 이 아이디어를 기각합니다. 플러그인 방식의 JIT를 도입하면 기여자의 노력이 분산될 위험이 있고 유지 관리 부담이 커집니다. 예를 들어, 3.13의 초기 JIT 버전에는 반공개 실험적 API가 있었습니다. 그러나 이 API는 내부 세부 사항을 “사용자”(실제로는 아무도 없었습니다)에게 노출했고 내부 JIT 개발을 더 어렵게 만들었습니다. 그래서 저희는 이 API를 제거했습니다. 게다가 대부분의 언어 런타임 JIT는 각자의 런타임과 깊이 통합되어 있어서, 플러그인 방식의 JIT 인프라는 실현 가능하지 않을 수도 있습니다.
하지만 CinderX나 TorchDynamo처럼 PEP 523을 사용하여 CPython 외부에서 JIT를 유지 관리하는 노력은 높이 평가할 만하다는 데 저희도 동의합니다. 기존 인터페이스를 개선하기 위한 논의는 별도의 PEP에서 다루는 것이 가장 좋다고 생각하며, 이 PEP의 범위를 벗어난 것으로 간주합니다.
빌드 시점 LLVM 요구 사항 제거
이 PEP는 빌드 시점에 JIT가 LLVM 툴체인에 의존하는 것을 변경하자고 제안하지 않습니다. 저희는 빌드 시점의 불편을 줄이는 것을 중요하게 여기지만, 여기에서 제시한 방향에 합의하기 위한 전제 조건으로 보지는 않습니다. PEP 774는 LLVM 사전 요구 사항을 제거하는 해결책을 제안하지만, 제출 당시 재임 중이던 운영 위원회(Steering Council)는 JIT가 더 상당한 성능 향상을 달성할 때까지 이에 대한 결정을 미루기로 했습니다. 저희는 이 영역에서 선택지를 계속 탐색하고자 하며, 따라서 이 문제는 별도의 PEP로 남겨 두고자 합니다.
상위 계층 JIT
저희는 다중 계층 JIT가 뛰어난 성능과 매력적인 워밍업 시간을 제공한다고 믿습니다. 그러나 당분간은 CPython의 복잡성과 유지보수 여력으로 그러한 시도를 감당하기 어려울 수 있다고도 생각합니다. 이것이 결코 일어나서는 안 된다고 말하는 것은 아닙니다. 오히려 저희의 목표는 감당할 수 있는 제약 조건 안에서 CPython의 현재 상태에 맞는 최선의 JIT를 만드는 것입니다. 이를 위해 최고 성능을 얻고자 현재 JIT 위에 또 다른 JIT를 구축하는 방안은 기각합니다.
현재 JIT를 있는 그대로 활성화/지원하기
현재 JIT는 의심할 여지 없이 많은 관심과 정성을 기울인 결과물입니다. 이에 기여해 주신 모든 분께 감사드립니다. 그러나 저희는 커뮤니티 전반에 아직 해결되지 않아 보완이 필요한 우려가 남아 있다는 점을 이해하고 있습니다. 또한 Python, 나아가 CPython은 매우 널리 사용되므로, 이 정도 규모의 변경은 프로젝트의 정식 일부가 되기 전에 적절히 검토하고 숙고해야 한다는 점을 인정합니다. 따라서 현재 JIT는 더 면밀한 검토와 발전 없이는 활성화할 수 없습니다.
미해결 문제
현재로서는 없습니다.
부록
머신별 평균 JIT 속도 향상 (2026-06-16부터 2026-06-27까지 계산)
| 머신 | 구성 | 평균 속도 향상 | 결과 | 일수 | 범위 |
|---|---|---|---|---|---|
| jones (M3 Pro, macOS) | JIT+TAILCALL | 1.126x | 12.6% 더 빠름 | 9 | 1.050-1.180 |
| sulaco (AmpereOne, Linux aarch64) | JIT | 1.073x | 7.3% 더 빠름 | 7 | 1.060-1.080 |
| ripley (i5-8400, Linux x86_64) | JIT | 1.069x | 6.9% 더 빠름 | 9 | 1.060-1.070 |
| prometheus (Ryzen 5 3600X, Windows) | JIT+TAILCALL | 1.047x | 4.7% 더 빠름 | 9 | 1.040-1.050 |
Note
일반 CPython 빌드는 테일 콜이 활성화된 상태로 배포되므로 Windows와 macOS 실행에는 JIT+TAILCALL을 사용한다는 점에 유의하십시오. 이 계산에 사용한 모든 데이터는 Does JIT Go Brrr?에서 확인할 수 있습니다.
변경 이력
현재로서는 없습니다.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.