메뉴 닫기

파이썬 성능 최적화: C-API, GIL 관리, Vectorcall 완벽 가이드

파이썬 성능 최적화: C-API, GIL 관리, Vectorcall 완벽 가이드

🚀 파이썬의 한계를 넘어서: C 확장으로 고성능 코드 구현하기

파이썬(Python)은 뛰어난 생산성과 방대한 라이브러리 생태계로 전 세계 개발자들에게 사랑받는 언어입니다.

하지만 CPU 집약적인 작업에서는 성능상의 병목 현상을 피하기 어려운 것도 사실입니다.

특히 데이터 처리, 과학 계산, 머신러닝 분야에서 속도 문제는 곧 프로젝트의 성공 여부를 가르는 핵심 요소가 되기도 합니다.

이러한 딜레마를 해결하기 위해 파이썬은 C/C++ 같은 저수준 언어로 작성된 코드를 통합할 수 있는 강력한 메커니즘을 제공합니다.

오늘 글에서는 파이썬의 성능을 비약적으로 끌어올리는 ‘C 확장’의 핵심 기술인 C-API, GIL(Global Interpreter Lock) 관리, 그리고 최신 함수 호출 규약인 Vectorcall까지 깊이 있게 다뤄보겠습니다.

단순히 라이브러리를 사용하는 것을 넘어, 직접 고성능 모듈을 구축하고자 하는 모든 파이썬 개발자들에게 실질적인 가이드가 될 것입니다.

파이썬 애플리케이션의 속도를 극한까지 끌어올리고 싶은가요?

혹은 이미 복잡한 계산 루틴 때문에 성능 저하를 경험하고 있다면, 이 글이 제시하는 C 확장을 통한 최적화 방법론은 가장 확실하고 효율적인 해결책이 될 것입니다.

C 확장의 기본 개념부터 가장 진보된 성능 최적화 기법까지 단계별로 상세히 안내합니다.

지금 바로 시작하여 여러분의 파이썬 코드를 한 단계 업그레이드할 기회를 잡으세요.



🚀 파이썬 성능 최적화의 첫걸음: C 확장 모듈의 기본 원리

파이썬 C 확장(C Extension) 모듈은 파이썬 인터프리터인 CPython이 C로 작성된 외부 함수나 클래스를 직접 호출할 수 있도록 해주는 핵심 메커니즘입니다.

이것이 중요한 이유는 파이썬 코드 자체가 느리기 때문이 아니라, C로 구현된 CPython 인터프리터의 오버헤드가 크기 때문입니다.

파이썬에서 변수나 객체를 생성하고 함수를 호출하는 모든 과정은 내부적으로 복잡한 C 함수 호출과 메모리 관리를 수반합니다.

이러한 오버헤드는 단순하고 반복적인 계산 루틴에서 성능 저하의 주범이 됩니다.

C 확장은 CPU 집약적인 루프나 알고리즘을 C로 작성한 뒤, 파이썬 객체와의 상호작용을 최소화하는 방식으로 성능을 극대화합니다.

가장 대표적인 C 확장 모듈의 예시는 NumPy입니다.

NumPy가 빠른 이유는 배열 연산을 C로 구현하고, 파이썬 레벨의 루프(Loop)를 사용하지 않기 때문입니다.

C 확장 모듈을 만들기 위해서는 파이썬의 C-API(Application Programming Interface)를 사용해야 합니다.

C-API는 C 코드가 파이썬 객체를 생성, 접근, 조작하고 예외 처리를 수행할 수 있도록 약속된 함수들의 집합입니다.

C 확장 모듈은 일반적으로 C 소스 파일(.c)로 작성되며, 파이썬에서 import 할 수 있는 공유 라이브러리 파일(.so 또는 .dll) 형태로 컴파일됩니다.

성능 최적화는 파이썬과 C 코드 사이의 경계를 최소화하는 데서 시작된다는 점을 명심해야 합니다.

C 코드는 계산을 전담하고, 파이썬은 C 코드를 호출하고 결과를 받는 역할만 수행하도록 설계해야 진정한 성능 향상을 얻을 수 있습니다.

C 확장을 사용하면 파이썬의 GIL(Global Interpreter Lock)을 일시적으로 해제하여 멀티 코어 환경에서 진정한 병렬 처리가 가능해진다는 강력한 이점도 있습니다.

