본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 6장: CPython 성능 향상의 메커니즘
2026년 1월 26일·프로그래밍·

6장: CPython 성능 향상의 메커니즘

Python 3.12의 성능 향상 원리를 분석합니다. 특수화 적응 인터프리터, 컴프리헨션 인라인화, immortal objects, asyncio 최적화 등 CPython 내부를 다룹니다.

16분441자10개 섹션
pythontypescriptperformancedevtoolsconcurrency
공유
python-trends6 / 13
12345678910111213
이전5장: PyREPL - 새로운 대화형 인터프리터다음7장: Free-threaded Python - GIL 제거의 시작

Faster CPython 프로젝트의 흐름

CPython의 성능 개선은 Mark Shannon의 제안서(Shannon Plan)로 시작된 장기 프로젝트입니다. Python 3.11에서 평균 25%의 속도 향상을 달성한 이후, 3.12에서도 이 흐름이 계속되고 있습니다.

CPython 버전별 성능 향상
text
Python 3.10  - 기준선
Python 3.11  - 약 25% 향상 (Faster CPython Phase 1)
Python 3.12  - 약 5% 추가 향상
Python 3.13  - free-threaded 모드에서 1~8% 오버헤드 (실험적)

3.12의 5% 향상은 수치로는 작아 보이지만, 3.11의 대폭 개선 이후에 달성한 것이라는 점에서 의미가 있습니다. 더 중요한 것은 이 향상이 특정 코드 패턴에 집중되어 있어, 해당 패턴을 많이 사용하는 코드에서는 체감이 클 수 있다는 점입니다.

특수화 적응 인터프리터 (Specializing Adaptive Interpreter)

Python 3.11에서 도입된 특수화 적응 인터프리터는 3.12에서 더욱 발전했습니다. 이 메커니즘의 핵심 아이디어는 바이트코드를 실행 중에 관찰된 타입에 맞게 특수화하는 것입니다.

동작 원리

CPython은 바이트코드를 실행하면서, 특정 연산이 반복적으로 같은 타입의 피연산자를 받는 것을 감지합니다. 이때 범용 바이트코드를 해당 타입에 최적화된 특수 바이트코드로 교체합니다.

특수화 과정
text
일반 바이트코드: BINARY_ADD
  - 실행할 때마다 타입 체크 필요
  - int + int, float + float, str + str 모두 같은 경로
 
관찰: x + y에서 x, y가 항상 int
  
특수화: BINARY_ADD_INT
  - int 전용 빠른 경로 사용
  - 타입 체크 생략
  - 실패 시 일반 경로로 폴백

3.12에서 추가된 특수화

Python 3.12는 다음 영역에서 새로운 특수화를 추가했습니다.

3.12의 새 특수화 바이트코드
text
FOR_ITER_LIST      - list 이터레이션 특수화
FOR_ITER_TUPLE     - tuple 이터레이션 특수화
FOR_ITER_RANGE     - range 이터레이션 특수화
FOR_ITER_GEN       - 제너레이터 이터레이션 특수화
SEND_GEN           - 제너레이터 send 특수화
TO_BOOL            - 불리언 변환 특수화
CONTAINS_OP_SET    - set의 in 연산 특수화
CONTAINS_OP_DICT   - dict의 in 연산 특수화

이 특수화 덕분에 반복문(for loop)과 멤버십 테스트(in 연산)의 성능이 향상되었습니다.

컴프리헨션 인라인화

Python 3.12의 가장 주목할 만한 최적화 중 하나는 컴프리헨션(comprehension)의 인라인화입니다.

기존 동작 방식

Python 3.11 이하에서 리스트, 딕셔너리, 셋 컴프리헨션과 제너레이터 표현식은 암묵적으로 중첩된 함수로 구현되었습니다.

컴프리헨션의 기존 구현
python
# 이 코드는
result = [x * 2 for x in range(10)]
 
# 내부적으로 이렇게 동작했음
def _comprehension(iterable):
    result = []
    for x in iterable:
        result.append(x * 2)
    return result
result = _comprehension(range(10))

이 방식에는 두 가지 비용이 있었습니다. 함수 객체를 생성하고 호출하는 오버헤드, 그리고 별도의 스코프로 인한 변수 접근 비용입니다.

3.12의 인라인화

Python 3.12에서는 컴프리헨션이 인라인으로 실행됩니다. 별도의 함수 호출 없이 현재 프레임 내에서 직접 실행됩니다.

인라인화의 효과
text
3.11: 컴프리헨션 = 함수 생성 + 호출 + 결과 반환
3.12: 컴프리헨션 = 현재 프레임에서 직접 실행
 
성능 향상: 컴프리헨션 실행 속도 약 11% 향상
Warning

인라인화로 인해 한 가지 동작이 변경되었습니다. 3.11 이하에서 컴프리헨션의 반복 변수는 별도 스코프에 있어서 외부로 누출되지 않았지만, 3.12에서는 인라인 실행이므로 이론적으로 변수 누출이 가능합니다. 다만 CPython은 호환성을 위해 반복 변수가 외부로 누출되지 않도록 처리합니다.

