콘텐츠로 바로가기

Python Architecture & GIL

Python 아키텍처 및 GIL의 정의, 범위, 선행 지식, 학습 주제, 참고 근거를 정리한 CS&E 학습 노드입니다.

Article
M

Me

hyunyoun's Blog

programming-languages-compilersprogramming-languagescompilersruntime-systemsmemory-managementpython-architecturegillanguages-compilers9 min read

1. Overview

Python 아키텍처와 GIL(Python Architecture & GIL)은 세계에서 가장 인기 있는 스크립팅 언어의 사실상 표준 구현체인 CPython의 내부 실행 구조를 다루며, Python이 왜 배우기 쉽지만 실행은 느린지, 그리고 GIL이 멀티코어 시대를 어떻게 제한해 왔는지를 정리합니다.

학습자는 소스 코드가 AST를 거쳐 바이트코드로 컴파일되고( .pyc ), C로 구현된 거대한 eval_frame_default 인터프리터 루프에서 실행되는 과정을 살펴봅니다. 이어서 Python 객체 모델의 핵심인 PyObject와 딕셔너리(dict) 기반 네임스페이스, 그리고 Python 동시성의 오래된 과제인 **GIL(Global Interpreter Lock)**의 탄생 배경과 멀티프로세싱/asyncio를 통한 우회 전략, 최근 Python 3.13의 Free-threading (No-GIL) 선언까지 Python 런타임의 변화 흐름을 정리합니다.

2. Scope & Boundaries

In-Scope

  • CPython 아키텍처 (CPython Architecture): CPython 인터프리터 루프, 바이트코드 컴파일 과정, .pyc 캐싱.
  • 객체 모델 (Object Model): PyObject 구조(참조 계수, 타입 포인터), 모든 것이 객체(Everything is an Object), __dict__ 이름 공간.
  • 메모리 관리 (Memory Management): 참조 계수(Reference Counting) 기반 기본 GC, 순환 참조 탐지(Cyclic GC).
  • GIL과 동시성 (GIL & Concurrency): GIL(Global Interpreter Lock)의 원리, CPU-bound vs I/O-bound 스레딩 효과, multiprocessing 모듈, PEP 703 (No-GIL).

Out-of-Scope

  • 타 런타임 (PyPy, Jython, MicroPython): 대체 구현체 아키텍처 상세 → 비교 언급 정도로 제한.
  • Python 문법/표준 라이브러리 활용: 언어 자체의 기능 사용법 → 언어 기본 영역.

Boundaries

  • 스레드 vs 프로세스 (GIL 제약): Python threading은 I/O 바운드 작업(네트워크 요청, 파일 읽기)에서는 GIL이 해제되어 동시성 이득을 얻지만, CPU 바운드 작업(행렬 연산, 암호화)에서는 GIL로 인해 싱글 코어 성능에 머뭅니다. CPU 바운드 작업을 병렬화하려면 반드시 multiprocessing을 사용하여 프로세스 간 통신(IPC) 비용을 지불하거나 C 확장을 사용해야 합니다.

3. Counterexample

  • 멀티스레딩으로 CPU 계산 가속화 시도 (CPU-bound Multithreading with GIL): 피보나치 수열 계산 같은 CPU 바운드 작업을 빠르게 하려고 Python threading.Thread를 4개 생성하여 병렬 실행. 결과적으로 단일 스레드 실행보다 더 느려집니다(컨텍스트 스위칭 오버헤드 + GIL 경쟁). GIL로 인해 한 번에 하나의 스레드만 파이썬 바이트코드를 실행할 수 있기 때문입니다. CPU 집약적 연산은 멀티스레딩이 아니라 멀티프로세싱을 써야 합니다.
  • Mutable Default Argument 버그 (Mutable Default Argument): def append_to(element, target=[]): target.append(element); return target. 함수 선언 시점에 target 리스트 객체가 생성되고(네임스페이스에 바인딩), 이후 모든 함수 호출이 동일한 리스트 객체를 공유합니다. append_to(1)[1], append_to(2)[1, 2]. 모든 것이 객체이고 함수 정의 자체도 실행 시점의 객체 생성임을 이해하지 못해 발생하는 CPython 객체 모델의 고전적 함정입니다.