🔑 C 확장이 성능을 가속하는 원리 세 가지

  • 인터프리터 오버헤드 우회: 파이썬 런타임의 동적 타입 체크 및 딕셔너리 룩업 같은 비용이 많이 드는 작업을 건너뛸 수 있습니다.
  • 🖥️네이티브 코드 실행: CPU가 이해하는 기계어로 코드가 직접 실행되어 C 코드가 가진 속도의 이점을 그대로 얻습니다.
  • 🤝GIL 해제 및 병렬화: 계산 시간이 긴 작업의 경우 GIL을 해제하여 여러 스레드가 동시에 작업할 수 있도록 허용합니다.

⚠️ 주의: C 확장은 메모리 관리 오류나 세그멘테이션 폴트와 같은 심각한 버그를 유발할 수 있습니다. 파이썬 객체에 대한 참조 카운팅(Reference Counting)을 정확히 관리하는 것이 필수입니다.

⚙️ C-API 핵심: PyObject*를 이해하고 활용하는 방법

파이썬 C 확장의 근간은 C-API이며, 이 API를 관통하는 가장 중요한 개념은 바로 PyObject*입니다.

PyObject*는 C 코드에서 파이썬 객체(정수, 문자열, 리스트, 함수 등)를 참조하는 범용적인 포인터 타입입니다.

파이썬의 모든 것은 객체라는 명제를 C 레벨에서 구현한 형태이며, C 확장 함수는 파이썬에서 전달받은 인수를 PyObject* 형태로 받고 결과를 다시 PyObject* 형태로 반환해야 합니다.

PyObject 구조체에는 객체의 타입을 식별하는 필드참조 카운트(Reference Count)가 포함되어 있습니다.

이 참조 카운트는 파이썬의 자동 메모리 관리(Garbage Collection)에서 매우 중요한 역할을 합니다.

C 확장 개발자는 C-API 함수들을 사용하여 이 참조 카운트를 수동으로 정확하게 관리해야 합니다.

💾 PyObject*와 참조 카운트 관리의 중요성

C-API를 사용할 때 가장 흔하고 치명적인 실수는 참조 카운트 관리 오류입니다.

참조 카운트를 증가시켜야 할 때 누락하면 객체가 불필요하게 파괴되어 ‘댕글링 포인터(Dangling Pointer)’ 문제가 발생할 수 있습니다.

반대로, 참조 카운트를 감소시켜야 할 때 누락하면 메모리가 해제되지 않아 ‘메모리 누수(Memory Leak)’가 발생합니다.

새로운 PyObject*를 생성하거나(예: $PyLong\_FromLong()$) 함수가 반환하는 객체를 받을 때는 반드시 참조 카운트를 관리해야 합니다.

💡 TIP: C-API 함수는 크게 ‘Borrowed Reference’를 반환하는 함수와 ‘New Reference’를 반환하는 함수로 나뉩니다. New Reference를 받았다면 사용 후에는 반드시 Py_DECREF()를 호출하여 카운트를 감소시켜야 합니다.

🛠️ 주요 C-API 함수 활용 예시

함수 기능 참조 관리 규칙
$PyArg\_ParseTuple()$ 파이썬 인수를 C 타입 변수로 변환 일반적으로 Borrowed Reference
$PyLong\_FromLong()$ C long을 PyObject* (정수)로 생성 New Reference (반환 시 $Py\_DECREF$ 필요)
$Py\_INCREF()$ 객체의 참조 카운트 1 증가 수동 호출 (필요한 경우)
$Py\_DECREF()$ 객체의 참조 카운트 1 감소 (0이 되면 메모리 해제) 수동 호출 (New Reference 반환 시)

C-API를 통한 함수 작성 시에는 다음 코드를 참고하여 함수의 시그니처를 구성합니다.

CODE BLOCK
static PyObject* my_c_function(PyObject *self, PyObject *args) {
    // 인수를 PyArg_ParseTuple로 파싱
    // 핵심 C 로직 수행
    // 결과 PyObject* 생성  반환 (New Reference)
    // return PyLong_FromLong(result_value);
    return NULL; // 오류 발생  NULL 반환
}

C 확장은 성능을 극대화하는 강력한 도구이지만, C-API에 대한 정확한 이해와 철저한 참조 카운트 관리가 성공의 열쇠입니다.