Immortal Objects (PEP 683)

Python 3.12는 "불멸 객체(Immortal Objects)"라는 개념을 도입했습니다. 이는 free-threaded Python을 위한 사전 작업이기도 합니다.

참조 카운팅의 문제

CPython은 참조 카운팅(reference counting)으로 메모리를 관리합니다. 모든 객체는 자신을 참조하는 변수의 수를 추적하며, 참조 수가 0이 되면 해제됩니다.

문제는 None, True, False, 작은 정수(-5~256) 같은 객체는 프로그램 전체에서 매우 빈번하게 참조되므로, 참조 카운터가 불필요하게 자주 변경된다는 것입니다. 이는 캐시 무효화를 유발하고, 멀티스레드 환경에서는 경쟁 조건(race condition)의 원인이 됩니다.

불멸 객체의 해결책

불멸 객체는 참조 카운터를 특수한 값으로 설정하여, 증가/감소 연산을 건너뛰도록 합니다.

불멸 객체의 동작
text
일반 객체:
  Py_INCREF(obj)  -> refcount++
  Py_DECREF(obj)  -> refcount--, if 0: dealloc
 
불멸 객체 (None, True, False, 작은 정수 등):
  Py_INCREF(obj)  -> no-op (아무 것도 하지 않음)
  Py_DECREF(obj)  -> no-op
  
결과: 캐시 친화적, 멀티스레드 안전

이 변화는 사용자 코드에 직접적인 영향을 주지 않지만, CPython 내부적으로 성능 향상과 free-threaded 모드의 기반을 제공합니다.

asyncio 성능 향상

Python 3.12는 asyncio 패키지의 성능을 상당히 개선했습니다. 일부 벤치마크에서는 75%의 속도 향상이 보고되었습니다.

주요 최적화

asyncio 최적화 내용
text
1. Task 생성 최적화
   - Task 객체 생성 비용 감소
   - eager task factory 도입
 
2. 이벤트 루프 최적화
   - 셀렉터 호출 빈도 감소
   - 콜백 스케줄링 효율화
 
3. Future 최적화
   - Future.result() 빠른 경로 추가
   - 완료된 Future에 대한 await 비용 감소

eager task factory

Python 3.12에서 도입된 asyncio.eager_task_factory는 코루틴이 첫 번째 await 지점에 도달하기 전에 완료될 수 있는 경우, Task 객체 생성을 건너뛰는 최적화입니다.

eager task factory
python
import asyncio
 
async def cached_get(key: str) -> str:
    # 캐시 히트 시 await 없이 즉시 반환
    if key in cache:
        return cache[key]
    # 캐시 미스 시에만 비동기 I/O
    result = await fetch_from_db(key)
    cache[key] = result
    return result
 
async def main():
    # eager task factory 설정
    loop = asyncio.get_event_loop()
    loop.set_task_factory(asyncio.eager_task_factory)
 
    # 캐시 히트 시 Task 생성 오버헤드 없이 즉시 결과 반환
    result = await cached_get("user:123")
Info

eager task factory는 많은 코루틴이 동기적으로 완료되는 패턴(캐시 히트, 조건 분기에서의 조기 반환 등)에서 효과적입니다. 모든 코루틴이 실제 I/O를 수행하는 경우에는 효과가 제한적입니다.

PEP 669: 저비용 모니터링 API

Python 3.12는 새로운 디버깅/프로파일링 API인 sys.monitoring을 도입했습니다(PEP 669). 기존의 sys.settrace와 sys.setprofile을 대체하는 것이 목적입니다.

기존 API의 문제

sys.settrace는 모든 바이트코드 실행마다 콜백을 호출합니다. 디버거나 프로파일러가 활성화되면 전체 프로그램의 실행 속도가 크게 저하됩니다.

sys.monitoring의 접근 방식

sys.monitoring 사용법
python
import sys
 
# 모니터링 도구 ID 등록
MY_TOOL = sys.monitoring.DEBUGGER_ID
 
# 이벤트 콜백 등록
def on_line(code, line_number):
    print("Line " + str(line_number) + " in " + code.co_filename)
    return sys.monitoring.DISABLE  # 이 위치에서 더 이상 모니터링하지 않음
 
# 모니터링 활성화
sys.monitoring.use_tool_id(MY_TOOL, "my_debugger")
sys.monitoring.set_events(MY_TOOL, sys.monitoring.events.LINE)
sys.monitoring.register_callback(
    MY_TOOL,
    sys.monitoring.events.LINE,
    on_line
)

핵심 차이점은 이벤트 기반이라는 것입니다. 관심 있는 이벤트만 선택적으로 모니터링하고, 특정 위치에서 모니터링을 비활성화할 수 있습니다. 이로 인해 모니터링 오버헤드가 대폭 감소합니다.

성능 비교
text
sys.settrace:
  - 모든 줄 실행마다 콜백
  - 프로그램 전체 속도 저하 (3~10배)
 