4. Prerequisites

  • 참조 계수 GC (Basic): CPython의 메모리 관리가 참조 계수를 기반으로 함. (05-02-04 Garbage Collection & Memory Management)
  • 가상 머신 기초 (Basic): 바이트코드와 인터프리터 루프 개념. (05-02-03 Virtual Machines & JIT Compilation)

5. Learning Map

Sequence Core Cluster Objective & Description Evidence (BoK)
1 CPython VM & Bytecode 파이썬 소스가 바이트코드로 변환되고 ceval.c 스택 머신에서 실행되는 루프를 이해합니다. P1
2 PyObject & Memory 모든 변수가 PyObject* 포인터이며, 딕셔너리로 관리되는 네임스페이스와 참조 계수 기반 메모리 해제를 살펴봅니다. P5
3 GIL (Global Interpreter Lock) CPython C API 안전성을 위해 도입된 GIL이 CPU 병렬성을 어떻게 제한하는지 살펴봅니다. Industry
4 Concurrency Workarounds & No-GIL 멀티프로세싱/C 확장을 통한 GIL 우회와, Python 3.13의 Free-threading (PEP 703) 흐름을 다룹니다. Industry

6. Learning Topics

Basic

Core Topic 01: 스택 기반의 거대한 C 스위치 문, CPython VM과 바이트코드 (CPython VM & Bytecode)

  • Why to Learn: "Python 코드는 어떻게 실행되는가?"에 대한 답은 CPython 런타임의 ceval.c에 있는 거대한 루프에 있습니다. 컴파일 속도가 빠른 바이트코드 체계와, 그것을 인터프리팅하는 스택 머신을 이해하기 위함입니다.
  • What to Learn:
    • Concepts: 소스 코드 → 파서(AST) → 컴파일러 → 바이트코드(.pyc), 스택 기반 가상 머신(Stack Machine), dis 모듈, 프레임 객체(Frame Object).
    • Skills: dis 모듈로 바이트코드 분석, 함수 호출 시 프레임 스택 동작 이해.
  • How to Learn:
    • 1단계: 바이트코드 컴파일: __pycache__에 저장되는 .pyc 파일은 파싱 시간을 절약할 뿐 런타임 속도를 빠르게 하진 않음을 확인합니다.
    • 2단계: 스택 머신: a = 1 + 2. LOAD_CONST 1, LOAD_CONST 2, BINARY_ADD, STORE_FAST a. 피연산자를 스택에 쌓고, 연산자가 스택에서 꺼내 계산 후 다시 쌓는 구조를 살펴봅니다.
  • Implement: 파이썬 import dis; dis.dis("a, b = b, a"). 튜플 언패킹(ROT_TWO 바이트코드)이 임시 변수 할당보다 왜 효율적인지(가상 머신 스택 조작) 바이트코드 수와 동작 비교 출력. CPython의 거대 C 스위치문 모사 미니 파이썬 바이트코드 인터프리터 구현.

Core Topic 02: 모든 것은 객체이고 모든 이름은 딕셔너리다, PyObject와 메모리 (PyObject & Memory)

  • Why to Learn: "파이썬에는 변수가 없고 이름표(Tag)만 있다"는 말의 실체인 C 구조체 PyObject를 이해해야 동적 타이핑의 비용, 참조 횟수 메모리 관리, 얕은 복사/깊은 복사 버그를 명확히 진단할 수 있습니다.
  • What to Learn:
    • Concepts: PyObject (C 구조체: ob_refcnt, ob_type), 모든 것은 객체(Everything is an Object), 네임스페이스(globals(), locals()), id()(메모리 주소), 참조 계수(Reference Counting).
    • Skills: id() 비교와 is 연산자, sys.getrefcount(), 작은 정수 캐싱(Small Integer Caching).
  • How to Learn:
    • 1단계: PyObject 구조: 모든 파이썬 값은 힙에 할당된 C 구조체입니다. x = 4242를 담은 PyLongObject를 만들고 이름 x를 그 주소에 바인딩합니다. y = x는 객체 복사가 아니라 포인터 복사(참조 계수 +1)임을 확인합니다.
    • 2단계: 네임스페이스: 클래스, 인스턴스, 모듈 등 모든 스코프는 내부적으로 C dict(__dict__)입니다. 객체 속성 조회 obj.attr은 해시 테이블 룩업과 동일하여 동적이지만 C/C++ 구조체 접근보다 느린 원인을 살펴봅니다.
  • Implement: 파이썬 id()is 시뮬레이션. a = 256; b = 256; a is b (True) (작은 정수 캐싱 -5~256). a = 257; b = 257; a is b (False) 콘솔 출력으로 싱글턴 캐싱 정책 증명. 인스턴스 __dict__ 조작을 통해 동적으로 속성이 추가/제거되는 Everything is a dict 데모.

