트랜지스터에서 시작해 파이썬 코드가 실행되기까지의 전체 흐름을 이해한다.
🎯 이 강의에서 배우는 것
파이썬을 배우기 전에, 먼저 물어봐야 할 질문이 있습니다. 컴퓨터는 어떻게 생각하는가?
우리가 print("Hello, World!")라고 쓰면 화면에 글자가 나타납니다. 이 짧은 한 줄이 실행되기까지, 트랜지스터에서 시작해 논리 게이트, CPU, 운영체제, 파이썬 인터프리터를 거치는 긴 여정이 있습니다. 이 여정을 이해하면 파이썬이 왜 이렇게 동작하는지, 오류가 왜 발생하는지, 어떻게 더 좋은 코드를 쓸 수 있는지가 보이기 시작합니다.
⚡ 트랜지스터: 0과 1의 물리적 기원
현대 컴퓨터의 모든 것은 트랜지스터(transistor)에서 시작합니다. 트랜지스터는 전류를 켜고(ON) 끄는(OFF) 스위치입니다. 이 두 가지 상태가 각각 1과 0이 됩니다.
2023년 기준, 최신 CPU 하나에는 수백억 개의 트랜지스터가 집적되어 있습니다. 이 수백억 개의 스위치들이 협력해 인터넷 서핑, 게임, AI 연산을 가능하게 합니다.
트랜지스터를 조합하면 논리 게이트(logic gate)가 만들어집니다. 논리 게이트는 0과 1의 입력을 받아 규칙에 따라 0 또는 1을 출력합니다.
- AND 게이트: 두 입력이 모두 1일 때만 1 출력 (A 그리고 B)
- OR 게이트: 두 입력 중 하나라도 1이면 1 출력 (A 또는 B)
- NOT 게이트: 입력을 반전(0→1, 1→0)
이 세 가지 기본 게이트를 조합하면 반가산기(half adder)가 만들어집니다. 반가산기는 1비트 두 개를 더해 합(Sum)과 올림수(Carry)를 계산합니다. 1 + 1 = 10(이진수, 10진수의 2)을 계산하는 회로가 불과 몇 개의 트랜지스터로 구현됩니다. 반가산기 두 개를 연결하면 전가산기(full adder)가 되고, 전가산기를 8개 연결하면 8비트 덧셈 회로가 됩니다. CPU 내부의 ALU(산술 논리 연산 장치)는 이런 회로들로 구성됩니다.
🔢 이진수 체계 — 왜 컴퓨터는 2진수를 쓰는가
사람은 손가락이 10개라서 10진수를 자연스럽게 사용합니다. 컴퓨터는 전기 신호가 ON 또는 OFF, 두 가지뿐이라서 2진수를 사용합니다. 안정적으로 구분할 수 있는 상태의 수가 2개이기 때문입니다.
| 단위 | 크기 | 예시 |
|---|---|---|
| 1 비트(bit) | 0 또는 1 | 전구 1개의 켜짐/꺼짐 |
| 1 바이트(byte) | 8비트 = 256가지 | 영문자 1글자 |
| 1 킬로바이트(KB) | 1,024 바이트 | 짧은 텍스트 파일 |
| 1 메가바이트(MB) | 1,024 KB | MP3 약 1분 |
| 1 기가바이트(GB) | 1,024 MB | HD 영상 약 30분 |
8비트(1바이트)로 표현할 수 있는 숫자는 2⁸ = 256가지입니다. 부호 없는 정수(unsigned)라면 0~255, 부호 있는 정수(signed, 2의 보수법)라면 -128~127을 표현합니다. 파이썬에서 이진수를 직접 다뤄봅시다:
# 2진수, 8진수, 16진수 리터럴
a = 0b1010 # 이진수 1010 = 10진수 10
b = 0o17 # 8진수 17 = 10진수 15
c = 0xFF # 16진수 FF = 10진수 255
print(a, b, c) # 10 15 255
# 10진수를 각 진법 문자열로 변환
print(bin(255)) # 0b11111111 (8비트 모두 1)
print(oct(255)) # 0o377
print(hex(255)) # 0xff
# 8비트 부호 없는 정수 범위
print(2**8 - 1) # 255 (최대값)
print(-(2**7)) # -128 (부호 있는 최솟값)
파이썬에서 정수는 오버플로우가 없습니다. C 언어의 int는 32비트 안에 갇혀 있지만, 파이썬의 int는 메모리가 허락하는 한 무한히 커집니다. 이 놀라운 특성은 6강 '정수 완전 해부'에서 자세히 다룹니다.
🧠 CPU의 명령 실행 사이클
CPU(Central Processing Unit)는 컴퓨터의 두뇌입니다. CPU는 메모리에서 명령을 읽어 실행하는 일을 끊임없이 반복합니다. 이 과정을 명령 실행 사이클(instruction cycle)이라고 부릅니다.
- Fetch (인출): 프로그램 카운터(PC, Program Counter)가 가리키는 메모리 주소에서 다음 명령어를 가져옵니다.
- Decode (해독): 가져온 명령어가 무엇을 의미하는지 해석합니다(덧셈인가, 메모리 저장인가, 점프인가).
- Execute (실행): ALU에서 연산을 수행하거나 메모리에 접근합니다.
- Write-back (저장): 연산 결과를 레지스터나 메모리에 다시 저장합니다.
이 4단계 사이클은 1초에 수십억 번 반복됩니다. 3GHz CPU는 초당 30억 번 이 사이클을 돌립니다. CPU가 처리하는 데이터는 속도와 용량이 반비례하는 계층 구조에서 가져옵니다:
| 저장소 | 접근 속도 | 용량 | 역할 |
|---|---|---|---|
| 레지스터 | ~1 사이클 (최고속) | 수십~수백 바이트 | 현재 연산 중인 값 |
| L1/L2/L3 캐시 | 2~50 사이클 | 수 KB~수십 MB | 자주 쓰는 데이터 임시 보관 |
| RAM (주기억장치) | 수백 사이클 | 수 GB | 실행 중인 프로그램과 데이터 |
| SSD (보조기억장치) | 수천~수만 사이클 | 수 TB | 영구 저장 |
이 계층 구조가 프로그래밍에 미치는 영향은 큽니다. 예를 들어 파이썬 리스트는 메모리상에 연속 배치되어 캐시 효율이 높지만, 딕셔너리는 해시 충돌 처리로 메모리 접근이 불연속적입니다. 대용량 데이터를 처리할 때 자료구조 선택이 성능에 영향을 주는 이유가 바로 캐시 계층 구조에 있습니다.
💻 소스 코드에서 실행까지 — 고수준 언어의 탄생
CPU는 기계어(machine code)만 이해합니다. 기계어는 0과 1의 나열로, 사람이 읽고 쓰기 매우 어렵습니다. 초창기 프로그래머들은 기계어를 조금 더 읽기 쉽게 표현한 어셈블리 언어(assembly language)를 사용했습니다.
두 수를 더하는 x86 어셈블리는 이런 형태입니다:
# 아래 주석은 x86 어셈블리 코드의 예시입니다
# MOV AX, 5 → AX 레지스터에 5를 저장
# MOV BX, 3 → BX 레지스터에 3을 저장
# ADD AX, BX → AX = AX + BX = 8
# 파이썬에서는 위의 모든 과정을 단 한 줄로:
result = 5 + 3
print(result) # 8
파이썬 같은 고수준 언어(high-level language)는 어셈블리의 복잡성을 추상화하고 사람이 읽기 쉬운 문법을 제공합니다. 그 대신, 소스 코드를 실행 가능한 형태로 변환하는 과정이 필요합니다. 변환 방식에는 크게 두 가지가 있습니다:
- 컴파일러(Compiler) — C, Go, Rust: 프로그램 전체를 한꺼번에 기계어로 번역. 실행 속도가 빠르지만 번역(컴파일) 시간이 필요합니다.
- 인터프리터(Interpreter) — Python, Ruby: 소스 코드를 한 줄씩 읽어 즉시 실행. 즉시 결과를 볼 수 있지만 실행 속도는 상대적으로 느립니다.
Java는 중간 방식을 택합니다. 소스 코드를 JVM 바이트코드로 컴파일한 후, JVM이 그 바이트코드를 해석하거나 JIT 컴파일합니다. 파이썬도 내부적으로 유사한 단계를 거칩니다.
🐍 CPython: 파이썬이 실제로 실행되는 방법
우리가 일반적으로 설치하는 파이썬은 CPython입니다. C 언어로 작성된 파이썬의 공식 기준 구현체입니다. 파이썬 코드가 실행되기까지의 과정을 단계별로 살펴봅시다:
- 소스 코드 (.py): 우리가 작성하는 파이썬 코드.
- 컴파일 → 바이트코드 (.pyc): CPython이 소스 코드를 바이트코드(bytecode)로 변환합니다. 이 파일은
__pycache__폴더에 저장되어 다음 실행 시 재사용됩니다(변경이 없으면 파싱을 생략해 빠르게 시작). - PVM 실행: 파이썬 가상 머신(PVM, Python Virtual Machine)이 바이트코드를 한 명령씩 해석해 실행합니다.
바이트코드는 dis 모듈로 직접 확인할 수 있습니다:
import dis # dis = disassembler (역어셈블러)
def add(a, b):
return a + b
dis.dis(add)
# 출력 (Python 3.12 기준):
# 2 RESUME 0
#
# 3 LOAD_FAST 0 (a) ← 지역변수 a를 스택에 올림
# LOAD_FAST 1 (b) ← 지역변수 b를 스택에 올림
# BINARY_OP 0 (+) ← 스택에서 두 값을 꺼내 더함
# RETURN_VALUE ← 결과를 반환
LOAD_FAST는 지역 변수를 스택에 올리는 명령이고, BINARY_OP는 스택 상위 두 값을 연산합니다. 이처럼 파이썬도 내부적으로 컴파일 단계를 거치지만, 기계어 대신 PVM이 이해하는 바이트코드로 변환됩니다. 이 구조 덕분에 파이썬 코드는 Windows, macOS, Linux에서 동일하게 동작합니다 — PVM이 플랫폼 차이를 흡수하기 때문입니다.
CPython 외에도 여러 파이썬 구현체가 존재합니다:
- PyPy: JIT(Just-In-Time) 컴파일러를 내장해 CPython보다 수 배 빠릅니다. 장기 실행 연산 집약적 프로그램에 유리합니다.
- Jython: JVM 위에서 동작하는 파이썬. Java 라이브러리를 파이썬에서 활용할 수 있습니다.
- MicroPython: 라즈베리 파이 피코 같은 마이크로컨트롤러에서 동작하도록 경량화된 파이썬.
이 강좌에서는 모두 CPython 기준으로 설명합니다. 특별한 이유가 없다면 CPython(공식 python.org 설치)을 사용하세요.
📝 정리 및 다음 강의 예고
이번 강의에서 배운 핵심 내용을 정리합니다:
- 컴퓨터는 ON/OFF 전기 신호를 0과 1로 해석하는 트랜지스터로 이루어져 있습니다.
- AND·OR·NOT 논리 게이트를 조합하면 덧셈 회로(반가산기→전가산기)가 만들어집니다.
- CPU는 Fetch → Decode → Execute → Write-back 사이클을 초당 수십억 번 반복합니다.
- 레지스터 → 캐시 → RAM → 디스크 순서로 속도는 빠르고, 용량은 작아집니다.
- 파이썬은 소스 코드(.py) → 바이트코드(.pyc) → PVM 실행의 3단계를 거칩니다.
다음 강의: 파이썬 개발 환경을 완전히 설정합니다. Python 설치, PATH 설정, VS Code 구성, 그리고 가상환경(venv)으로 깨끗한 프로젝트 환경을 만드는 방법을 배웁니다.
관련 주제
- 트랜지스터와 논리게이트
- 이진수 체계
- CPU 명령 실행 사이클
- 컴파일러 vs 인터프리터
- CPython 구조
- 레지스터·캐시·RAM 계층
- 개발·프로그래밍
- 개발·프로그래밍 강의
- 파이썬 기초 40강 — 처음 배우는 프로그래밍
- 무료강의
- 무료 온라인 강의
- NUGUNA
- 누구나