sys.monitoring:
  - 선택적 이벤트 모니터링
  - 동적 활성화/비활성화
  - 모니터링 오버헤드: 거의 0 (이벤트 미발생 시)

Linux perf 프로파일러 지원

Python 3.12는 Linux perf 프로파일러와의 통합을 지원합니다. 기존에는 perf로 Python 프로그램을 프로파일링하면, C 레벨의 함수만 보였습니다. 3.12에서는 Python 함수 이름이 perf 출력에 직접 표시됩니다.

perf 프로파일링
bash
# Python 3.12에서 perf 지원 활성화
python3.12 -X perf my_script.py
 
# 또는 환경 변수로
PYTHONPERFSUPPORT=1 python3.12 my_script.py
 
# perf로 프로파일링
perf record -g -p $PID
perf report

이 기능은 프로덕션 환경에서 Python 애플리케이션의 성능 병목을 분석할 때 유용합니다.

실전 벤치마크

실제 코드 패턴에서의 성능 변화를 측정합니다.

벤치마크 코드
python
import timeit
 
# 컴프리헨션 성능
comprehension_code = """
result = [x * 2 for x in range(10000)]
"""
 
# for 루프 성능
loop_code = """
result = []
for x in range(10000):
    result.append(x * 2)
"""
 
# 딕셔너리 컴프리헨션
dict_comp_code = """
result = {str(k): v * 2 for k, v in enumerate(range(10000))}
"""
 
# 멤버십 테스트
membership_code = """
s = set(range(10000))
result = [x for x in range(20000) if x in s]
"""
 
# 각 벤치마크 실행
for name, code in [
    ("list comprehension", comprehension_code),
    ("for loop", loop_code),
    ("dict comprehension", dict_comp_code),
    ("membership test", membership_code),
]:
    time = timeit.timeit(code, number=1000)
    print(name + ": " + str(round(time, 3)) + "s")
Tip

성능 비교는 동일한 하드웨어, 동일한 OS, 동일한 부하 상태에서 수행해야 의미가 있습니다. pyperformance 벤치마크 스위트를 사용하면 더 신뢰할 수 있는 결과를 얻을 수 있습니다.

정리

Python 3.12의 성능 향상은 특정 기법이 아니라 여러 최적화의 누적 결과입니다.

  • 특수화 적응 인터프리터가 for 루프와 멤버십 테스트에 새로운 특수화를 추가했습니다
  • 컴프리헨션이 인라인으로 실행되어 약 11%의 속도 향상을 달성했습니다
  • Immortal Objects로 참조 카운팅 오버헤드가 감소하고 free-threaded 모드의 기반이 마련되었습니다
  • asyncio의 eager task factory로 동기적 완료 코루틴의 성능이 크게 향상되었습니다
  • sys.monitoring API로 디버깅/프로파일링 시 성능 저하가 대폭 감소했습니다

다음 장 미리보기

7장에서는 Python 3.13의 가장 혁신적인 변화인 free-threaded Python(GIL 제거)을 다룹니다. PEP 703의 배경, free-threaded 빌드의 설치와 사용법, 그리고 실제 멀티스레드 성능 테스트를 살펴봅니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#python#typescript#performance#devtools#concurrency

관련 글

프로그래밍

7장: Free-threaded Python - GIL 제거의 시작

Python 3.13의 free-threaded 모드를 심층 분석합니다. GIL의 역사와 문제점, PEP 703의 설계, free-threaded 빌드의 설치와 실전 멀티스레드 성능을 다룹니다.

2026년 1월 28일·16분
프로그래밍

5장: PyREPL - 새로운 대화형 인터프리터

Python 3.13에서 도입된 PyREPL의 구문 강조, 멀티라인 편집, 자동완성, 히스토리 관리 등 현대적 REPL 기능을 실전 예시와 함께 다룹니다.

2026년 1월 24일·15분
프로그래밍

8장: 실험적 JIT 컴파일러의 원리와 가능성

Python 3.13에 도입된 실험적 JIT 컴파일러를 분석합니다. copy-and-patch 기법의 원리, Tier 2 최적화 파이프라인, 빌드와 활성화 방법, 성능 벤치마크를 다룹니다.

2026년 1월 30일·17분
이전 글5장: PyREPL - 새로운 대화형 인터프리터
다음 글7장: Free-threaded Python - GIL 제거의 시작

댓글

목차

약 16분 남음
  • Faster CPython 프로젝트의 흐름
  • 특수화 적응 인터프리터 (Specializing Adaptive Interpreter)
    • 동작 원리
    • 3.12에서 추가된 특수화
  • 컴프리헨션 인라인화
    • 기존 동작 방식
    • 3.12의 인라인화
  • Immortal Objects (PEP 683)
    • 참조 카운팅의 문제
    • 불멸 객체의 해결책
  • asyncio 성능 향상
    • 주요 최적화
    • eager task factory
  • PEP 669: 저비용 모니터링 API
    • 기존 API의 문제
    • sys.monitoring의 접근 방식
  • Linux perf 프로파일러 지원
  • 실전 벤치마크
  • 정리
  • 다음 장 미리보기