Practical

Core Topic 03: 코어 하나만 씁니다, GIL의 탄생과 한계 (Global Interpreter Lock)

  • Why to Learn: 멀티코어 서버에서 CPU 사용률이 100%(코어 1개)를 넘지 못하는 파이썬의 병목, GIL(Global Interpreter Lock)이 왜 만들어졌고 정확히 무엇을 막는지를 이해하기 위함입니다.
  • What to Learn:
    • Concepts: GIL(Global Interpreter Lock), CPython 스레드 안전성(Thread Safety for C API), 참조 계수 경쟁 조건 방지, 컨텍스트 스위칭 비용.
    • Skills: CPU-bound 작업과 I/O-bound 작업 식별, GIL 해제 조건.
  • How to Learn:
    • 1단계: GIL의 존재 이유: CPython 메모리 관리는 참조 계수(ob_refcnt)를 씁니다. 여러 스레드가 동시에 x를 참조하면 참조 계수 업데이트 시 Race Condition이 발생하여 메모리가 누수되거나 크래시됩니다. 모든 객체에 락을 거는 대신 인터프리터 전체에 단 1개의 큰 락(GIL)을 거는 해결 방식을 살펴봅니다.
    • 2단계: I/O와 GIL 해제: 네트워크 대기, time.sleep(), 파일 읽기 등 I/O 대기 중에는 CPython이 자발적으로 GIL을 해제(Release)하여 다른 스레드가 파이썬 코드를 실행할 수 있게 합니다. 즉, I/O 바운드 작업에서는 파이썬 멀티스레딩이 유효함을 살펴봅니다.
  • Implement: 파이썬 스레딩 벤치마크. count_to_100_million() (CPU-bound) 단일 스레드 실행 시간 vs 4개 스레드 분할 실행 시간 비교 (멀티스레드가 더 느림 증명). fetch_url_sleep() (I/O-bound) 단일 스레드 순차 실행 시간 vs 4개 스레드 병렬 실행 시간 비교 (멀티스레드가 약 4배 빠름 증명).

Advanced

Core Topic 04: GIL을 우회하고 마침내 제거하다, 병렬성과 No-GIL (Concurrency Workarounds & PEP 703)

  • Why to Learn: GIL의 제약 아래서 실제 데이터 사이언스 워크로드를 병렬화하는 C 확장/멀티프로세싱 우회 기법을 익히고, Python 3.13부터 도입되는 "GIL 없는 파이썬(Free-threading)" 변화가 시스템에 미칠 영향을 이해하기 위해서입니다.
  • What to Learn:
    • Concepts: multiprocessing (IPC 비용과 프로세스 분리), C 확장/Cython/Numba의 GIL 해제(with nogil), PEP 703(Making the Global Interpreter Lock Optional in CPython), 바이어스 참조 계수(Biased Reference Counting), 밈 멀티스레딩(Mimalloc).
    • Skills: ProcessPoolExecutor vs ThreadPoolExecutor 선택 기준.
  • How to Learn:
    • 1단계: GIL 우회: CPU 바운드 작업을 병렬화하려면 1) multiprocessing으로 여러 CPython 프로세스를 띄우거나 (객체 직렬화/역직렬화 통신 오버헤드 존재), 2) Numpy 내부 C 코드처럼 연산 중 명시적으로 GIL을 해제하고 C 레벨에서 병렬 처리하는 전략을 살펴봅니다.
    • 2단계: PEP 703 (No-GIL): GIL을 제거하면 참조 계수를 원자적(Atomic) 연산으로 바꿔야 하므로 싱글 스레드 성능이 10~15% 하락합니다. 이를 극복하기 위해 객체 접근 스레드에 락 소유권을 편향(Biased)시키는 최신 런타임 락-프리 최적화 기법을 살펴봅니다.
  • Implement: CPU 병렬화 벤치마크. 행렬 곱셈 시뮬레이션을 concurrent.futures.ThreadPoolExecutor (GIL 블로킹 확인) vs ProcessPoolExecutor (멀티코어 활용, IPC 오버헤드 측정) 시간 비교. NumPy 벡터화 연산 실행 시 내부 C 확장 코드에서 GIL이 자동 해제되어 다중 코어를 활발히 사용하는 Htop 캡처 데모.