이를 소홀히 하면 파이썬 인터프리터 전체를 크래시(Crash) 시킬 위험이 있습니다.



최신 호출 규약: METH_FASTCALL과 Vectorcall 지원

파이썬 C 확장의 성능은 함수 호출 메커니즘 자체에서도 큰 영향을 받습니다.

전통적인 방식인 $METH\_VARARGS$는 인수를 튜플(Tuple)로 패키징하여 전달하는데, 이 과정에서 튜플 생성 및 파싱 오버헤드가 발생합니다.

파이썬 3.8 버전 이후부터는 이러한 오버헤드를 크게 줄이는 새로운 호출 규약들이 등장했습니다.

🚀 METH_FASTCALL: 튜플 오버헤드 제거

$METH\_FASTCALL$은 C 확장 함수를 등록할 때 사용하는 플래그 중 하나로, 인수를 튜플로 패키징하지 않고 C 레벨 배열(C-level array) 형태로 직접 전달합니다.

함수 시그니처가 다음과 같이 변경됩니다.

CODE BLOCK
// METH_VARARGS (전통): (PyObject *self, PyObject *args)
// METH_FASTCALL (최적화): (PyObject *self, PyObject *const *args, Py_ssize_t nargs)

여기서 $args$는 인수 객체들의 배열(C-level array of $PyObject\*$)이고, $nargs$는 인수의 개수입니다.

인수 개수가 정해진 함수($METH\_KEYWORDS$)에서도 튜플 생성/해제 과정이 생략되면서 함수 호출 비용이 현저히 절감됩니다.

💻 Vectorcall 지원을 통한 대규모 함수 호출 최적화

Vectorcall은 파이썬 3.8에 도입된 새로운 내부 함수 호출 프로토콜입니다.

이는 단순한 함수 플래그를 넘어, CPython 내부에서 함수 객체가 호출되는 방식을 근본적으로 개선합니다.

$METH\_FASTCALL$이 특정 함수 시그니처에 대한 최적화라면, Vectorcall은 모든 호출 가능한 객체(Callable objects)에 대한 범용적인 최적화를 목표로 합니다.

특히 반복적인 호출이 많은 라이브러리(예: Numpy의 ufuncs)에서 큰 성능 향상을 기대할 수 있습니다.

C 확장 모듈에서 Vectorcall을 지원하려면, 해당 모듈의 타입 정의($PyTypeObject$) 내부에 $tp\_vectorcall\_offset$ 필드를 설정하고, 관련 함수를 구현해야 합니다.

이는 개발자가 직접 구현해야 하는 고급 최적화 단계이며, 함수 호출 자체의 오버헤드를 거의 제거하여 파이썬 코드를 실행하는 것과 거의 유사한 속도를 낼 수 있습니다.

💎 핵심 포인트:
함수 호출 횟수가 많은 CPU 집약적 작업일수록 $METH\_FASTCALL$이나 Vectorcall 같은 최신 호출 규약을 적용하는 것이 중요합니다. 전통적인 $METH\_VARARGS$ 방식은 대부분의 경우 최적화 대상이 됩니다.

🔍 호출 규약별 성능 영향 비교

호출 규약에 따른 성능 차이는 함수가 얼마나 자주, 그리고 얼마나 많은 인수를 사용하여 호출되는지에 따라 달라집니다.

규약 특징 성능 최적화 수준
$METH\_VARARGS$ 인수를 튜플로 패키징하여 전달 표준 (낮음)
$METH\_FASTCALL$ 인수를 C 배열로 직접 전달 우수 (튜플 오버헤드 제거)
Vectorcall CPython 내부의 호출 로직 자체를 최적화 최상 (내부 호출 오버헤드 최소화)

새로운 프로젝트를 시작하거나 기존 C 확장을 마이그레이션할 때, 이 최신 호출 규약들을 적용하는 것은 파이썬 성능 가속의 핵심 단계가 될 것입니다.

🔒 GIL 관리 필수 테크닉: Py_BEGIN_ALLOW_THREADS를 통한 병렬화

파이썬의 성능 최적화에서 가장 중요한 요소 중 하나는 GIL(Global Interpreter Lock) 관리입니다.

CPython은 한 번에 하나의 스레드만 파이썬 바이트코드를 실행하도록 강제하는 GIL을 사용하여 메모리 관리의 안전성을 보장합니다.

