컴퓨터 구조 개론
개요: 컴퓨터는 어떻게 생각할까?
컴퓨터는 생각하지 않는다. 단지 매우 빠르게 계산할 뿐이다. 하지만 초당 수십억 번의 계산을 하다 보니, 마치 생각하는 것처럼 보인다. 이번 파트에서는 컴퓨터가 어떻게 이런 놀라운 일을 해내는지, 그 내부 구조를 뜯어보자.
컴퓨터를 요리사에 비유해보자. CPU는 요리사, 메모리는 도마, 저장장치는 냉장고, 버스는 주방의 통로다. 요리사가 아무리 실력이 좋아도 도마가 작거나, 냉장고가 멀거나, 통로가 좁으면 요리 속도가 느려진다. 컴퓨터도 마찬가지다!
3.1 폰 노이만 구조 깊이 이해하기
3.1.1 CPU: 컴퓨터의 두뇌 해부하기
CPU(Central Processing Unit)는 컴퓨터의 두뇌다. 하지만 인간의 뇌와 달리, CPU는 한 번에 하나씩만 처리한다. 대신 엄청나게 빨리 처리해서 동시에 여러 일을 하는 것처럼 보인다.
그림 3.1: CPU 내부 구조 - ALU, 제어장치, 레지스터의 관계
CPU의 주요 구성요소:
- ALU (Arithmetic Logic Unit): 실제 계산을 하는 곳
- 산술 연산: 덧셈, 뺄셈, 곱셈, 나눗셈
- 논리 연산: AND, OR, NOT, XOR
- 비교 연산: 크다, 작다, 같다
- 제어장치 (Control Unit): 지휘자 역할
- 명령어 해석
- 다른 부품에 신호 전달
- 타이밍 조절
- 레지스터 (Register): CPU의 작은 주머니
- 범용 레지스터: 계산용 임시 저장
- PC (Program Counter): 다음 명령어 주소
- SP (Stack Pointer): 스택 위치
- 상태 레지스터: 계산 결과 상태 (0인지, 음수인지 등)
🔍 간단한 덧셈의 여정
3 + 5를 계산한다고 해보자:
- 메모리에서 3을 레지스터 A로 가져옴 (LOAD)
- 메모리에서 5를 레지스터 B로 가져옴 (LOAD)
- ALU가 A와 B를 더함 (ADD)
- 결과 8을 레지스터 C에 저장
- C의 값을 메모리로 보냄 (STORE)
이 5단계가 나노초(10억분의 1초) 단위로 일어난다!
3.1.2 메모리: 작업 공간의 비밀
메모리는 CPU의 작업 공간이다. 책상이 넓으면 여러 책을 펼쳐놓고 공부할 수 있듯이, 메모리가 크면 여러 프로그램을 동시에 실행할 수 있다.
메모리의 특징:
- 휘발성: 전원이 꺼지면 내용이 사라짐
- 빠른 접근: 어느 위치든 같은 속도로 접근 (Random Access)
- 바이트 단위 주소: 각 바이트마다 고유 주소 부여
| 메모리 주소 | 저장된 값 | 의미 |
|---|---|---|
| 0x0000 | 0x48 | 'H' (ASCII) |
| 0x0001 | 0x65 | 'e' (ASCII) |
| 0x0002 | 0x6C | 'l' (ASCII) |
| 0x0003 | 0x6C | 'l' (ASCII) |
| 0x0004 | 0x6F | 'o' (ASCII) |
메모리 주소는 보통 16진수로 표현한다. 0x는 16진수를 의미하고, 한 자리가 4비트를 나타내므로 두 자리(8비트 = 1바이트)씩 묶어서 표현한다.
3.1.3 버스: 정보의 고속도로
버스(Bus)는 CPU, 메모리, 입출력장치를 연결하는 통로다. 도로가 넓으면 많은 차가 다닐 수 있듯이, 버스가 넓으면 한 번에 많은 데이터를 전송할 수 있다.
3종류의 버스:
- 데이터 버스: 실제 데이터가 이동
- 너비: 8비트, 16비트, 32비트, 64비트
- 예: 64비트 버스 = 한 번에 8바이트 전송
- 주소 버스: 데이터의 위치 정보
- 너비가 메모리 최대 용량 결정
- 32비트 = 2^32 = 4GB까지 주소 지정
- 64비트 = 2^64 = 16EB까지 (실제로는 48비트만 사용)
- 제어 버스: 읽기/쓰기 신호
- 메모리 읽기/쓰기
- I/O 읽기/쓰기
- 인터럽트 신호
버스 비유: 택배 시스템
데이터 버스 = 택배 트럭 (물건 운반)
주소 버스 = 주소 정보 (어디로 갈지)
제어 버스 = 배송 지시 (받기/보내기)
트럭이 크면(버스 폭이 넓으면) 한 번에 많이 실을 수 있고, 주소 체계가 세밀하면(주소 버스가 넓으면) 더 많은 곳에 배달할 수 있다!
3.2 CPU는 어떻게 일할까?
3.2.1 명령어 사이클: Fetch-Decode-Execute
CPU는 단순한 일을 반복한다. 명령어를 가져오고(Fetch), 해석하고(Decode), 실행한다(Execute). 이것을 명령어 사이클이라 한다.
1. Fetch (인출): 메모리에서 명령어 가져오기
2. Decode (해독): 명령어가 무엇인지 해석
3. Execute (실행): 실제로 명령 수행
4. Store (저장): 결과를 메모리에 저장 (필요시)
// CPU 동작을 시뮬레이션하는 의사 코드 while (power_on) { // 1. Fetch: PC가 가리키는 명령어 가져오기 instruction = memory[PC]; PC = PC + 1; // 2. Decode: 명령어 해석 opcode = extract_opcode(instruction); operands = extract_operands(instruction); // 3. Execute: 명령어 종류에 따라 실행 switch (opcode) { case ADD: result = register[operands[0]] + register[operands[1]]; register[operands[2]] = result; break; case LOAD: register[operands[0]] = memory[operands[1]]; break; case STORE: memory[operands[1]] = register[operands[0]]; break; case JUMP: PC = operands[0]; break; } }
3.2.2 파이프라인: 조립 라인처럼 일하기
현대 CPU는 여러 명령어를 동시에 처리한다. 마치 세탁소에서 세탁-건조-다림질을 동시에 다른 옷에 대해 진행하는 것처럼!
그림 3.2: 5단계 파이프라인 - 동시에 5개 명령어 처리
| 시간 | Fetch | Decode | Execute | Memory | Write |
|---|---|---|---|---|---|
| 1 | 명령어1 | - | - | - | - |
| 2 | 명령어2 | 명령어1 | - | - | - |
| 3 | 명령어3 | 명령어2 | 명령어1 | - | - |
| 4 | 명령어4 | 명령어3 | 명령어2 | 명령어1 | - |
| 5 | 명령어5 | 명령어4 | 명령어3 | 명령어2 | 명령어1 |
파이프라인 덕분에 이론적으로 5배 빨라질 수 있다! 하지만 실제로는 여러 문제가 있다:
- 데이터 해저드: 이전 명령어 결과가 필요한 경우
- 제어 해저드: 분기(if문) 때문에 다음 명령어가 불확실
- 구조적 해저드: 같은 자원을 동시에 사용하려는 경우
3.2.3 캐시: CPU의 주머니
CPU는 빠른데 메모리는 느리다. 이 속도 차이를 극복하기 위해 캐시(Cache)를 사용한다. 자주 쓰는 물건을 주머니에 넣고 다니는 것처럼!
캐시 계층 구조:
| 레벨 | 크기 | 속도 | 위치 | 용도 |
|---|---|---|---|---|
| 레지스터 | 수십 바이트 | 1 사이클 | CPU 내부 | 즉시 사용할 데이터 |
| L1 캐시 | 32-64 KB | 3-4 사이클 | 각 코어 내부 | 자주 쓰는 명령어/데이터 |
| L2 캐시 | 256-512 KB | 10-20 사이클 | 각 코어 전용 | L1에서 못 찾은 것 |
| L3 캐시 | 8-32 MB | 20-40 사이클 | 모든 코어 공유 | L2에서 못 찾은 것 |
| 메인 메모리 | 8-32 GB | 100+ 사이클 | 메인보드 | 모든 데이터 |
📚 캐시를 도서관에 비유하면
레지스터 = 손에 든 책 (바로 읽기)
L1 캐시 = 책상 위 (팔만 뻗으면 됨)
L2 캐시 = 책장 (일어나서 가져오기)
L3 캐시 = 같은 층 서가 (걸어가기)
메모리 = 다른 층 서고 (엘리베이터 타기)
디스크 = 외부 창고 (차 타고 가기)
캐시의 작동 원리:
- 시간 지역성: 최근 사용한 데이터는 또 사용할 확률이 높다
- 공간 지역성: 근처 데이터도 곧 사용할 확률이 높다
// 캐시 친화적인 코드 vs 비친화적인 코드 // 나쁜 예: 캐시 미스 많음 (열 우선 접근) for (j = 0; j < 1000; j++) { for (i = 0; i < 1000; i++) { sum += array[i][j]; // 메모리 점프! } } // 좋은 예: 캐시 히트 많음 (행 우선 접근) for (i = 0; i < 1000; i++) { for (j = 0; j < 1000; j++) { sum += array[i][j]; // 연속된 메모리! } }
3.3 최신 프로세서 이야기
3.3.1 Intel vs AMD vs Apple Silicon
CPU 시장은 오랫동안 Intel이 지배했지만, 최근 AMD와 Apple이 혁신적인 제품으로 판도를 바꾸고 있다.
| 제조사 | 최신 제품 | 특징 | 장단점 |
|---|---|---|---|
| Intel | Core i9-14900K | • P코어 + E코어 • 최대 6GHz • x86-64 |
✓ 높은 클럭 ✓ 게임 성능 ✗ 전력 소비 |
| AMD | Ryzen 9 7950X | • 칩렛 설계 • 3D V-Cache • x86-64 |
✓ 멀티코어 성능 ✓ 가성비 ✗ 싱글코어 |
| Apple | M3 Max | • ARM 기반 • 통합 메모리 • SoC 설계 |
✓ 전력 효율 ✓ 통합 성능 ✗ 호환성 |
Apple Silicon의 혁신:
- 통합 메모리 아키텍처: CPU, GPU가 메모리 공유
- 전용 가속기: Neural Engine, ProRes 코덱 등
- 효율 코어 + 성능 코어: 작업에 따라 선택 사용
- 5nm 공정: 더 작고 효율적인 트랜지스터
3.3.2 코어가 많으면 정말 빠를까?
"코어가 2배면 속도도 2배?" 안타깝게도 그렇지 않다. 암달의 법칙(Amdahl's Law)이 이를 설명한다.
프로그램의 일부만 병렬화 가능하다면, 코어를 아무리 늘려도 속도 향상에는 한계가 있다.
예: 프로그램의 50%만 병렬화 가능하다면,
• 2코어: 1.33배 빨라짐
• 4코어: 1.6배 빨라짐
• 무한 코어: 최대 2배까지만 빨라짐
멀티코어가 유리한 작업:
- 동영상 인코딩 (각 프레임 독립 처리)
- 3D 렌더링 (픽셀별 독립 계산)
- 과학 시뮬레이션 (영역 분할)
- 웹 서버 (요청별 독립 처리)
멀티코어가 불리한 작업:
- 게임 (순차적 로직이 많음)
- 오피스 작업 (단일 스레드 위주)
- 웹 브라우징 (UI는 단일 스레드)
3.3.3 AI 칩은 뭐가 다를까?
AI 계산은 주로 행렬 곱셈이다. CPU는 범용적이라 행렬 곱셈이 비효율적이다. 그래서 전용 칩이 나왔다.
CPU vs GPU vs TPU
| 특징 | CPU | GPU | TPU/NPU |
|---|---|---|---|
| 설계 목적 | 범용 계산 | 그래픽/병렬 | AI 전용 |
| 코어 수 | 4-64개 | 수천 개 | 수만 개 |
| 코어 복잡도 | 매우 복잡 | 단순 | 특화 |
| 적합한 작업 | 순차 처리 | 병렬 처리 | 행렬 연산 |
// 행렬 곱셈: CPU vs GPU 차이 // CPU 방식: 순차적으로 하나씩 for (i = 0; i < N; i++) { for (j = 0; j < N; j++) { for (k = 0; k < N; k++) { C[i][j] += A[i][k] * B[k][j]; } } } // 시간 복잡도: O(N³) // GPU 방식: 모든 (i,j)를 동시에 // 각 스레드가 C[i][j] 하나씩 계산 __global__ void matrixMul(A, B, C) { i = threadIdx.x; j = threadIdx.y; for (k = 0; k < N; k++) { C[i][j] += A[i][k] * B[k][j]; } } // N²개 스레드가 동시 실행!
AI 칩의 특별한 기능들:
- Tensor Core: 4x4 행렬을 한 번에 곱셈
- Mixed Precision: FP16으로 계산, FP32로 누적
- Sparsity: 0이 많은 행렬 최적화
- 메모리 대역폭: HBM으로 초고속 데이터 전송
3.4 프로그래머가 꼭 알아야 할 하드웨어 지식
3.4.1 메모리 정렬과 패딩
CPU는 메모리를 특정 단위로 읽는 것을 좋아한다. 4바이트 정수는 4의 배수 주소에, 8바이트 실수는 8의 배수 주소에 있을 때 가장 빠르게 읽을 수 있다.
// 구조체 패딩 예제 struct BadLayout { char a; // 1바이트 int b; // 4바이트 (3바이트 패딩 추가됨!) char c; // 1바이트 double d; // 8바이트 (7바이트 패딩 추가됨!) }; // 총 크기: 24바이트 (10바이트 낭비) struct GoodLayout { double d; // 8바이트 int b; // 4바이트 char a; // 1바이트 char c; // 1바이트 // 2바이트 패딩 }; // 총 크기: 16바이트 (2바이트만 낭비)
3.4.2 분기 예측과 최적화
현대 CPU는 if문의 결과를 예측한다. 예측이 맞으면 빠르고, 틀리면 파이프라인을 비워야 해서 느려진다.
// 분기 예측 실험 // 느린 코드: 예측 불가능한 패턴 for (i = 0; i < N; i++) { if (random_array[i] > 128) { // 50% 확률, 예측 어려움 sum += random_array[i]; } } // 빠른 코드: 예측 가능한 패턴 sort(sorted_array); // 먼저 정렬! for (i = 0; i < N; i++) { if (sorted_array[i] > 128) { // 앞은 false, 뒤는 true sum += sorted_array[i]; } } // 정렬된 배열이 2-6배 빠를 수 있다!
3.4.3 SIMD: 한 번에 여러 개 계산하기
SIMD(Single Instruction Multiple Data)는 하나의 명령으로 여러 데이터를 동시에 처리한다. 벡터 연산에 매우 효과적이다.
// SIMD를 활용한 벡터 덧셈 // 일반 방식: 하나씩 더하기 for (i = 0; i < 1000; i++) { c[i] = a[i] + b[i]; } // SIMD 방식: 4개씩 한번에 (SSE 사용) for (i = 0; i < 1000; i += 4) { __m128 va = _mm_load_ps(&a[i]); // 4개 float 로드 __m128 vb = _mm_load_ps(&b[i]); // 4개 float 로드 __m128 vc = _mm_add_ps(va, vb); // 4개 동시 덧셈! _mm_store_ps(&c[i], vc); // 4개 float 저장 } // 이론상 4배 빠름!
정리
컴퓨터 구조를 이해하는 것은 단순히 하드웨어를 아는 것이 아니다. 효율적인 프로그램을 작성하고, 성능 문제를 해결하고, 새로운 기술을 활용하는 데 필수적인 지식이다.
핵심 요점:
- 폰 노이만 구조는 여전히 현대 컴퓨터의 기본
- CPU는 단순한 일을 매우 빠르게 반복
- 메모리 계층을 이해하면 빠른 프로그램을 만들 수 있음
- 파이프라인과 캐시가 성능의 핵심
- 멀티코어는 만능이 아님 (암달의 법칙)
- AI 가속기는 특정 연산에 특화
- 하드웨어 특성을 고려한 프로그래밍이 중요
'강의 > 컴퓨터 구조' 카테고리의 다른 글
| 데이터의 표현 - Part2 (0) | 2025.09.19 |
|---|---|
| 데이터의 표현 - Part1 (0) | 2025.09.19 |
| 컴퓨터 구조 개론 - Part 2 (0) | 2025.09.12 |
| 컴퓨터 구조 개론 - Part 1 (0) | 2025.09.12 |