7. Terminology

Term (EN / ko, abbr) 1문장 정의 단계(기본/권장/실무/심화) 역할/맥락 관련 개념 유사/대비/함께 사용 오해 포인트 Evidence(Primary/Secondary/Industry) Flags(core)
CPython 우리가 흔히 부르는 '파이썬'의 공식이자 기본 구현체로, C 언어로 작성되어 파이썬 코드를 바이트코드로 컴파일한 뒤 가상머신에서 실행합니다. 기본 언어 구현체 Interpreter PyPy / Jython 파이썬 코드가 기계어로 직역되는 것이 아니라 C로 만든 가상머신 위에서 실행됨 P1:CS2023 core
GIL (Global Interpreter Lock) 멀티스레드 환경에서 여러 스레드가 동시에 파이썬 객체에 접근하여 메모리가 꼬이는 것을 막기 위해, 한 번에 단 하나의 스레드만 파이썬 바이트코드를 실행하도록 강제하는 뮤텍스입니다. 권장 동시성 제어 Thread / Mutex Multiprocessing 스레드를 10개 만들어도 GIL 때문에 10개가 병렬(Parallel)로 실행되지 않음 P5:SFIA core
PyPy CPython의 느린 속도를 개선하기 위해 만들어진 JIT(Just-In-Time) 컴파일러 기반의 대안 파이썬 구현체입니다. 실무 성능 최적화 JIT Compiler CPython 무조건 빠른 것은 아니며, C 확장 모듈을 많이 쓰는 경우 오히려 느려질 수 있음 Industry core
Duck Typing 객체의 실제 상속 타입(Class)이 무엇인지 묻지 않고, 객체가 특정 메서드나 속성을 가지고 있으면(오리처럼 꽥꽥거리면) 오리로 취급하여 실행하는 파이썬의 핵심 타입 철학입니다. 심화 다형성(Polymorphism) Dynamic Typing Nominal Typing 인터페이스 선언 없이 런타임에 속성 존재 여부만으로 다형성이 성립함 Industry core

8. References

Primary

  • [P1] CS2023 - Programming Languages (PL) - Runtime Systems
  • [P5] SFIA - Software Development (PROG) - Performance Tuning

Secondary

  • [Fluent Python] Luciano Ramalho - Pythonic Data Model and Metaprogramming
  • [Effective Python] Brett Slatkin - Concurrency and Parallelism (GIL)

Industry

  • [Python Developer's Guide] - CPython Internals & Global Interpreter Lock
  • [PyPy Documentation] - Just-in-Time Compilation in PyPy

9. Final Checklist

Primary

  • CPython 아키텍처에서 소스 코드(.py)가 바이트코드(.pyc)로 컴파일된 후 가상머신(PVM) 위에서 한 줄씩 인터프리팅되는 물리적 과정을 설명할 수 있는가?
  • 파이썬의 동적 타입 검사(Dynamic Typing)와 덕 타이핑(Duck Typing) 철학이 컴파일 언어의 정적 타입 시스템(Static Typing) 대비 갖는 개발 속도 상의 이점을 논증할 수 있는가?

Secondary

  • 파이썬 프로그램에서 CPU 바운드 작업을 할 때 스레드(Thread)를 늘려도 속도가 전혀 빨라지지 않는 이유를 GIL(Global Interpreter Lock)의 CPython 구조적 한계로 설명할 수 있는가?
  • GIL의 한계를 극복하기 위해 multiprocessing 모듈을 사용하여 프로세스를 나누는 우회 기법의 메모리 오버헤드(IPC 비용)를 분석할 수 있는가?

Industry

  • I/O 바운드(네트워크 통신 등) 작업에서는 GIL이 대기 상태에서 풀리므로, 스레딩이나 asyncio 코루틴이 여전히 강력한 성능을 내는 원리를 설계할 수 있는가?
  • 순수 수학 연산이 많은 파이썬 코드를 실행할 때, CPython 대신 JIT 기반의 PyPy를 도입하거나 C 모듈(NumPy, Cython)로 병목을 오프로딩하는 아키텍처 결정을 내릴 수 있는가?

Languages Compilers · Runtime Systems & Memory Management

1 / 4