Theory
Python
Python Interpreter

Python 인터프리터란?

Python은 하나의 언어 명세이지만, 실제로는 여러 가지 구현체(Implementation)가 존재한다.
각각의 구현체는 서로 다른 철학과 최적화 전략을 가지고 있으며, 사용 목적에 따라 선택할 수 있다.

주요 Python 구현체

CPython

  • Python Software Foundation에서 개발한 공식 구현체
  • C 언어로 작성되었으며, Python의 표준 구현체
  • Reference Counting + Mark-and-Sweep 방식의 메모리 관리
  • GIL(Global Interpreter Lock) 사용

PyPy

  • RPython으로 작성된 JIT 컴파일 지원 구현체
  • incminimark GC 사용으로 성능 최적화
  • 순수 Python 코드에서 CPython보다 빠른 성능
  • C 확장 모듈 호환성 제한

기타 구현체

  • Jython : Java 플랫폼에서 실행되는 Python
  • IronPython : .NET Framework에서 실행되는 Python
  • MicroPython : 마이크로컨트롤러용 경량 Python

CPython의 특징

Reference Counting

CPython은 즉시 메모리 해제를 위해 Reference Counting 방식을 사용한다.

# 객체가 생성되면 참조 카운트가 1
obj = MyObject()
 
# 다른 변수가 같은 객체를 참조하면 카운트 증가
another_ref = obj
 
# 참조가 사라지면 카운트 감소, 0이 되면 즉시 해제
del obj
del another_ref  # 이 시점에서 메모리 해제

장점:

  • 즉시 메모리 해제로 메모리 효율성 높음
  • 예측 가능한 메모리 관리
  • Deterministic Destruction 지원

단점:

  • 순환 참조 문제 (별도의 Mark-and-Sweep GC로 해결)
  • Thread Safety 문제 (GIL로 해결)

GIL (Global Interpreter Lock)

GIL은 한 번에 하나의 스레드만 Python 바이트코드를 실행할 수 있도록 하는 뮤텍스이다.

ℹ️

GIL의 존재 이유는 CPython의 Reference Counting이 Thread-Safe하지 않기 때문이다.

GIL의 영향:

  • CPU 집약적 작업에서 멀티스레딩 성능 제한
  • I/O 집약적 작업에서는 영향 적음 (I/O 대기 시 GIL 해제)
  • C 확장 모듈과의 호환성 보장
import threading
import time
 
def cpu_intensive_task():
    # CPU 집약적 작업 - GIL 때문에 병렬 실행되지 않음
    for i in range(10000000):
        pass
 
def io_intensive_task():
    # I/O 집약적 작업 - GIL이 해제되어 병렬 실행 가능
    time.sleep(1)

PyPy의 특징

JIT 컴파일

PyPy는 실행 시점에 코드를 최적화하는 JIT(Just-In-Time) 컴파일러를 내장한다.

실행 과정:

  1. 인터프리터 모드로 시작
  2. Hot Spot 감지 (자주 실행되는 코드 부분)
  3. 기계어로 컴파일하여 최적화
  4. 최적화된 코드 실행
# PyPy에서 성능이 크게 향상되는 예시
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)
 
# 이런 순수 Python 코드는 PyPy에서 매우 빠르게 실행됨
result = fibonacci(35)

incminimark GC

PyPy는 점진적 세대별 가비지 컬렉션을 사용한다.

특징:

  • 세대별 수집: 젊은 객체와 오래된 객체를 구분하여 처리
  • 점진적 수집: GC로 인한 긴 일시정지 시간 최소화
  • Mark-and-Sweep 기반: Reference Counting 대신 사용
⚠️

PyPy는 C 확장 모듈 호환성이 제한적이므로, NumPy, Pandas 등을 많이 사용하는 경우 CPython이 더 적합할 수 있다.

Python 3.13의 변화

GIL 제거 실험

Python 3.13부터는 실험적으로 GIL 없는 빌드를 제공한다.

# GIL 없는 Python 빌드 컴파일
./configure --disable-gil
make

Starting with the 3.13 release, CPython has experimental support for a build of Python called free threading where the global interpreter lock (GIL) is disabled. - Python Documentation (opens in a new tab)

Biased Reference Counting

새로운 편향된 참조 카운팅 방식을 도입한다.

핵심 아이디어:

  • 객체를 주로 접근하는 스레드에 "편향"
  • 소유 스레드는 non-atomic 연산 사용 가능
  • 다른 스레드는 atomic 연산 필요

The solution is to use biased reference counting, which also guarantees thread safety. The idea is to bias each object towards an owner thread, which is the thread accessing that object most of the time. - PyCharm Blog (opens in a new tab)

구현체 선택 기준

CPython을 선택해야 하는 경우

  • 다양한 C 확장 모듈 사용이 필요한 경우 (NumPy, Pandas, TensorFlow 등)
  • 메모리 사용량이 중요한 환경
  • 짧은 실행 시간의 스크립트
  • 안정성과 호환성이 최우선인 경우

PyPy를 선택해야 하는 경우

  • 순수 Python 코드 위주의 CPU 집약적 작업
  • 긴 실행 시간의 애플리케이션 (JIT 최적화 효과 극대화)
  • 성능이 최우선이고 생태계 제약을 감수할 수 있는 경우
  • 새로운 기술에 대한 실험적 시도

성능 비교

CPU 집약적 작업

# 순수 Python 계산 - PyPy가 유리
def prime_sieve(limit):
    sieve = [True] * (limit + 1)
    sieve[0] = sieve[1] = False
    
    for i in range(2, int(limit**0.5) + 1):
        if sieve[i]:
            for j in range(i*i, limit + 1, i):
                sieve[j] = False
    
    return [i for i in range(2, limit + 1) if sieve[i]]
 
# PyPy: 약 5-10배 빠름
primes = prime_sieve(1000000)

메모리 집약적 작업

# 대량 데이터 처리 - CPython이 유리 (즉시 메모리 해제)
def process_large_data():
    data = []
    for i in range(1000000):
        data.append(complex_object(i))
    
    # CPython: 즉시 메모리 해제
    # PyPy: GC 실행까지 메모리 유지
    del data

마치며

Python 생태계의 다양한 구현체들은 각각 다른 철학과 접근법을 가지고 있다.
CPython의 Reference Counting + GIL 방식과 PyPy의 복잡한 GC + JIT 방식 모두 각자의 장단점이 있으며, 사용 목적에 맞는 최적의 선택을 하는 것이 중요하다.

🔮

Python 3.13의 free-threading 실험이 성공한다면, CPython도 새로운 전환점을 맞게 될 것이다.

참고 자료

공식 문서

기술 블로그 및 아티클

한국어 기술 블로그

메모리 관리 및 GC

Stack Overflow