Cinder는 CPython 3.10의 Meta 내부 성능 지향 프로덕션 버전입니다. 여기에는 바이트코드 인라인 캐싱, 코루틴의 적극적 평가, 시간별 메소드 JIT, JIT에서 더 잘 수행되는 유형 전문 바이트코드를 생성하기 위해 유형 주석을 사용하는 실험적 바이트코드 컴파일러 등 다양한 성능 최적화가 포함되어 있습니다.
Cinder는 Instagram이 시작된 곳을 지원하고 있으며 점점 더 많은 Meta Python 애플리케이션에서 사용되고 있습니다.
CPython에 대한 자세한 내용은 README.cpython.rst
참조하세요.
짧은 대답: 아니요.
우리는 이 작업 중 일부를 CPython으로 잠재적으로 업스트림하는 것에 대한 대화를 촉진하고 CPython 성능에 대해 작업하는 사람들 간의 노력 중복을 줄이기 위해 Cinder를 공개적으로 사용할 수 있도록 했습니다.
Cinder는 다른 사람이 사용할 수 있도록 다듬어지거나 문서화되지 않았습니다. 우리는 이것이 CPython의 대안이 되기를 바라지 않습니다. 이 코드를 사용할 수 있게 만드는 우리의 목표는 더 빠른 통합 CPython입니다. 따라서 우리가 프로덕션 환경에서 Cinder를 실행하는 동안 귀하가 그렇게 하기로 결정했다면 귀하는 스스로 결정해야 합니다. 우리는 외부 버그 보고서를 수정하거나 풀 요청을 검토하는 데 전념할 수 없습니다. 우리는 Cinder가 프로덕션 워크로드에 대해 충분히 안정적이고 빠른지 확인하지만 외부 워크로드나 사용 사례에 대한 안정성이나 정확성 또는 성능에 대해서는 보장하지 않습니다.
즉, 동적 언어 런타임에 대한 경험이 있고 Cinder를 더 빠르게 만들 수 있는 아이디어가 있다면; 또는 CPython 작업을 하고 있고 Cinder를 CPython 개선을 위한 영감으로 사용하고 싶다면(또는 Cinder의 CPython 업스트림 부분을 돕고 싶다면) 연락해 주세요. 우리는 채팅하고 싶습니다!
Cinder는 CPython처럼 빌드해야 합니다. -j를 configure
하고 make -j
. 그러나 Cinder의 대부분의 개발 및 사용은 매우 특정한 Meta 컨텍스트에서 발생하므로 다른 환경에서는 이를 많이 실행하지 않습니다. 따라서 Cinder를 구축하고 실행하는 가장 안정적인 방법은 GitHub CI 워크플로에서 Docker 기반 설정을 재사용하는 것입니다.
직접 구축하지 않고 작동하는 Cinder를 얻으려면 런타임 Docker 이미지가 가장 쉬울 것입니다(저장소 복제가 필요하지 않습니다!).
docker run -it --rm ghcr.io/facebookincubator/cinder-runtime:cinder-3.10
직접 구축하고 싶다면:
git clone https://github.com/facebookincubator/cinder
docker run -v "$PWD/cinder:/vol" -w /vol -it --rm ghcr.io/facebookincubator/cinder/python-build-env:latest bash
./configure && make
Cinder는 Linux x64에서만 구축되거나 테스트된다는 점에 유의하세요. 다른 것(macOS 포함)은 아마도 작동하지 않을 것입니다. 위의 Docker 이미지는 Fedora Linux 기반이며 Cinder 저장소의 Docker 사양 파일( .github/workflows/python-build-env/Dockerfile
에서 빌드되었습니다.
흥미로울 수 있는 몇 가지 새로운 테스트 대상이 있습니다. make testcinder
개발 환경에서 문제가 되는 몇 가지 테스트를 건너뛴다는 점을 제외하면 make test
와 거의 동일합니다. make testcinder_jit
JIT가 완전히 활성화된 상태에서 테스트 스위트를 실행하므로 모든 기능이 JIT됩니다. make testruntime
JIT에 대한 C++ gtest 단위 테스트 모음을 실행합니다. 그리고 make test_strict_module
엄격한 모듈에 대한 테스트 스위트를 실행합니다(아래 참조).
이러한 단계에서는 PGO/LTO 최적화가 활성화되지 않은 상태에서 Cinder Python 바이너리가 생성되므로 이러한 지침을 사용하여 Python 워크로드의 속도를 높일 것으로 기대하지 마십시오.
Cinder Explorer는 Cinder가 Python 코드를 소스에서 어셈블리로 컴파일하는 방법을 볼 수 있는 라이브 놀이터입니다. 언제든지 사용해 보실 수 있습니다! 기능 요청 및 버그 보고서를 자유롭게 제출해 주세요. Cinder Explorer는 나머지 부분과 마찬가지로 최선을 다해 "지원"된다는 점을 명심하세요.
Instagram은 다중 프로세스 웹 서버 아키텍처를 사용합니다. 상위 프로세스가 시작되고 초기화 작업(예: 코드 로드)을 수행하며 수십 개의 작업자 프로세스를 포크하여 클라이언트 요청을 처리합니다. 작업자 프로세스는 여러 가지 이유(예: 메모리 누수, 코드 배포)로 인해 주기적으로 다시 시작되며 수명이 상대적으로 짧습니다. 이 모델에서 OS는 개체의 참조 횟수가 수정될 때 상위 프로세스에 할당된 개체가 포함된 전체 페이지를 복사해야 합니다. 실제로 상위 프로세스에 할당된 개체는 작업자보다 오래 지속됩니다. 참조 계산과 관련된 모든 작업은 불필요합니다.
Instagram은 매우 큰 Python 코드베이스를 가지고 있으며 수명이 긴 개체를 참조 계산하여 쓰기 시 복사로 인한 오버헤드가 상당한 것으로 나타났습니다. 우리는 참조 카운팅에서 개체를 제외하는 방법을 제공하기 위해 "불멸 인스턴스"라는 솔루션을 개발했습니다. 자세한 내용은 포함/object.h를 참조하세요. 이 기능은 Py_IMMORTAL_INSTANCES를 정의하여 제어되며 Cinder에서는 기본적으로 활성화됩니다. 이는 프로덕션 측면에서 우리에게 큰 승리(~5%)였지만 직선 코드를 느리게 만듭니다. 참조 계산 작업은 자주 발생하며 이 기능이 활성화되면 개체가 참조 계산에 참여하는지 여부를 확인해야 합니다.
"Shadowcode" 또는 "shadow bytecode"는 특수 인터프리터를 구현한 것입니다. 일반 Python opcode 실행에서 최적화 가능한 특정 사례를 관찰하고 (핫 함수의 경우) 해당 opcode를 특수 버전으로 동적으로 대체합니다. 섀도우코드의 핵심은 Shadowcode/shadowcode.c
에 있지만 특수 바이트코드의 구현은 나머지 평가 루프와 함께 Python/ceval.c
에 있습니다. Shadowcode 관련 테스트는 Lib/test/test_shadowcode.py
에 있습니다.
이는 CPython 3.11에 내장될 전문 적응형 인터프리터(PEP-659)와 정신적으로 유사합니다.
Instagram Server는 비동기 작업이 많은 워크로드로, 각 웹 요청이 수십만 개의 비동기 작업을 트리거할 수 있으며, 그 중 대부분은 중단 없이 완료될 수 있습니다(예: 메모된 값 덕분에).
우리는 벡터콜 프로토콜을 확장하여 호출자가 이 호출을 즉시 기다리고 있음을 나타내는 새로운 플래그인 Ci_Py_AWAITED_CALL_MARKER
전달했습니다.
즉시 대기되는 비동기 함수 호출과 함께 사용하면 호출된 함수를 완료될 때까지 또는 첫 번째 일시 중지할 때까지 즉시(열심히) 평가할 수 있습니다. 일시 중단 없이 함수가 완료되면 추가 힙 할당 없이 즉시 값을 반환할 수 있습니다.
비동기 수집과 함께 사용하면 전달된 대기 가능 항목 세트를 즉시(열심히) 평가할 수 있으므로 동기식으로 완료될 수 있는 코루틴, 완료된 미래, 메모된 값 등에 대한 여러 작업의 생성 및 예약 비용을 잠재적으로 피할 수 있습니다.
이러한 최적화를 통해 CPU 효율성이 크게(~5%) 향상되었습니다.
이는 대부분 Python/ceval.c
에서 새로운 벡터콜 플래그 Ci_Py_AWAITED_CALL_MARKER
통해 구현되어 호출자가 이 호출을 즉시 기다리고 있음을 나타냅니다. IS_AWAITED()
매크로와 이 벡터콜 플래그의 사용을 찾아보세요.
Cinder JIT는 C++로 구현된 한 번에 메서드를 구현하는 사용자 정의 JIT입니다. -X jit
플래그 또는 PYTHONJIT=1
환경 변수를 통해 활성화됩니다. 거의 모든 Python opcode를 지원하며 많은 Python 성능 벤치마크에서 1.5~4배의 속도 향상을 달성할 수 있습니다.
기본적으로 활성화되면 호출되는 모든 함수를 JIT 컴파일하므로 거의 호출되지 않는 함수의 JIT 컴파일 오버헤드로 인해 프로그램이 빨라지는 것이 아니라 느려질 수 있습니다. -X jit-list-file=/path/to/jitlist.txt
또는 PYTHONJITLISTFILE=/path/to/jitlist.txt
옵션은 정규화된 함수 이름( path.to.module:funcname
형식)이 포함된 텍스트 파일을 가리킬 수 있습니다. path.to.module:funcname
또는 path.to.module:ClassName.method_name
), 한 줄에 하나씩 JIT 컴파일해야 합니다. 우리는 프로덕션 프로파일링 데이터에서 파생된 핫 기능 세트만 컴파일하기 위해 이 옵션을 사용합니다. (JIT에 대한 보다 일반적인 접근 방식은 자주 호출되는 것으로 관찰되는 함수를 동적으로 컴파일하는 것입니다. 우리의 프로덕션 아키텍처는 프리포크 웹 서버이고 메모리 공유 이유는 작업자가 분기되기 전 초기 프로세스에서 모든 JIT 컴파일을 미리 수행하려는 이유입니다. 즉, JIT 컴파일할 기능을 결정하기 전에는 진행 중인 작업 부하를 관찰할 수 없습니다.)
JIT는 Jit/
디렉터리에 있고 해당 C++ 테스트는 RuntimeTests/
에 있습니다( make testruntime
으로 실행). Lib/test/test_cinderjit.py
에는 이에 대한 Python 테스트도 있습니다. make testcinder_jit
통해 JIT 하에서 전체 CPython 테스트 스위트를 실행하기 때문에 이것들이 철저하지는 않습니다. CPython 테스트 스위트에서는 찾을 수 없는 JIT 엣지 케이스를 다룹니다.
JIT의 동작에 영향을 미치는 다른 -X
옵션과 환경 변수에 대해서는 Jit/pyjit.cpp
참조하세요. 해당 파일에는 일부 JIT 유틸리티를 Python 코드에 노출하는 cinderjit
모듈도 정의되어 있습니다(예: 특정 함수를 강제로 컴파일하고, 함수가 컴파일되었는지 확인하고, JIT를 비활성화하는 등). cinderjit.disable()
향후 컴파일만 비활성화한다는 점에 유의하세요. 알려진 모든 기능을 즉시 컴파일하고 기존 JIT 컴파일 기능을 유지합니다.
JIT는 먼저 Python 바이트코드를 HIR(고수준 중간 표현)로 낮춥니다. 이는 Jit/hir/
에서 구현됩니다. HIR은 스택 시스템이 아닌 레지스터 시스템이지만 약간 낮은 수준이고 형식이 지정되어 있지만 Python 바이트코드에 합리적으로 밀접하게 매핑되며 Python 바이트코드에 의해 가려지지만 성능에 중요한 일부 세부 정보(특히 참조 카운팅)는 다음과 같습니다. HIR에 명시적으로 노출됩니다. HIR은 SSA 형식으로 변환되고 일부 최적화 단계가 수행된 다음 HIR opcode의 참조 카운트 및 메모리 효과에 대한 메타데이터에 따라 참조 계산 작업이 자동으로 삽입됩니다.
그런 다음 HIR은 Jit/lir/
에서 구현되는 어셈블리에 대한 추상화인 저수준 중간 표현(LIR)으로 낮아집니다. LIR에서는 레지스터 할당을 수행하고 몇 가지 추가 최적화 과정을 거친 다음 마지막으로 우수한 asmjit 라이브러리를 사용하여 LIR을 어셈블리( Jit/codegen/
)로 낮춥니다.
JIT는 초기 단계에 있다. 이미 인터프리터 루프 오버헤드를 제거하고 많은 기능에 대해 상당한 성능 향상을 제공할 수 있지만 우리는 가능한 최적화의 표면을 긁기 시작했을 뿐입니다. 많은 일반적인 컴파일러 최적화는 아직 구현되지 않았습니다. 최적화의 우선순위는 주로 Instagram 제작 작업량의 특성에 따라 결정됩니다.
엄격한 모듈은 몇 가지 사항을 하나로 통합한 것입니다.
1. 모듈의 최상위 코드 실행 시 해당 모듈 외부에 부작용이 표시되지 않는지 검증할 수 있는 정적 분석기.
2. Python의 기본 모듈 유형 대신 사용할 수 있는 불변 StrictModule
유형입니다.
3. (모듈 상단의 import __strict__
통해) 엄격 모드로 선택된 모듈을 인식하고, 이를 분석하여 가져오기 부작용이 없는지 검증하고, sys.modules
에 StrictModule
객체로 채울 수 있는 Python 모듈 로더.
정적 Python은 유형 주석을 사용하여 유형 전문화되고 유형 확인된 Python 바이트코드를 생성하는 바이트코드 컴파일러입니다. Cinder JIT와 함께 사용하면 순수 Python 개발자 경험(일반 Python 구문, 추가 컴파일 단계 없음)을 제공하면서 많은 경우에 MyPyC 또는 Cython과 유사한 성능을 제공할 수 있습니다. Static Python과 Cinder JIT는 Richards 벤치마크의 형식화된 버전에서 순정 CPython 성능의 18배를 달성합니다. Instagram에서는 성능 저하 없이 기본 웹 서버 코드베이스의 모든 Cython 모듈을 교체하기 위해 프로덕션에서 Static Python을 성공적으로 사용했습니다.
정적 Python 컴파일러는 Python 3의 표준 라이브러리에서 제거된 이후 외부적으로 유지 관리되고 업데이트된 Python compiler
모듈 위에 구축되었습니다. 이 컴파일러는 Lib/compiler
의 Cinder에 통합되었습니다. 정적 Python 컴파일러는 Lib/compiler/static/
에 구현되고 해당 테스트는 Lib/test/test_compiler/test_static.py
에 있습니다.
정적 Python 모듈에 정의된 클래스에는 자동으로 유형이 지정된 슬롯이 제공되며( __init__
의 유형이 지정된 클래스 속성 및 주석이 달린 할당 검사를 기반으로 함) 이러한 유형의 인스턴스에 대한 속성 로드 및 저장은 새로운 STORE_FIELD
및 LOAD_FIELD
opcode를 사용합니다. 이는 JIT에서 직접적으로 사용됩니다. LOAD_ATTR
또는 STORE_ATTR
의 간접 참조 없이 객체의 고정된 메모리 오프셋에서/로 로드/저장합니다. 또한 클래스는 아래에 언급된 INVOKE_*
opcode에서 사용할 수 있도록 해당 메서드의 vtable을 얻습니다. 이러한 기능에 대한 런타임 지원은 StaticPython/classloader.h
및 StaticPython/classloader.c
에 있습니다.
정적 Python 함수는 제공된 인수의 유형이 유형 주석과 일치하는지 확인하고 그렇지 않으면 TypeError
발생시키는 숨겨진 프롤로그로 시작합니다. 정적 Python 함수에서 다른 정적 Python 함수로의 호출은 이 opcode를 건너뜁니다(유형이 이미 컴파일러에 의해 검증되었으므로). 정적-정적 호출은 일반적인 Python 함수 호출의 오버헤드를 상당 부분 피할 수도 있습니다. 호출된 함수나 메서드에 대한 메타데이터를 전달하는 INVOKE_FUNCTION
또는 INVOKE_METHOD
opcode를 내보냅니다. 여기에 선택적으로 불변 모듈( StrictModule
통해)과 유형( cinder.freeze_type()
을 통해 현재 가져오기 로더의 엄격 및 정적 모듈에 있는 모든 유형에 적용하지만 앞으로는 정적 Python의 고유한 부분이 될 수 있음)을 추가하고 컴파일합니다. 호출 수신자 서명에 대한 시간 지식을 사용하면 (JIT에서) 많은 Python 함수 호출을 x64 호출 규칙을 사용하여 고정 메모리 주소에 대한 직접 호출로 전환할 수 있으며 C 함수 호출보다 오버헤드가 거의 없습니다.
정적 Python은 여전히 점진적으로 유형이 지정되며 부분적으로만 주석이 추가되거나 일반적인 Python 동적 동작으로 폴백하여 알 수 없는 유형을 사용하는 코드를 지원합니다. 일부 경우(예: 정적으로 알 수 없는 유형의 값이 반환 주석이 있는 함수에서 반환되는 경우) 런타임 유형이 예상 유형과 일치하지 않으면 TypeError
발생시키는 런타임 CAST
opcode가 삽입됩니다.
정적 Python은 기계 정수, 부울, 복식 및 벡터/배열에 대한 새로운 유형도 지원합니다. JIT에서 이는 unboxed 값으로 처리되며, 예를 들어 기본 정수 산술은 모든 Python 오버헤드를 방지합니다. 내장 유형(예: 목록이나 사전 첨자 또는 len()
)에 대한 일부 작업도 최적화되었습니다.
Cinder는 정적 모듈을 자동으로 감지하고 교차 모듈 컴파일을 통해 정적 모듈로 로드할 수 있는 엄격한/정적 모듈 로더를 통해 정적 모듈의 점진적인 채택을 지원합니다. 로더는 파일 상단에서 import __static__
및 import __strict__
주석을 찾아 적절하게 모듈을 컴파일합니다. 로더를 활성화하려면 다음 세 가지 옵션 중 하나가 있습니다.
1. from cinderx.compiler.strict.loader import install; install()
.
PYTHONINSTALLSTRICTLOADER=1
설정합니다../python -X install-strict-loader application.py
실행합니다. 또는 ./python -m compiler --static some_module.py
사용하여 모든 코드를 정적으로 컴파일할 수 있습니다. 그러면 모듈이 정적 Python으로 컴파일되고 실행됩니다.
자세한 문서는 CinderDoc/static_python.rst
참조하세요.