이 때문에 CPU 집약적인 파이썬 스레드들은 멀티 코어 환경에서도 병렬로 실행되지 못하고 순차적으로 대기하게 됩니다.

C 확장은 이 GIL의 제약을 우회하고 진정한 멀티 코어 병렬 처리를 가능하게 하는 유일한 방법입니다.

C 확장 코드 내부에서 $Py\_BEGIN\_ALLOW\_THREADS$ 매크로를 사용하면, C 코드가 실행되는 동안 GIL을 해제하여 다른 파이썬 스레드(또는 I/O 바운드 스레드)가 동시에 실행될 수 있게 됩니다.

🔑 GIL 해제 및 재획득 매크로 쌍

GIL을 해제하고 다시 획득하는 과정은 반드시 $Py\_BEGIN\_ALLOW\_THREADS$와 $Py\_END\_ALLOW\_THREADS$ 쌍을 이루어야 합니다.

이 매크로 쌍 사이에 위치하는 C 코드는 GIL로부터 자유롭기 때문에, 여러 스레드가 동시에 실행되어 CPU의 모든 코어를 활용할 수 있습니다.

특히 장시간 동안 복잡한 계산을 수행하는 루틴에 이 패턴을 적용하면 극적인 성능 향상을 볼 수 있습니다.

CODE BLOCK
PyObject* my_parallel_func(PyObject *self, PyObject *args) {
    // 1. 인수 파싱  PyObject*에서 C 데이터로 변환 (GIL 필요)

    PyThreadState *_save;
    
    // 2. GIL 해제 시작 (다른 스레드 실행 허용)
    Py_BEGIN_ALLOW_THREADS
    
    // 3. 순수 C 코드 기반의 CPU 집약적 계산 수행
    //     구간에서는 파이썬 객체를 건드리면  됩니다.
    long result = perform_heavy_computation(c_data);
    
    // 4. GIL 재획득 완료
    Py_END_ALLOW_THREADS
    
    // 5. C 결과를 PyObject* 변환  반환 (GIL 필요)
    return PyLong_FromLong(result);
}

💬 C 확장에서 GIL을 해제하는 핵심 규칙은, GIL이 해제된 상태에서는 어떤 C-API 함수도 호출해서는 안 된다는 것입니다. C-API는 파이썬 객체에 접근하고 참조 카운트를 변경하므로, 이는 인터프리터의 안전을 위협합니다.

🚨 GIL 재획득 및 상태 저장 ($PyThreadState$)

$Py\_BEGIN\_ALLOW\_THREADS$ 매크로가 작동하려면 현재 스레드의 상태를 저장해야 합니다.

매크로 내부적으로 $PyThreadState$ 구조체를 이용하며, 위 예시 코드처럼 $PyThreadState \*\_save;$를 선언할 필요가 있지만, 최근의 CPython 헤더에서는 매크로 자체가 이를 처리하기도 합니다.

가장 안전한 방법은 C-API 문서에서 권장하는 매크로 사용법을 정확히 따르는 것입니다.

만약 C 코드 중간에 불가피하게 파이썬 객체에 접근해야 한다면, $PyGILState\_Ensure()$와 $PyGILState\_Release()$를 사용하여 GIL을 일시적으로 다시 확보할 수 있습니다.

이러한 세밀한 GIL 관리는 C 확장을 통해 멀티 스레딩 성능을 완벽하게 제어할 수 있게 해주는 필수적인 고급 기술입니다.

병렬화가 필요한 대규모 계산 작업에서 $Py\_BEGIN\_ALLOW\_THREADS$는 파이썬의 한계를 뛰어넘는 성능을 제공합니다.



🔬 C 확장 모듈 작성의 실전 팁과 주의사항

파이썬의 성능을 최적화하기 위해 C 확장을 작성하는 것은 강력하지만, 여러 가지 실수를 범하기 쉬운 작업입니다.

성공적인 고성능 C 확장 모듈을 만들기 위해 반드시 기억해야 할 실전 팁과 주의사항을 정리했습니다.

🛡️ 디버깅 및 오류 처리: 예외 전달의 중요성

C 코드는 파이썬과 달리 오류가 발생하면 세그멘테이션 폴트를 일으키며 인터프리터를 종료시킬 수 있습니다.

C-API 함수는 오류 발생 시 대부분 NULL 포인터를 반환하고 내부적으로 파이썬 예외(Exception) 플래그를 설정합니다.

