왜 CPU 구조를 공부하게 되었나
코드를 짜다 보면 "왜 이 코드가 느리지?"라는 의문이 생길 때가 있습니다. 프로파일러를 돌려보면 특정 함수에서 병목이 걸리는데, 알고리즘 복잡도는 문제가 없습니다. 캐시 미스? 브랜치 프리딕션 실패? 이런 용어들이 나오기 시작하면, 결국 CPU가 내부적으로 어떻게 돌아가는지를 알아야 했습니다.
그래서 CPU를 뜯어보기로 했습니다. 물리적으로가 아니라, 개념적으로.
처음엔 뭐가 이해가 안 갔나
CPU는 그냥 "빠른 계산기"인 줄 알았습니다. "전기 넣으면 덧셈하는 기계" 정도로 생각했는데, 구조를 보니 생각보다 복잡했습니다.
- 제어장치(CU)랑 ALU는 왜 분리되어 있는 거지?
- 레지스터는 메모리(RAM)랑 뭐가 다른 건데?
- 클럭(Clock)이라는 게 대체 무슨 역할을 하는 거야?
한번에 다 이해하려니 머리가 터질 것 같아서, 공장 비유로 정리하기로 했습니다.
깨달음의 순간 - "CPU는 초고속 공장이다"
CPU를 현미경으로 들여다보면, 그 안에는 거대한 도시가 들어있습니다. 이 도시를 이해하는 가장 쉬운 비유는 공장입니다.
공장에는 세 가지 핵심 구성원이 있습니다:
- 관리자 (제어장치): 작업 지시를 내리는 사람
- 노동자 (ALU): 실제로 물건을 만드는 사람
- 작업대 (레지스터): 당장 필요한 재료와 도구를 올려놓는 책상
이 세 가지가 CPU의 전부입니다. 나머지는 전부 이 구조의 변형이거나 최적화입니다.
1. 제어장치 (Control Unit) - 관리자
오케스트라의 지휘자와 같습니다. 메모리에서 명령어를 읽어오고, 해석하고, 각 부품에게 지시를 내립니다.
- "야, 메모리! 100번지에 있는 데이터 가져와."
- "야, ALU! 이거 두 개 더해."
- "야, 레지스터! 결과값 저장해."
직접 일은 안 하지만, 모든 흐름을 통제하는 Manager입니다.
제어장치가 하는 일을 좀 더 구체적으로 보면:
- 명령어 페치(Fetch): 메모리에서 다음 실행할 명령어를 가져옵니다.
- 명령어 디코드(Decode): "0x1A"같은 기계어를 해석해서 "아, 이건 덧셈이구나"를 파악합니다.
- 제어 신호 생성: ALU에 "더해라", 레지스터에 "저장해라" 같은 전기 신호를 보냅니다.
제어장치 없이는 ALU가 아무리 빨라도 뭘 계산해야 하는지 모릅니다. 공장에 노동자만 있고 관리자가 없으면 아무도 뭘 만들어야 하는지 모르는 것과 같습니다.
2. ALU (Arithmetic Logic Unit): 노동자
실제로 일을 하는 곳입니다. 두 가지 종류의 연산을 수행합니다.
산술 연산 (Arithmetic):
- 덧셈, 뺄셈, 곱셈, 나눗셈
- 우리가 앞서 배운 가산기(Adder)들이 잔뜩 모여있는 곳
논리 연산 (Logic):
- AND, OR, NOT, XOR
- 비교 연산 (A가 B보다 큰가?)
ALU 자체는 단순합니다. "시키신 덧셈 완료했습니다", "시키신 비교 연산 완료했습니다" — 묵묵히 계산만 하는 Worker입니다.
하지만 현대 CPU의 ALU는 단순한 덧셈기가 아닙니다. 부동소수점 연산을 위한 FPU(Floating Point Unit)가 별도로 존재하고, 벡터 연산을 위한 SIMD(Single Instruction, Multiple Data) 유닛도 들어있습니다. 하나의 명령어로 여러 데이터를 동시에 처리할 수 있어서, 이미지 처리나 과학 계산에서 성능이 크게 향상됩니다.
3. 레지스터 (Register) - 작업대
CPU가 일할 때 쓰는 초고속 임시 메모리입니다. RAM(메모리)이 '책장'이라면, 레지스터는 '책상 위'입니다.
책장에 있는 책을 가지러 왔다 갔다 하면 시간이 오래 걸리죠? 그래서 당장 계산할 숫자들은 책상(레지스터)에 올려놓고 작업하는 겁니다.
주요 레지스터 종류
범용 레지스터 (General Purpose):
- 계산할 데이터를 임시로 담아두는 상자들
- x86에서는
EAX,EBX,ECX,EDX등이 있고, ARM에서는R0~R15
특수 목적 레지스터:
- PC (Program Counter): "다음엔 책장 몇 번째 칸 읽을 차례지?" — 다음 명령어의 메모리 주소를 기억합니다.
- IR (Instruction Register): "지금 읽고 있는 책 내용이 뭐지?" — 현재 실행 중인 명령어를 저장합니다.
- ACC (Accumulator): "계산 결과 잠시 여기 둬." — 연산 중간값을 저장합니다.
- MAR (Memory Address Register): 메모리에서 읽거나 쓸 주소를 담습니다.
- MDR (Memory Data Register): 메모리에서 읽어온 데이터, 또는 메모리에 쓸 데이터를 담습니다.
- SP (Stack Pointer): 스택의 꼭대기 주소를 가리킵니다. 함수 호출/리턴에 핵심적입니다.
- PSW/FLAGS (Program Status Word): 마지막 연산 결과의 상태 (양수? 음수? 제로? 오버플로우?) 를 기록합니다.
속도 비교 - 왜 레지스터가 필요한가
| 메모리 종류 | 접근 시간 | 비유 |
|---|---|---|
| 레지스터 | ~0.3ns | 책상 위에 놓인 메모지 |
| L1 캐시 | ~1ns | 서랍 안 |
| L2 캐시 | ~3-10ns | 같은 방 책장 |
| RAM | ~50-100ns | 복도 끝 도서관 |
| SSD | ~100,000ns | 다른 건물 창고 |
레지스터는 RAM보다 200배 이상 빠릅니다. CPU가 연산할 때마다 RAM까지 왔다 갔다 하면 성능이 바닥을 칩니다. 그래서 당장 필요한 데이터만 레지스터에 올려놓고, 계산이 끝나면 결과를 메모리에 기록하는 방식으로 작동합니다.
4. CPU의 심장 박동 - Fetch-Decode-Execute 사이클
CPU는 전원이 켜진 순간부터 꺼질 때까지 딱 한 가지 루프를 반복합니다. 이것이 FDE(Fetch-Decode-Execute) 사이클이고, 초당 수십억 번 돌아갑니다.
1단계 - Fetch (가져오기)
- PC가 가리키는 메모리 주소에서 명령어를 가져옵니다.
- 가져온 명령어를 IR(Instruction Register)에 저장합니다.
- PC를 다음 명령어 주소로 증가시킵니다. (자동으로 다음 줄로 넘어감)
2단계 - Decode (해석하기)
- 제어장치가 IR에 있는 명령어를 읽습니다.
- "0x1A? 아, 이건 ADD 명령이구나."
- 피연산자(operand)가 뭔지 파악합니다. "레지스터 R1과 R2를 더해라."
3단계 - Execute (실행하기)
- 제어장치가 ALU에 제어 신호를 보냅니다.
- ALU가 실제 연산을 수행합니다. (R1 + R2)
- 결과를 지정된 레지스터나 메모리에 저장합니다 (Write Back).
이 세 단계가 하나의 명령어를 처리하는 전부입니다.
a = b + c; 같은 코드 한 줄도, CPU 입장에서는 여러 번의 FDE 사이클을 돌려야 합니다.
(b를 레지스터로 로드 → c를 레지스터로 로드 → 덧셈 → 결과를 a의 메모리 주소에 저장)
클럭(Clock)과 GHz
이 사이클의 속도를 결정하는 것이 클럭(Clock)입니다. 클럭은 일정한 간격으로 "틱, 틱, 틱" 신호를 보내는 메트로놈 같은 존재입니다.
- 1GHz = 초당 10억 번 틱
- 4GHz = 초당 40억 번 틱
매 틱마다 FDE 사이클의 한 단계가 진행됩니다. (실제로는 파이프라이닝으로 더 복잡하지만, 기본 원리는 이것) CPU가 빠르다는 건, 결국 이 공장이 1초에 수십억 번 돌아간다는 뜻입니다.
5. 설계 철학: CISC vs RISC
CPU를 설계할 때 근본적인 철학의 차이가 있습니다. "명령어를 복잡하게 만들까, 단순하게 만들까?"
CISC (Complex Instruction Set Computer) — Intel x86
철학: "하나의 명령어가 많은 일을 하게 하자."
- 하나의 명령어로 메모리에서 읽기 + 계산 + 저장까지 한 번에 수행합니다.
- 장점: 프로그래머(컴파일러) 입장에서 코드가 짧아집니다. 하드웨어가 무거운 일을 대신 해줍니다.
- 단점: 회로가 복잡해지고, 전력 소모가 큽니다. 발열 이슈가 심합니다.
- 대표: Intel, AMD의 데스크톱/서버 CPU
RISC (Reduced Instruction Set Computer) — ARM
철학: "명령어를 최대한 단순하게 유지하자."
- Load, Store, Add, Compare 같은 기본 명령만 제공합니다.
- 복잡한 작업은 단순한 명령어를 여러 개 조합해서 수행합니다.
- 장점: 회로가 단순해서 전력 효율이 좋습니다. 파이프라이닝 최적화에 유리합니다.
- 단점: 같은 작업을 수행하는 데 더 많은 명령어가 필요합니다 (컴파일러가 더 열심히 일해야 합니다).
- 대표: ARM(Apple M시리즈, 스마트폰), RISC-V
현실에서의 융합
재밌는 건, 오늘날 이 구분이 점점 흐려지고 있다는 것입니다. Intel의 x86 CPU는 겉으로는 CISC 명령어를 받지만, 내부적으로는 마이크로옵스(micro-ops)라는 RISC 스타일 명령으로 변환해서 실행합니다. Apple의 M1/M2 칩은 RISC(ARM) 기반이지만, 데스크톱급 성능을 내면서도 전력 효율이 뛰어납니다.
결국 "어떤 게 더 좋다"가 아니라, 용도에 따른 트레이드오프입니다.
- 서버/데스크톱처럼 전원이 풍부한 환경 → CISC(x86)의 호환성과 생태계
- 모바일/임베디드처럼 배터리가 중요한 환경 → RISC(ARM)의 전력 효율
CPU = 똑똑한 바보
이 구조를 보면 알 수 있듯이, CPU는 창의적인 생각을 하는 뇌가 아닙니다. 그저 제어장치(관리자)가 시키는 대로, 레지스터(책상)에 있는 데이터를 ALU(노동자)가 지지고 볶는 기계적인 공장일 뿐입니다.
하지만 이 공장이 초당 수십억 번(GHz) 돌아가기 때문에, 우리 눈에는 엄청나게 똑똑한 것처럼 보이는 것이죠.
정리하면
우리가 코드로 if, for, +를 칠 때마다 이 공장 내부에서는 비상이 걸립니다.
관리자는 소리 지르고, 노동자는 땀 흘리고, 작업대는 정신없이 바뀝니다.
CPU 구조를 이해하고 나니 보이는 것들:
- 왜 지역 변수가 빠른지: 레지스터에 올라갈 확률이 높으니까.
- 왜 분기(if/else)가 비용인지: 파이프라인이 깨지고 FDE 사이클이 낭비되니까.
- 왜 캐시 친화적 코드가 중요한지: 메모리 접근이 레지스터보다 200배 느리니까.
내 코드가 느리다면, 혹시 이 공장을 너무 비효율적으로 돌리고 있는 건 아닌지 생각해 볼 필요가 있습니다.