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 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
실험적인 Just-in-Time(JIT) 컴파일러는 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의 첫 번째 베타 릴리스까지, 지원되는 Tier 1 플랫폼 전체에서 평균을 내어 측정했을 때, JIT + 자유 스레드 빌드가 비 JIT 자유 스레드 빌드의 인터프리터에 비해 pyperformance에서 최소 20%의 성능 기하 평균 향상을 달성하는 것입니다. 이 목표는 JIT의 트리 내 개발을 계속하기 위한 최소 기준으로 설정됩니다.
제안
이 PEP는 JIT를 즉시 지원되는 것으로 선언하자는 제안이 아닙니다. 대신 프로젝트가 명시적인 성능, 호환성, 도구, 배포, 보안 및 유지 관리 목표를 충족하는 동안 CPython의 main에서 JIT 개발을 유지하기 위한 기간이 정해진 경로를 제안합니다.
이러한 목표가 충족되면 JIT를 CPython의 비실험적 기능으로 승격할 수 있습니다. 목표가 충족되지 않으면 Steering Council과 핵심 팀은 JIT를 CPython의 main에 유지해야 하는지 재평가해야 합니다. 승격 후 지원되는 플랫폼에서 JIT를 기본적으로 활성화하려면 Release Manager의 별도 최종 승인이 필요합니다.
큰 틀에서 향후 2.5년 동안 JIT에 대해 설정한 마일스톤과 목표는 다음과 같습니다.
- 1년 차(Python 3.16의 첫 번째 베타로 종료) - 개발자 경험 개선
- 프런트엔드를 트레이스 기록 방식에서 메서드 기반 방식으로 발전시킵니다. 메서드 프런트엔드가 더 쉬운 유지 관리, 교육, 디버깅 등을 가능하게 하는 방향으로 나아가게 해 줄 것이라고 생각합니다. 첫 번째 구현은 최소한으로 구성해야 하며, 초기에는 메모리를 더 많이 사용하거나 성능이 약간 낮을 수 있고, 첫해에 이 접근 방식이 프로젝트의 목표를 충족하지 못하면 현재의 트레이싱 프런트엔드로 되돌릴 수 있습니다. JIT의 나머지 부분(중간 표현, 미들엔드/옵티마이저 및 Copy and Patch 백엔드)은 CPython 3.15에서와 거의 완전히 동일하게 유지됩니다. 다시 말해, JIT가 컴파일 대상으로 선택하는 것만 트레이스에서 메서드로 발전하며, CPython 3.15에서 다른 어떤 것도 변경되지 않습니다.
- JIT를 자유 스레딩과 호환되도록 만듭니다. 자유 스레딩의 도입이 빠르게 확대되고 있으므로, JIT의 다음 단계에서는 이를 초기에 우선하는 것이 중요하다고 생각합니다.
- 네이티브 및 Python 프로파일러와 디버거에 대한 추가 테스트를 수행하고 발견된 나머지 커버리지 공백을 해결합니다. 최소한 여기에는 프레임 포인터를 사용하여 스택을 언와인드하는 모든 항목이 포함되며, Python 프레임의 심볼을 확인하는 도구에 대한 지원까지 확장해야 합니다. 기존 동작을 정확히 보존할 수 없는 경우 서드파티 도구에는 문서화된 해결 경로가 있어야 합니다.
- 더 나은 JIT 배포 방안. long-term dependence on one exact LLVM version을 요구하지 않으면서, 재배포자에게 JIT 스텐실을 빌드하거나 검증할 수 있는 문서화되고 재현 가능한 방법을 제공합니다.
- JIT + GIL이 GIL 인터프리터 단독 대비 5% 이상 향상되어야 합니다. 다시 말해, 장기적인 목표를 추구하는 동안 기존의 성능 향상을 크게 후퇴시키지 않을 것입니다. 또한 이 단계에서 다른 기여자들이 성능 향상에 기여하는 것을 막지 않을 것입니다. 그러나 우리의 주된 초점은 개발자 경험 개선에 맞춰질 것입니다.
- 2년 차(Python 3.17의 첫 번째 베타로 종료) - 성능 개선
- JIT + 자유 스레딩이 자유 스레딩 인터프리터 단독 대비 pyperformance에서 최소 20%의 성능 기하 평균 향상을 달성해야 합니다. 이는 CPython의
main에 JIT를 유지하기 위한 최소 목표이며, 자유 스레딩을 주요 성능 초점으로 취급합니다.
- JIT + 자유 스레딩이 자유 스레딩 인터프리터 단독 대비 pyperformance에서 최소 20%의 성능 기하 평균 향상을 달성해야 합니다. 이는 CPython의
- 2.5년 차( Python 3.17의 첫 릴리스 후보로 마무리됨) - 도입 및 호환성
- 호환성 검토. JIT에서 선택된 인기 있는 PyPI 패키지와 대표적인 실제 워크로드의 테스트 모음을 실행하십시오. 회귀는 실제 호환성 중단을 반영하는 문제에 대해 수정 사항이나 문서화된 설명을 제공하면서 사례별로 분류해야 합니다.
동기
CPython의 성능을 개선하는 것은 Python의 미래에 필수적입니다. JIT 컴파일러는 사용자, 확장 작성자, 임베더, 배포자, 디버거 및 프로파일러가 이미 대상으로 삼는 런타임을 유지하면서 CPython을 개선할 수 있는 몇 안 되는 성능 전략 중 하나입니다. Ruby, PHP, JavaScript와 같은 다른 동적 언어들은 대규모 기존 생태계와의 호환성을 유지하면서 상당한 성능 향상을 제공하기 위해 JIT 컴파일러를 성공적으로 사용해 왔습니다. CPython에는 서로 다른 제약이 있지만, JIT는 그러한 제약 안에서 성능을 개선하는 것을 명시적인 목표로 합니다.
PyPy와 GraalPy 같은 대체 Python 구현은 일부 Python 프로그램에서 더 큰 속도 향상이 가능함을 보여 주며, 저희는 이러한 프로젝트를 매우 소중하게 생각합니다. 그러나 지원되는 Python 버전, 확장 호환성, 임베딩 요구 사항, 배포 제약, 도구 지원 등의 요인으로 인해 대체 런타임이 자신의 코드에서 더 나은 성능을 낼 수 있더라도 많은 사용자는 이를 도입할 수 없습니다.
PEP 744는 JIT의 복사 및 패치 접근 방식을 설명하고, 더 광범위한 자원봉사자 그룹이 유지 관리할 수 있도록 구현을 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 프로젝트는 수명 주기 동안 진화하며, 여러 JIT 컴파일러를 거친 CRuby가 그 예입니다. CPython의 JIT도 다르지 않습니다.
CPython의 JIT 컴파일러에는 개선할 영역이 있습니다. 장기적으로 지속 가능하고 성능 목표를 달성하며 커뮤니티 참여를 계속 촉진하려면 특정한 절충이 필요합니다. 도구, 다른 JIT 프로젝트와의 호환성, 자유 스레딩은 일급 시민이어야 합니다.
저희의 경험에 따르면 성공적이었다고 생각하는 영역이 몇 가지 있습니다:
- 바이트코드 DSL과 uops. 이 접근 방식은 인터프리터에서도 유지 관리 부담을 줄입니다. 반복되는 코드 단위를 해석 오버헤드 없이 공유할 수 있고, 바이트코드 검증을 통해 인터프리터를 수정할 때 오류 발생 가능성도 줄일 수 있기 때문입니다. 현재 JIT가 성공하지 못해 제거를 요청받더라도 이러한 요소는 CPython에 남아 있어야 합니다. uops 자체도 JIT의 중간 표현을 자동으로 구성합니다.
- 자체 도구를 사용하여 JIT 변환기를 자동으로 생성하기. CPython의 JIT는 바이트코드 DSL을 사용하여 JIT 중간 표현(IR) 규칙에 해당하는 바이트코드를 자동으로 생성할 수 있습니다. 다시 말해 대부분의 JIT 변환이 구성상 올바르게 이루어지므로 오류 발생 가능성과 유지 관리 부담이 줄어듭니다. 이는 또한 JIT의 복잡성이 자체적으로 국한된다는 의미입니다. 일반적으로 CPython에 추가되는 새 기능은 구현자가 JIT로 해당 기능을 최적화하려는 경우가 아니면 JIT 지원을 필요로 하지 않습니다. 예를 들어 초기 지연 임포트 풀 리퀘스트는 C에서 포함할 새 헤더를 추가하는 것 외에는 어떤 JIT 파일도 수정할 필요가 없었습니다.
- CPython 인터프리터와 유사한 JIT 옵티마이저. 현재 JIT 옵티마이저(미들엔드)는 CPython uops에 대한 타입 정보를 분석합니다. 여기서 핵심적인 유지 관리상의 이점은 미들엔드가 일반적인 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의 경우 트레이싱을 처음 시작할 때의 용이성은 중기적으로 지속되지 않는 것으로 보입니다. Learnings에서 언급했듯이, 트레이싱은 시작하기 쉽습니다. 그러나 CPython의 단순한 트레이싱 구현은 처음에는 3.13과 3.14에서 속도 향상을 가져오지 못했습니다. 더 복잡한 트레이싱 런타임으로 전환하고 인터프리터를 수정한 후에야 성능 향상을 경험했습니다. 트레이싱 런타임이 성숙함에 따라 트레이싱을 처음 시작할 때의 용이성이 줄어들 것으로 생각합니다.
- 저희 경험상, 성숙한 트레이싱 런타임의 복잡성에는 많은 비관습적이고 영리한 “트릭”이 필요한 것으로 보입니다. 예를 들어 현재의 트레이스 기록 메커니즘은 인터프리터 실행 기록을 효율적이고 효과적으로 수행하기 위해 그러한 트릭에 의존합니다. 추가적인 복잡성으로는 트레이스 그래프와 그 수명을 관리하는 일이 있습니다. JIT를 더 쉽게 유지 관리하고 가르칠 수 있도록 이러한 트릭을 줄이고자 합니다. 메서드 기반 프런트엔드와 같은 다른 프런트엔드도 트릭을 구현합니다. 그러나 다른 동적 언어 런타임에서 널리 사용되기 때문에 최근 몇 년 동안 더 많이 연구되었고, 따라서 더 잘 문서화된 것으로 보입니다.
- CPython에서 트레이싱의 상호작용은 가르치고 분석하기에 비전통적입니다. 트레이싱은 AI/ML 컴파일러에서 흔히 사용되지만, 전통적인 컴파일러 문헌에서는 사용되고 가르쳐지는 빈도가 낮습니다. 이로 인해 컴파일러는 알고 있지만 CPython은 모르는 새로운 기여자가 진입하기 더 어려워진다고 생각합니다. 또한 트레이스의 성능이 좋지 않을 때 CPython 인터프리터와의 상호작용을 분석하기 어려울 수 있습니다. 예측하기 어려운 성능에는 무수히 많은 원인이 있을 수 있으며, 이를 분석하려면 인터프리터에 대한 깊은 이해도 필요합니다. 예를 들어 현재의 트레이스 기록 런타임에는 트레이스를 계속할지 종료할지를 결정하는 복잡한 트레이싱 휴리스틱이 있습니다. 이러한 휴리스틱을 올바르게 구현하기까지 한 기여자와 이 PEP의 작성자 중 한 명이 수없이 시도해야 했습니다(그들의 잘못은 아니었습니다). 새로운 기여자에게 인터프리터를 깊이 분석하도록 요구하지 않고도 더 쉽게 가르치고 온보딩할 수 있게 하고자 합니다.
- 현재 JIT가 더 큰 실제 워크로드에 이점을 제공하는지에 대해서는 확신이 없습니다. 현재 JIT는 주로 pyperformance 벤치마크 모음 실행을 통해 측정하고 평가합니다. 실제 최종 사용자 코드에 미치는 영향을 평가하는 데 더 많은 시간을 사용하고자 합니다(Compatibility Review 참조).
- 배포 방식은 개선하고 체계화해야 합니다. 지금까지의 배포자 피드백에 따르면 대부분이 최신 LLVM 툴체인을 제공할 수 있으므로 LLVM 자체가 항상 주요 장애물인 것은 아닙니다. 더 어려운 문제는 Python 릴리스의 수명 동안 하나의 특정 LLVM 버전에 의존하는 것이며, 이로 인해 재배포자는 여러 LLVM 버전을 함께 제공하거나, 지원되지 않는 툴체인에 의존하거나, JIT를 비활성화하거나, 자체적인 스텐실 생성 워크플로를 유지해야 할 수 있습니다. 배포자가 실행할 수 있는 해결책을 체계화해야 합니다. PEP 774는 그러한 해결책 중 하나이지만, 각 Linux 배포판이 자체적인 해결책을 만들지 않도록 하려면 더 많은 연구가 필요합니다.
근거
위에서 above 언급한 바와 같이, JIT는 측정된 Tier 1 플랫폼의 pyperformance 벤치마크 스위트에서 몇 가지 제한 사항, 과제 및 개선 영역과 함께 대략 4~12% 더 빠른 기하 평균 성능을 달성했습니다( 부록 참조). 이 JIT의 다음 단계에서 향후 2.5년 이내(즉, Python 3.17까지)에 프리 스레드 빌드에서 인터프리터보다 최소 20% 향상된 성능을 달성한다는 야심 차지만 달성 가능한 초기 목표를 설정하고자 합니다.
그러나 성능만을 위한 성능 향상과 툴링 비호환성을 대가로 하는 성능 향상은 프로젝트에 의미가 있거나 매력적이지 않다는 점을 알고 있습니다. 따라서 아래 사양 섹션에 자세히 열거한 명확한 계획에 따라 의도적으로 이 다음 단계에 진입하고자 합니다.
사양
향후 2.5년 이내에 완전한 툴링 호환성을 유지하면서 지속 가능하고 유지 관리 가능한 20% 이상의 성능 향상을 달성하려면 논의할 가치가 있는 여러 영역이 있습니다.
- JIT 프런트엔드의 발전을 포함한 핵심 JIT 인프라
- 최적화
- 프리 스레딩에 대한 일급 지원
- 더 나은 배포 방식
- 호환성
- 도구 지원
핵심 JIT 인프라
전통적으로 컴파일러는 프론트엔드, 미들엔드, 백엔드로 나뉩니다. 이들은 여기서 다음과 같은 의미를 가집니다.
- 프론트엔드: 컴파일할 대상을 선택합니다. 이는 CPython 특수화 바이트코드의 메서드 또는 트레이스일 수 있습니다.
- 미들엔드: 명령어를 최적화합니다. 특수화 바이트코드를 uops로 변환하고 이를 최적화합니다.
- 백엔드: 기계어를 생성합니다.
현재 프론트엔드는 트레이스 기록을 사용합니다. 더 자세히 설명하면, 트레이스 기록은 프로그램의 바이트코드를 통한 실제 실행 흐름과 실행 중의 활성 값을 함께 기록합니다. 이를 위해 인터프리터를 계측했습니다. 이 프론트엔드는 3.13에서 최초로 도입된 것이 아니며, 당시에는 various reasons때문에 효과가 없는 것으로 보였습니다.
유지 관리 부담을 줄이고, JIT와 인터프리터를 분리하며, 향후 최적화를 지속 가능한 방식으로 가능하게 하기 위해 3.16까지 프론트엔드를 메서드 방식으로 변경할 것을 제안합니다. 메서드 프론트엔드가 목표를 충족하지 못하면 진행 도중 트레이스 기록 방식으로 되돌릴 수 있습니다.
프론트엔드를 변경하는 데에는 비용이 따릅니다. 이 작업에 소요되는 시간은 현재의 트레이싱 프론트엔드에 최적화를 직접 추가하는 데 사용되지 않는 시간이며, 전환 후에는 일부 트레이스 특화 성능 향상을 복구해야 할 수도 있습니다. 현재 프론트엔드는 성숙해짐에 따라 유지 관리, 교육, 디버깅 및 최적화 비용을 점점 증가시킬 가능성이 있어 이것이 메서드 프론트엔드의 초기 구현 비용을 초과할 것으로 보이기 때문에만 이 기회비용이 정당화된다고 생각합니다.
차이를 더 자세히 설명하면, 트레이스 기록은 코드를 통과하는 직선형 시퀀스를 기록하는 반면, 메서드는 일반적으로 컴파일할 하나 이상의 Python 함수를 선택합니다.
미들엔드와 백엔드는 큰 변경이 필요하지 않습니다. 현재 코드의 거의 전부를 메서드 프론트엔드에 재사용할 수 있습니다. Copy and Patch 컴파일을 사용하는 현재 백엔드는 이미 제어 흐름에서 분기와 점프를 지원합니다. uops에 대해 타입을 분석하는 미들엔드는 제어 흐름 병합 지점에서 타입 정보를 병합하는 기능만 지원하면 됩니다.
지난 몇 년 동안 얻은 교훈을 바탕으로 한 메서드 프론트엔드의 목표는 다음과 같습니다.
- 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의 대부분의 최적화를 로컬 재작성으로 구현할 수 있습니다. 이는 다시 다른 런타임의 중간 표현이 가진 특정 속성에서 영감을 받았습니다. 우리의 목표는 최적화할 수 있는 것을 희생하지 않으면서 JIT를 더욱 전통적이고 가르치기 쉽게 만드는 것입니다. 메서드 JIT에는 제어 흐름을 결합하는 작업이 필요하다는 점은 인정합니다. 그러나 가장 기본적인 최적화 이외의 것이 구현되면 트레이싱 JIT도 이미 제어 흐름 결합이라는 개념을 가르쳐야 하므로, 이것이 개념적으로 큰 부담은 아니라고 생각합니다.
이 프런트엔드를 구현하는 데 필요한 코드라는 관점에서 보면, 필요한 인프라 대부분은 이미 존재합니다. 필요한 주요 코드 수정 사항은 제어 흐름 그래프를 표현하는 데이터 구조와, 기존 최적화/분석 패스를 구동하는 워크리스트 알고리즘입니다. JIT와 인터프리터에서 현재 트레이싱 프런트엔드의 대부분을 제거할 수 있으며, 이를 통해 인터프리터의 핵심 디스패치 메커니즘과 주 인터프리터 루프가 단순해집니다. CPython의 현재 바이트코드 컴파일러가 이미 제어 흐름 그래프를 표현하고 워크리스트 알고리즘을 갖추고 있으므로, 이러한 개념이 CPython에 낯선 것은 아니라고 생각합니다.
마지막으로 메서드 JIT와 트레이싱 JIT는 모두 훌륭한 성능을 달성하는 과정에서 복잡성이 증가하며 다양한 상충 관계가 발생합니다. 트레이싱은 값 기록과 프로파일링이 더 단순한 반면, 메서드에는 더 발전된 다형성 인라인 캐시가 필요합니다. 더 높은 수준의 최적화를 얻으려면 트레이싱에는 트레이스 간 최적화가 필요한 반면, 메서드 JIT는 더 많은 코드를 볼 수 있으므로 더 간단합니다. 훌륭한 성능을 달성하려면 두 방식 모두 인터프리터와 긴밀하게 결합되어야 합니다. 두 기술 모두 상충 관계를 수반한다는 점을 이해하고 있으며, 어느 쪽이 궁극적으로 더 나은지에 대해서는 이번에도 판단을 내리지 않습니다. 우리의 주장은 CPython에 필요한 최적화, 교육·디버깅·분석의 용이성, 그리고 우리의 문제와 유사한 언어 런타임에서 해결책을 찾는다는 측면에서, 이 경우에는 궁극적으로 메서드 기반 JIT를 선택한다는 것뿐입니다. 솔직하게 말해 필요한 잠재적 추가 복잡성을 이해할 수 있도록 하자면, 제안된 메서드 JIT에는 추가 사이드 테이블에 추가 타입 프로파일링 데이터를 기록하는 것과 같은 특정 추가 기능(문헌에 설명된 기능)이 필요할 수도 있습니다. 그러나 현재의 바이트코드 DSL과 프로파일링 연산을 자동으로 생성하는 기능을 활용하면 복잡성을 크게 완화할 수 있으며, 이는 현재 트레이싱 JIT가 이미 작업을 수행하는 방식과 유사합니다. 또한 Optimizations에서 이러한 문제를 완화할 해결책을 제안합니다. 따라서 개념적·유지 관리상의 도약이 크지는 않다고 생각합니다.
최적화
메서드 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는 참조 카운팅의 상당 부분을 제거할 수 있습니다.
- JIT는 지연된 참조 카운팅(PEP 703 참조)과 Quiescent State-Based Reclamation(QSBR) 덕분에 특정 객체의 수명을 더 유연하게 처리할 수 있습니다. 이를 통해 즉시 회수로는 불가능한 더 많은 최적화 기회가 열립니다.
- JIT는 객체가 고유하게 참조되고 있음을 감지하면 특수화 적응형 인터프리터에서 잠금과 원자 연산을 제거할 수 있습니다. 원자 연산의 비용이 더 높은 아키텍처에서는 이것이 성능 저하의 원인이 됩니다.
여기서 올바른 구도는 JIT 또는 프리 스레딩이 아니라, JIT와 프리 스레딩이라고 믿습니다. 프리 스레딩의 의미론으로 인해 JIT가 처음에는 일부 성능 기회를 잃을 수 있다는 점을 이해하고 있습니다. 그러나 JIT와 프리 스레딩 모두 얻을 수 있는 것이 많습니다. JIT는 프리 스레딩의 단일 스레드 성능 손실을 모두 만회하고, 어쩌면 그 이상을 회복할 수 있습니다.
더 나은 JIT 배포 이야기
우리는 PEP 774의 해결책을 채택하거나 LLVM 버전 범위를 허용하여 JIT를 빌드하는 방안 중 하나를 선택할 수 있습니다. 이는 더 많은 논의와 실험이 필요한 사안입니다. 최소한 주요 Linux 배포판의 피드백을 수집하고, 논의하고, 종합적인 해결책에 반영해야 합니다. 현재 상황에 대한 이해로는 여러 LLVM 버전을 지원해야 합니다. 이로 인해 추가되는 복잡성은 크지 않을 것입니다. 그러나 테스트를 위해 더 많은 CI 리소스가 필요할 수 있습니다. JIT가 성공하려면 서드파티 배포판에 부당한 부담을 주어서는 안 됩니다.
호환성 검토
로드맵의 일환으로 주요 PyPI 패키지의 테스트 스위트를 실행하여 JIT가 해당 패키지를 중단시키는지 감지할 계획이며, 이는 초기 nogil 저장소의 접근 방식과 유사합니다. 당시 Sam Gross는 버그와 비호환성을 감지하기 위해 인기 있는 PyPI 패키지를 실행했습니다(추가적인 선행 사례는 labeille project를 참조하십시오).
패키지의 테스트 모음을 통과하지 못한다고 해서 목표가 자동으로 충족되지 않는 것은 아닙니다. 일부 테스트 모음은 보장되지 않는 CPython 내부 세부 사항에 의존할 수 있습니다. 따라서 이는 사례별 검토가 필요합니다. 핵심은 현재 존재하는 Python 코드와의 JIT 호환성을 평가하기 위해 최소한 집중적인 노력을 기울이고, “실제” 버그를 수정하기 위해 최선을 다했어야 한다는 것입니다.
도구 지원
JIT는 현재의 네이티브 언와인딩 지원을 퇴보시키지 않습니다. 다시 말해, JIT는 현재 모든 프레임 포인터 기반 언와인더와 eh_frame 기반 언와인더(예: GNU backtrace)를 지원합니다.
JIT는 Python 프레임을 필요로 하는 프로세스 외부 프로파일러와 디버거를 계속 지원합니다. 프레임 제거(인라이닝)가 유망한 최적화라는 점을 이해하고 있습니다. 그러나 JIT에서 프레임을 완전히 제거하면 서드파티 도구가 작동하지 않게 됩니다. Python 프레임 언와인더가 제거된 프레임을 복구하는 데 필요한 정보를 얻을 수 있도록, 제거된 프레임의 메타데이터를 저장하는 등의 대체 방법을 조율하고 제공하기 위해 주의를 기울일 것입니다. 또한 Python 스택을 검사하는 도구는 JIT C 심 프레임을 심볼화해야 할 수 있습니다(즉, 이를 Python 함수 호출과 연결해야 할 수 있습니다). 이 경우 이러한 도구를 지원하는 데 필요한 모든 정보는 실행기 객체를 통하거나 그 밖의 방식으로 CPython 런타임에 제공되며, JIT 프레임이 포함된 호출 스택을 이러한 도구가 이해할 수 있도록 디버그 오프셋에도 제공됩니다. 이를 위해 널리 사용되는 Python 프레임 언와인딩 애플리케이션의 유지 관리자와 협의할 수 있습니다. 일반적인 원칙은 다음과 같습니다. JIT가 꺼진 상태에서 무언가가 작동한다면, JIT가 켜진 상태에서도 작동하거나 사용할 수 있는 대안이 있도록 최선을 다해야 하며, 순수한 성능을 위해 진정으로 유용한 관측 가능성 및 디버깅 기능을 손상해서는 안 됩니다.
JIT는 프로세스 내부 도구를 계속 지원합니다. 즉, sys._getframe, pdb 또는 sys.monitoring을 손상시키지 않습니다.
다른 JIT 및 컴파일러 도구와의 관계
CPython의 JIT는 CinderX, Numba, PyTorch Compile 또는 기타 도메인별 컴파일러와 같은 서드파티 전문 JIT나 컴파일러 프로젝트를 대체하기 위한 것이 아닙니다. 이러한 프로젝트는 서로 다른 워크로드를 최적화하고, 서로 다른 가정을 사용하거나, 스택의 서로 다른 계층에서 작동하는 경우가 많습니다. JIT는 오늘날과 마찬가지로 인터프리터 자체의 책임에 해당하게 되는 모든 코드의 실행을 위한 “보루” 역할을 하도록 설계되었습니다.
플랫폼 지원
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 빌드는 다운로드 수가 극히 적으므로 해당 플랫폼을 목표에서 제외하고자 합니다. 또한 WASI와 같이 관례적으로 비-JIT 플랫폼인 다른 플랫폼이 결국 Tier 1로 승격되더라도, 그러한 플랫폼을 지원할 것으로 기대하지 않습니다.
Copy and Patch 백엔드 덕분에 JIT는 추가로 필요한 작업을 최소화하면서 관심 대상 플랫폼을 지원합니다. 핵심 아이디어는 IR에 해당하는 기계어를 직접 작성하지 않는 것입니다. 그렇게 하면 변경이 지나치게 많아지고 CPython의 빠른 바이트코드 변경 속도를 지속적으로 따라갈 수 없기 때문입니다.
유지 관리 모델
JIT는 프런트엔드, 옵티마이저, 코드 생성 백엔드라는 세 단계 전반에서 작업하는 CPython 핵심 개발자와 기여자 그룹이 유지 관리합니다. JIT의 핵심 목표 중 하나는 각 단계에 익숙한 활성 유지 관리자를 둘 이상 확보하여 JIT의 어떤 부분도 한 사람에게 의존하지 않도록 하는 것입니다. 기여자 기반은 우연이 아니라 의도적으로 성장해 왔습니다. 3.15 주기 동안 최적화 작업을 작고 개별적으로 실행 가능한 작업으로 세분화하여 기여의 장벽을 낮추었고, 트레이스 기록 변환 작업에 대략 12명을 참여시켰으며 동시에 increasing the number of recurring optimizer contributors했습니다. 이러한 작업 세분화는 기여자를 영입하고 계속 참여하도록 하는 지속적인 방법이며, 프로젝트가 시간이 지남에 따라 유지 관리자 풀을 유지하고 확대하려는 방식입니다.
현재 프로젝트는 어떤 단일 후원자에게도 의존하지 않습니다. 최초의 주요 기업 후원자가 전용 자금 지원을 축소한 후에도 커뮤니티 주도 활동으로 계속되었으며, 현재는 자원봉사 활동과 일부 지속적인 기업 기여를 결합하고 있습니다. 기업 기여는 주로 Arm, FastAPI Labs, OpenAI에서 제공됩니다. JIT를 유지하려면 공유되는 핵심 인프라에도 의존해야 합니다. 여기에는 JIT 빌드를 실행하는 지속적 통합 및 빌드 구성(현재 main에서 part of regular CI의 일부)과, publish nightly results하는 자체 호스팅 벤치마킹 머신 및 인프라(현재 Savannah가 유지 관리하며, 머신은 Savannah와 Arm이 기여)가 포함됩니다.
마지막으로, 어쩌면 가장 중요하게도 JIT를 작업하지 않는 기여자도 JIT에 계속 접근할 수 있어야 합니다. 이는 인터프리터를 JIT에서 이해하기 쉽도록 유지하거나 분리하고, 생성된 코드를 재생성하고 변경 사항을 기여하는 작업 흐름을 문서화하며, 내부 구현 문서를 최신 상태로 유지하겠다는 약속을 의미합니다. 3.15에 제공된 옵티마이저 작업의 단순화(Learnings 참조)는 이러한 유지 관리 투자가 실제로 이루어진 예입니다. JIT를 빌드하고 배포하는 재배포자에 대한 의무는 A Better JIT Distribution Story 섹션에 설명되어 있습니다.
하위 호환성
JIT는 언어의 변경이 아니라 최적화이므로, 핵심 호환성 보장은 JIT가 활성화된 빌드가 비-JIT 빌드와 구별할 수 없는 동작을 단지 더 빠르게 생성해야 한다는 것입니다. 결과, 예외 및 트레이스백, 그리고 지원되는 동일한 검사 가능한 상태가 그 대상입니다.
위에서 covered above 다룬 바와 같이, CPython에서 JIT를 지원되는 것으로 간주하기 위한 요건으로 주요 PyPI 패키지의 테스트 스위트에 대한 호환성 분석을 수행합니다.
보안 관련 사항
관련 PEP 744에 명시된 바와 같이, CPython의 JIT는 모든 JIT와 마찬가지로 런타임에 대량의 실행 가능한 데이터를 생성합니다. 이는 모든 JIT 컴파일러에 존재하는 공격 벡터입니다. 이 데이터의 내용을 조작할 수 있는 악의적인 행위자는 따라서 임의의 코드를 실행할 수 있습니다.
이러한 위험을 완화하기 위해 JIT는 모범 사례를 염두에 두고 작성되었습니다. 특히 해당 데이터는 쓰기 가능한 상태로 남아 있는 동안 JIT 컴파일러에 의해 프로그램의 다른 부분에 노출되지 않으며, 어느 시점에도 쓰기 가능하면서 동시에 실행 가능한 상태가 되지 않습니다.
템플릿 기반 JIT의 특성상 생성할 수 있는 코드의 종류도 크게 제한되므로, 성공적인 익스플로잇의 가능성이 더욱 낮아집니다. 추가 예방 조치로 템플릿 자체는 정적 읽기 전용 메모리에 저장됩니다.
그러나 특히 이 초기 단계에서 JIT에 가능한 취약점이 전혀 존재하지 않는다고 가정하는 것은 순진한 생각입니다. 저자들은 보안 전문가가 아니지만, 보안 문제가 발생하는 대로 이를 분류하고 수정하기 위해 Python 보안 대응 팀과 긴밀히 협력할 것입니다.
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가 지원되는 것으로 간주되면 새로운 변경 사항으로 인해 치명적으로 망가져서는 안 됩니다. 그러나 거의 모든 경우에 Python에 새로운 기능을 도입하는 일이 JIT로 인해 방해받지는 않을 것으로 예상합니다. 기여자가 자신의 기능을 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을 대상으로 한 작동하는 개념 증명 구현이 존재하며, 곧 공유될 예정입니다.
현재 개발 중이며 완성되지 않았음에도 불구하고(예를 들어 아직 제너레이터와 코루틴을 처리하지 못하고 다형성을 지원하지 않습니다. 이 두 기능은 기존 트레이싱 프런트엔드에서 부분적으로 지원됩니다), 전역 인터프리터 잠금이 활성화된 빌드에서는 JIT를 끈 경우보다 pyperformance 및 Pyston 매크로벤치마크 모음에서 여전히 4~5% 더 빠릅니다. 이는 불과 몇 달 만에 개발된 새로운 설계가 기존 트레이싱 설계와 경쟁할 수 있음을 보여 줍니다. 기존 트레이싱 설계는 동일한 x86-64 Linux 구성에서 3년에 걸쳐 발전해 온 결과 7~8% 더 빠릅니다.
테스트를 제외하면, 현재 메서드-JIT 구현의 main대비 규모는 대략 다음과 같습니다:
| 파일 유형 | 변경된 파일 | 추가된 줄 | 삭제된 줄 |
|---|---|---|---|
| 생성된 파일 | 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가 소비하는 uops 또한 인터프리터와 공동으로 설계되며 인터프리터에서 다시 생성됩니다. 트리 외부의 JIT는 브랜치 경계를 넘어 이러한 정의를 추적해야 하므로, 정확성이 가장 중요한 영역에서 바로 유지 관리 비용과 변경 사항 불일치 위험이 증가합니다. 3.15 주기 동안 기여자 기반이 확대된 것( Maintenance Model 참조) 자체가 트리 내부 개발이 참여 장벽을 높이는 것이 아니라 낮춘다는 증거입니다. CPython의 main 브랜치에 JIT를 유지하면 배포 패키지 관리자의 요구 사항을 더 잘 파악할 수 있으며, 최종 사용자가 JIT를 시험하고 관찰한 동작과 발견한 문제를 알려 주기도 더 쉬워집니다.
플러그형 JIT 인프라
또 다른 반복적으로 제기되는 아이디어는 CPython이 하나의 JIT를 트리 내부에서 유지 관리하는 대신, 임의의 서드파티 JIT 컴파일러를 연결할 수 있는 안정적인 범용 인터페이스를 공개하자는 것입니다. 이 PEP는 CPython의 main 외부에서 JIT를 유지 관리하는 경우와 같은 이유로 이 아이디어를 거부합니다. 플러그형 JIT를 도입하면 기여자의 노력이 분산될 위험이 있으며 유지 관리 오버헤드가 증가합니다. 예를 들어 3.13의 이전 JIT 버전에는 준공개 실험적 API가 있었습니다. 그러나 이 API는 내부 세부 사항을 “사용자”(실제로는 사용자가 없었습니다)에게 노출했으며 내부 JIT 개발을 더 어렵게 만들었습니다. 따라서 이를 제거했습니다. 또한 대부분의 언어 런타임 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.