따라서 C 확장 함수는 반환된 포인터가 NULL인지 항상 확인하고, NULL이면 즉시 NULL을 반환하여 파이썬 레벨로 예외를 전달해야 합니다.

파이썬 예외를 명시적으로 설정하려면 $PyErr\_SetString()$ 함수를 사용하며, C 코드가 아닌 파이썬에서 사용자 친화적인 오류 메시지를 받을 수 있도록 처리해야 합니다.

CODE BLOCK
if (!PyArg_ParseTuple(args, "l", &value)) {
    // 인수가 잘못되면 PyArg_ParseTuple이 이미 예외를 설정함
    return NULL; 
}

🔗 Py_XDECREF와 Py_DECREF의 현명한 사용

참조 카운팅은 C 확장의 성공을 좌우하는 가장 중요한 요소입니다.

$Py\_DECREF()$는 대상 포인터가 NULL이 아닌 것을 전제로 하지만, $Py\_XDECREF()$는 포인터가 NULL일 경우에도 안전하게 작동합니다.

특히 오류 경로에서 객체 포인터가 초기화되지 않았거나 해제되었을 가능성이 있을 때 $Py\_XDECREF()$를 사용하는 것이 가장 안전한 방어 코딩 습관입니다.

⚠️ 주의: 파이썬 객체와 C 데이터 간의 변환 비용을 최소화해야 합니다. 큰 데이터셋(예: NumPy 배열)의 경우, 데이터를 C 메모리로 복사하지 않고 포인터만 전달받아 처리하는 것이 성능상 유리합니다. 예를 들어, 버퍼 프로토콜을 활용하면 불필요한 복사를 막을 수 있습니다.

🛠️ 빌드 시스템의 선택: setuptools와 CMake

C 확장 모듈을 실제로 빌드하고 배포하려면 안정적인 빌드 시스템이 필요합니다.

가장 기본은 Python 표준 라이브러리의 distutils를 사용한 $setup.py$ 파일이지만, 현재는 setuptools를 사용하는 것이 일반적입니다.

복잡한 C/C++ 프로젝트나 외부 라이브러리(예: OpenMP, BLAS)를 통합해야 한다면, setuptools와 연동되는 CMake 같은 전문 빌드 시스템을 사용하는 것이 훨씬 유연하고 관리하기 쉽습니다.

C 확장은 운영체제별, 파이썬 버전별, 심지어 컴파일러별로 호환성 문제가 발생할 수 있으므로, Cibuildwheel과 같은 도구를 사용하여 다양한 환경에 대응하는 휠(wheel) 파일을 생성하는 것이 배포의 표준입니다.

자주 묻는 질문 (FAQ)

파이썬 C 확장을 꼭 사용해야 하는 기준은 무엇인가요?
C 확장은 주로 CPU 집약적인 연산이 전체 실행 시간의 80% 이상을 차지하는 병목 구간이 있을 때 고려해야 합니다. I/O 바운드 작업(네트워크 통신, 디스크 접근 등)에는 C 확장의 이점이 적습니다. 프로파일러를 사용하여 느린 부분을 정확히 식별한 후 적용하는 것이 중요합니다.
Cython, Numba 등 다른 가속 도구와 C-API는 어떤 차이가 있나요?
Cython이나 Numba는 파이썬 코드를 C 코드로 변환해주는 ‘자동화된’ 도구입니다. 개발 속도가 빠르고 GIL 관리가 비교적 쉽습니다. 반면, C-API를 직접 사용하는 것은 C 코드를 처음부터 작성하는 ‘수동적인’ 방법입니다. 복잡도가 높지만, Vectorcall이나 커스텀 메모리 관리 등 가장 높은 수준의 성능 최적화와 유연한 기능을 제공합니다.
PyObject* 참조 카운트가 잘못되면 어떻게 되나요?
참조 카운트가 과도하게 증가하면 메모리 누수(Memory Leak)가 발생하여 프로그램의 메모리 사용량이 계속 증가합니다. 참조 카운트가 너무 빨리 감소하면 객체가 너무 일찍 해제되어 ‘댕글링 포인터’ 문제가 발생하고, 이는 세그멘테이션 폴트(Segmentation Fault)를 일으키며 파이썬 인터프리터를 즉시 강제 종료시킵니다.
METH_FASTCALL은 METH_VARARGS를 완전히 대체할 수 있나요?
$METH\_FASTCALL$은 인수가 위치 인수로만 전달될 때 가장 효율적이며, 성능 면에서 $METH\_VARARGS$를 대체하는 최신 규약으로 권장됩니다. 다만, 키워드 인수가 복잡하게 사용되는 경우에는 $METH\_KEYWORDS$ 또는 $METH\_FASTCALL | METH\_KEYWORDS$ 조합을 사용해야 합니다.
GIL 해제 후 C 코드에서 파이썬 함수를 호출해도 되나요?
GIL이 해제된 상태($Py\_BEGIN\_ALLOW\_THREADS$와 $Py\_END\_ALLOW\_THREADS$ 사이)에서는 파이썬 객체에 접근하는 어떠한 C-API 함수도 호출해서는 안 됩니다. 파이썬 함수 호출 역시 GIL을 필요로 하므로, 반드시 $PyGILState\_Ensure()$로 GIL을 재획득한 후 호출해야 합니다.
C 확장을 사용하면 디버깅이 더 어려워지나요?
네, 일반적인 파이썬 디버거로는 C 코드 내부의 문제를 추적하기 어렵습니다. GDB 또는 Visual Studio 같은 네이티브 C/C++ 디버거를 사용하여 파이썬 인터프리터 프로세스에 연결해야 합니다. 특히 참조 카운트 오류는 디버깅이 매우 까다롭습니다.
Vectorcall을 지원하는 함수를 작성하려면 특별한 라이브러리가 필요한가요?
별도의 라이브러리는 필요 없습니다. Vectorcall은 CPython 인터프리터 자체에 내장된 프로토콜입니다. 다만, Vectorcall을 지원하려면 파이썬의 C-API를 사용하여 모듈의 $PyTypeObject$ 구조체를 정의할 때 $tp\_vectorcall\_offset$ 필드를 설정하고, Vectorcall 규약에 맞는 함수 포인터를 제공해야 합니다.
GIL을 해제하면 데이터 경쟁 문제로부터 자유로운가요?
아닙니다. GIL은 파이썬 인터프리터 상태를 보호할 뿐, C 코드 내부에서 여러 스레드가 동시에 공유하는 데이터에 대한 경쟁 상태는 개발자가 직접 처리해야 합니다. GIL이 해제된 C 코드 블록에서는 뮤텍스(Mutex)나 세마포어(Semaphore) 같은 C 표준 동기화 프리미티브를 사용하여 공유 자원에 대한 접근을 제어해야 합니다.

💡 성능 극한을 위한 파이썬 C 확장 마스터 전략

파이썬의 성능을 네이티브 코드 수준으로 끌어올리는 C 확장은 고성능 컴퓨팅 및 데이터 과학 분야에서 필수적인 기술입니다.

오늘 살펴본 핵심 전략은 C-API의 $PyObject\*$를 통한 정확한 메모리 및 참조 카운트 관리에서 시작됩니다.

특히 $Py\_INCREF()$와 $Py\_DECREF()$의 규칙을 철저히 지키는 것이 인터프리터의 안정성을 보장하는 기본입니다.

성능 가속을 위해서는 전통적인 $METH\_VARARGS$ 대신 $METH\_FASTCALL$이나 Vectorcall과 같은 최신 함수 호출 규약을 적용하여 함수 호출 오버헤드를 최소화해야 합니다.

또한, CPU 집약적인 장시간 작업에서는 $Py\_BEGIN\_ALLOW\_THREADS$ 매크로를 이용해 GIL을 일시 해제함으로써 멀티 코어 환경에서 진정한 병렬 처리를 구현할 수 있었습니다.

C 확장은 복잡하지만, 이러한 핵심 원칙과 디버깅 습관을 갖춘다면 여러분의 파이썬 애플리케이션은 비약적인 속도 향상을 경험하게 될 것입니다.

제공된 가이드를 바탕으로 안정적이고 최적화된 C 확장 모듈을 구축하여 파이썬 개발의 새로운 지평을 열어보시기 바랍니다.

C 확장을 통해 파이썬의 강력함과 C의 속도를 모두 누릴 수 있습니다.


🏷️ 관련 태그 : 파이썬성능, C확장모듈, C-API, PyObject, GIL관리, METH_FASTCALL, Vectorcall, 파이썬최적화, 병렬처리, CPython