- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며
- 1. 컴퓨터 구조 개요
- 2. 명령어 집합 구조 (ISA)
- 3. ALU와 데이터패스
- 4. 파이프라이닝 (Pipelining)
- 5. 메모리 계층 구조
- 6. 가상 메모리
- 7. 입출력 시스템
- 8. 병렬 아키텍처
- 9. GPU 아키텍처
- 10. 최신 아키텍처 트렌드
- 11. 성능 최적화 실전
- 12. 퀴즈
- 참고 자료
들어가며
컴퓨터 구조(Computer Architecture)는 하드웨어와 소프트웨어의 경계를 이해하는 핵심 학문입니다. 전자/컴퓨터공학 전공자라면 반드시 깊이 이해해야 하는 분야이며, 고성능 소프트웨어 개발자, 시스템 프로그래머, 반도체 설계자 모두에게 필수 지식입니다.
이 가이드는 Patterson & Hennessy의 Computer Organization and Design과 Computer Architecture: A Quantitative Approach를 기반으로, ISA 설계부터 현대 GPU 병렬 아키텍처까지 체계적으로 다룹니다.
1. 컴퓨터 구조 개요
폰 노이만 아키텍처 (Von Neumann Architecture)
현대 컴퓨터의 기반인 폰 노이만 아키텍처는 1945년 John von Neumann이 제안했습니다. 핵심 특징은 프로그램과 데이터를 같은 메모리 공간에 저장한다는 것입니다.
구성 요소:
- CPU (Central Processing Unit): ALU + 제어 유닛 + 레지스터
- 메모리: 프로그램 코드 + 데이터 저장
- 입출력 장치: 키보드, 디스플레이, 디스크 등
- 버스: 데이터/주소/제어 신호 전달
폰 노이만 병목(Von Neumann Bottleneck): CPU와 메모리 사이의 버스 대역폭이 성능을 제한합니다. 현대 캐시 계층 구조는 이 문제를 완화하기 위해 설계되었습니다.
하버드 아키텍처 (Harvard Architecture)
하버드 아키텍처는 명령어 메모리와 데이터 메모리를 분리하여 동시 접근을 허용합니다. DSP, 마이크로컨트롤러(AVR, PIC), 그리고 현대 CPU의 L1 캐시(I-Cache/D-Cache 분리)에 사용됩니다.
| 구분 | 폰 노이만 | 하버드 |
|---|---|---|
| 메모리 | 통합 | 분리 |
| 대역폭 | 제한적 | 높음 |
| 복잡도 | 낮음 | 높음 |
| 사용처 | 범용 CPU | DSP, 마이크로컨트롤러 |
컴퓨터 추상화 계층
컴퓨터 시스템은 여러 추상화 계층으로 구성됩니다:
응용 프로그램 (Application)
↓
운영체제 (Operating System)
↓
ISA (Instruction Set Architecture) ← 하드웨어/소프트웨어 경계
↓
마이크로아키텍처 (Microarchitecture)
↓
논리 게이트 (Logic Gates)
↓
트랜지스터 (Transistors)
ISA는 하드웨어와 소프트웨어의 계약(contract)입니다. 같은 ISA를 구현하는 프로세서라면 어떤 마이크로아키텍처를 사용하든 동일한 소프트웨어가 동작합니다.
성능 측정 지표
CPU 실행 시간 공식:
CPU Time = Instruction Count × CPI × Clock Cycle Time
= Instruction Count × CPI / Clock Rate
- CPI (Cycles Per Instruction): 명령어 1개 실행에 걸리는 평균 클럭 사이클 수
- Clock Rate: Hz 단위, 초당 클럭 사이클 수 (예: 3.5 GHz)
- MIPS (Millions of Instructions Per Second): 초당 실행 명령어 수 (백만 단위)
- FLOPS (Floating Point Operations Per Second): 부동소수점 연산 성능
Amdahl의 법칙 (성능 개선 한계):
Speedup = 1 / ((1 - f) + f/s)
여기서 f는 병렬화 가능한 비율, s는 병렬화된 부분의 속도 향상 배율입니다. 전체의 40%만 병렬화 가능하다면, 아무리 많은 코어를 사용해도 최대 1.67배 속도 향상이 한계입니다.
2. 명령어 집합 구조 (ISA)
RISC vs CISC
RISC (Reduced Instruction Set Computer):
- 단순하고 고정 길이 명령어
- 레지스터 중심 연산 (Load/Store 아키텍처)
- 하드웨어 파이프라이닝에 유리
- 대표: ARM, RISC-V, MIPS, PowerPC
CISC (Complex Instruction Set Computer):
- 복잡하고 가변 길이 명령어
- 메모리 직접 연산 가능
- 코드 밀도 높음 (적은 명령어로 복잡한 작업)
- 대표: x86-64, VAX
현대 x86-64 프로세서는 내부적으로 RISC 마이크로연산(micro-ops)으로 변환하여 실행하므로, 실제 경계는 모호해졌습니다.
명령어 형식 (RISC-V 기준)
RISC-V는 6가지 명령어 형식을 사용합니다:
R-형식 (레지스터 연산):
[funct7|rs2|rs1|funct3|rd|opcode]
7비트 5비트 5비트 3비트 5비트 7비트
I-형식 (즉시값/로드):
[imm[11:0]|rs1|funct3|rd|opcode]
12비트 5비트 3비트 5비트 7비트
S-형식 (스토어):
[imm[11:5]|rs2|rs1|funct3|imm[4:0]|opcode]
B-형식 (분기):
[imm[12|10:5]|rs2|rs1|funct3|imm[4:1|11]|opcode]
U-형식 (상위 즉시값):
[imm[31:12]|rd|opcode]
J-형식 (JAL):
[imm[20|10:1|11|19:12]|rd|opcode]
RISC-V ISA 예제 코드
RISC-V는 UC Berkeley에서 개발한 오픈소스 ISA로, 교육 및 산업 모두에서 급성장 중입니다.
# RISC-V 어셈블리 예제
# 기본 산술 연산
add a0, a0, a1 # a0 = a0 + a1
sub t0, t1, t2 # t0 = t1 - t2
addi t0, t0, 10 # t0 = t0 + 10 (즉시값)
mul a0, a1, a2 # a0 = a1 * a2
# 메모리 접근 (Load/Store)
lw t0, 0(a0) # t0 = Memory[a0 + 0] (워드 로드)
lh t1, 2(a0) # t1 = Memory[a0 + 2] (하프워드 로드)
lb t2, 1(a0) # t2 = Memory[a0 + 1] (바이트 로드)
sw t0, 4(a0) # Memory[a0 + 4] = t0 (워드 스토어)
# 논리 연산
and t0, t1, t2 # t0 = t1 & t2
or t0, t1, t2 # t0 = t1 | t2
xor t0, t1, t2 # t0 = t1 ^ t2
sll t0, t1, t2 # t0 = t1 << t2 (논리 왼쪽 시프트)
srl t0, t1, t2 # t0 = t1 >> t2 (논리 오른쪽 시프트)
# 분기 및 점프
beq t0, t1, label # if t0 == t1, jump to label
bne t0, t1, label # if t0 != t1, jump to label
blt t0, t1, label # if t0 < t1, jump to label
bge t0, t1, label # if t0 >= t1, jump to label
jal ra, func # ra = PC+4; jump to func
jalr ra, 0(t0) # ra = PC+4; jump to t0+0
# RISC-V 레지스터 규약
# x0 (zero): 항상 0
# x1 (ra): 반환 주소 (Return Address)
# x2 (sp): 스택 포인터 (Stack Pointer)
# x10-x17 (a0-a7): 함수 인자/반환값
# x5-x7, x28-x31 (t0-t6): 임시 레지스터
# x8-x9, x18-x27 (s0-s11): 저장 레지스터
C 함수를 RISC-V 어셈블리로 변환
// C 코드
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
# RISC-V 어셈블리 (재귀 팩토리얼)
factorial:
addi sp, sp, -16 # 스택 프레임 할당
sw ra, 12(sp) # 반환 주소 저장
sw a0, 8(sp) # n 저장
addi t0, zero, 1
bgt a0, t0, recurse # n > 1이면 재귀
addi a0, zero, 1 # return 1
j done
recurse:
addi a0, a0, -1 # n - 1
jal ra, factorial # 재귀 호출
lw t0, 8(sp) # n 복원
mul a0, t0, a0 # n * factorial(n-1)
done:
lw ra, 12(sp) # 반환 주소 복원
addi sp, sp, 16 # 스택 프레임 해제
jalr zero, 0(ra) # 반환
3. ALU와 데이터패스
ALU 설계
ALU(Arithmetic Logic Unit)는 CPU의 핵심 연산 장치입니다. 기본 연산:
ADD: 결과 = A + B
SUB: 결과 = A + (~B + 1) = A - B (2의 보수)
AND: 결과 = A AND B
OR: 결과 = A OR B
XOR: 결과 = A XOR B
SLT: 결과 = (A < B) ? 1 : 0 (Set Less Than)
SLL: 결과 = A << B (시프트)
리플 캐리 가산기 (Ripple Carry Adder):
FA0: S0 = A0 XOR B0 XOR Cin; C1 = (A0 AND B0) OR ...
FA1: S1 = A1 XOR B1 XOR C1; C2 = ...
...
FAn: Sn = An XOR Bn XOR Cn; Cout = ...
N비트 리플 캐리 가산기의 지연: O(N). 64비트 가산에 64단계 게이트 지연 발생.
선행 올림 가산기 (Carry Lookahead Adder, CLA):
생성(Generate)과 전파(Propagate)를 정의합니다:
Gi = Ai AND Bi (올림 생성)
Pi = Ai XOR Bi (올림 전파)
Ci+1 = Gi OR (Pi AND Ci)
4비트 그룹 단위로 올림을 병렬 계산하면 O(log N) 지연으로 줄어듭니다.
단일 사이클 데이터패스
단일 사이클(Single-cycle) 구현에서는 모든 명령어가 한 클럭 사이클에 실행됩니다.
명령어 흐름 (R-형식 ADD):
1. IF: PC → 명령어 메모리 → 명령어 인출
2. ID: 레지스터 파일에서 rs1, rs2 읽기 + 제어 신호 생성
3. EX: ALU에서 rs1 + rs2 계산
4. MEM: (R-형식은 메모리 접근 없음)
5. WB: ALU 결과를 rd에 기록
단일 사이클의 문제: 가장 느린 명령어(예: 메모리 접근)가 클럭 주기를 결정 → 빠른 명령어도 느린 클럭에 맞춰야 함.
4. 파이프라이닝 (Pipelining)
파이프라이닝은 세탁기-건조기-다리미를 동시에 사용하는 것처럼, 여러 명령어의 서로 다른 단계를 겹쳐 실행하는 기법입니다.
5단계 파이프라인
단계 약어 역할
------+-----+----------------------------------------
인출 IF PC로부터 명령어를 메모리에서 가져옴
해독 ID 명령어 해독, 레지스터 읽기, 제어 신호 생성
실행 EX ALU 연산 수행
메모리 MEM 데이터 메모리 읽기/쓰기
기록 WB 레지스터 파일에 결과 저장
타이밍 다이어그램:
사이클: 1 2 3 4 5 6 7 8
Inst 1: IF ID EX MEM WB
Inst 2: IF ID EX MEM WB
Inst 3: IF ID EX MEM WB
Inst 4: IF ID EX MEM WB
이상적으로 5단계 파이프라인은 단일 사이클 대비 최대 5배 처리량 향상을 제공합니다.
파이프라인 해저드 (Pipeline Hazards)
1. 구조적 해저드 (Structural Hazard)
동일한 하드웨어 자원을 두 명령어가 동시에 사용하려 할 때 발생합니다.
- 해결: 자원 분리(I-Cache와 D-Cache 분리), 레지스터 포트 추가
2. 데이터 해저드 (Data Hazard)
이전 명령어의 결과가 준비되기 전에 다음 명령어가 그 값을 필요로 할 때 발생합니다.
add t0, t1, t2 # t0 쓰기 (WB: 5사이클)
sub t3, t0, t4 # t0 읽기 (ID: 3사이클) → RAW 해저드!
종류:
- RAW (Read After Write): 가장 흔함. 이전 명령어가 쓰기 전에 읽기 시도.
- WAR (Write After Read): 순서 바뀐 실행(OoO)에서 발생.
- WAW (Write After Write): 두 명령어가 같은 레지스터에 쓸 때.
해결 방법:
- 전방전달 (Forwarding/Bypassing): EX/MEM 단계 결과를 바로 다음 EX 단계 입력으로 전달.
- 스톨 (Stall/Bubble): 파이프라인을 멈추고 NOP(No Operation) 삽입. 성능 저하.
- 코드 재배치: 컴파일러가 독립적인 명령어를 사이에 삽입.
# 로드-유스 해저드 (1 사이클 스톨 불가피)
lw t0, 0(a0) # 메모리에서 로드
add t1, t0, t2 # t0 바로 사용 → MEM→EX 전방전달 불가
# 해결: 컴파일러가 독립 명령어 삽입
lw t0, 0(a0)
add t3, t4, t5 # 독립 명령어 삽입
add t1, t0, t2 # 이제 t0 준비됨
3. 제어 해저드 (Control Hazard)
분기(branch) 명령어로 인해 다음에 실행할 명령어가 불확실할 때 발생합니다.
beq t0, t1, label # 분기 여부 EX 단계에서 결정
# 그 동안 IF, ID에서 인출된 명령어들은?
해결 방법:
- 플러시 (Flush): 잘못 인출된 명령어 버리기 (2-3 사이클 페널티)
- 분기 예측 (Branch Prediction):
- 정적 예측: 항상 Not-Taken 또는 항상 Taken
- 동적 예측: 1비트/2비트 예측기, BTB(Branch Target Buffer)
- 현대 CPU는 95%+ 예측 정확도
- 지연 분기 (Delayed Branch): 분기 명령어 직후 슬롯에 항상 유용한 명령어 배치 (MIPS)
슈퍼스칼라 파이프라인
현대 CPU는 여러 파이프라인을 병렬로 운영합니다:
Intel Core: 6-way 비순서 실행 (Out-of-Order Execution)
AMD Zen 4: 4-way 디코드 + OoO 실행
ARM Cortex-X4: 5-way 디코드
비순서 실행 (Out-of-Order Execution):
- 명령어를 순서대로 인출/해독
- 준비된 명령어부터 실행 (토마술로 알고리즘)
- 결과는 순서대로 기록 (Reorder Buffer 사용)
5. 메모리 계층 구조
메모리 계층
레지스터 (Register)
용량: ~1KB | 속도: 1 사이클 | 비용: 매우 높음
L1 캐시 (On-chip)
용량: 32-64KB | 속도: 4-5 사이클 | 비용: 높음
L2 캐시 (On-chip)
용량: 256KB-1MB | 속도: 12-15 사이클 | 비용: 중간
L3 캐시 (On-chip, 공유)
용량: 8-64MB | 속도: 30-40 사이클 | 비용: 낮음
DRAM (Main Memory)
용량: 8-256GB | 속도: 200-300 사이클 | 비용: 매우 낮음
SSD/NVMe
용량: 1-4TB | 속도: 10,000+ 사이클 | 비용: 아주 낮음
지역성 원리 (Principle of Locality)
- 시간적 지역성 (Temporal Locality): 최근 접근한 데이터는 곧 다시 접근될 가능성이 높다. (루프 변수, 카운터)
- 공간적 지역성 (Spatial Locality): 접근한 데이터 근처의 데이터도 곧 접근될 가능성이 높다. (배열 순차 접근)
// 공간적 지역성 최적화 예시
// 나쁜 예: 열 우선 접근 (캐시 미스 빈번)
for (int j = 0; j < N; j++)
for (int i = 0; i < N; i++)
sum += A[i][j]; // A[0][0], A[1][0], A[2][0]... (행 건너뜀)
// 좋은 예: 행 우선 접근 (캐시 친화적)
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
sum += A[i][j]; // A[0][0], A[0][1], A[0][2]... (연속 메모리)
캐시 구조
Direct-mapped 캐시: 메모리의 각 블록이 캐시의 딱 한 곳에만 저장될 수 있습니다.
- 장점: 구현 간단, 빠른 접근
- 단점: 충돌 미스(Conflict Miss) 발생 가능
N-way Set-associative 캐시: 메모리 블록이 N개 슬롯 중 하나에 저장됩니다.
- 실제 CPU에서 가장 많이 사용 (L1: 4-way, L2: 8-way, L3: 16-way)
Fully-associative 캐시: 메모리 블록이 어느 슬롯에나 저장 가능.
- 장점: 충돌 미스 없음
- 단점: 탐색 비용 큼 (TLB에 사용)
캐시 주소 분해 (32비트 주소, 4KB 캐시, 64B 블록, 4-way):
[태그(20비트) | 인덱스(6비트) | 오프셋(6비트)]
캐시 교체 정책
- LRU (Least Recently Used): 가장 오래 사용되지 않은 블록 교체. 성능 최고, 구현 복잡.
- FIFO (First In First Out): 가장 먼저 들어온 블록 교체. 구현 간단.
- Random: 무작위 교체. 하드웨어 구현 간단, LRU와 유사한 성능.
Write 정책
- Write-through: 캐시와 메모리를 동시에 갱신. 일관성 보장, 대역폭 소비.
- Write-back: 캐시만 갱신, 교체 시 메모리 기록(Dirty bit 사용). 성능 좋음, 일관성 복잡.
6. 가상 메모리
개요
가상 메모리는 각 프로세스에 독립적인 주소 공간을 제공하여 보호, 격리, 메모리 오버커밋을 가능하게 합니다.
가상 주소 (VA): 프로세스가 사용하는 주소 (0x0000 ~ 0xFFFFFFFF)
물리 주소 (PA): 실제 DRAM의 주소
페이지: 가상/물리 메모리의 고정 크기 블록 (보통 4KB)
페이지 테이블 변환
가상 주소 [VPN | Page Offset]
↓
페이지 테이블 조회
↓
물리 주소 [PFN | Page Offset]
64비트 시스템에서는 4단계 페이지 테이블을 사용합니다 (x86-64):
[PML4(9비트) | PDPT(9비트) | PD(9비트) | PT(9비트) | Offset(12비트)]
TLB (Translation Lookaside Buffer)
페이지 테이블 조회는 메모리 접근을 추가로 필요로 합니다. TLB는 최근 변환 결과를 캐싱하는 완전 연관 캐시입니다.
TLB Hit: 가상 주소 → TLB 조회 → 물리 주소 (1-2 사이클)
TLB Miss: 가상 주소 → 페이지 테이블 워크 → 물리 주소 (100+ 사이클)
TLB 크기: 보통 64-1024 엔트리. 히트율 99%+ 유지.
페이지 교체 알고리즘
메모리가 가득 찼을 때 어떤 페이지를 디스크로 내보낼지 결정합니다.
- Optimal: 미래에 가장 오랫동안 사용되지 않을 페이지 교체 (이론적 최적, 실제 불가)
- LRU: 가장 오래 사용되지 않은 페이지 교체 (성능 좋음, 구현 비쌈)
- Clock (FIFO + 기회): 실제 OS에서 많이 사용하는 LRU 근사
7. 입출력 시스템
I/O 제어 방법
폴링 (Polling): CPU가 장치 상태를 주기적으로 확인합니다.
- 장점: 구현 간단, 낮은 지연
- 단점: CPU 시간 낭비 (Busy-wait)
인터럽트 (Interrupt): 장치가 준비되면 CPU에 신호를 보냅니다.
- 장점: CPU가 다른 작업 수행 가능
- 단점: 인터럽트 처리 오버헤드
DMA (Direct Memory Access): DMA 컨트롤러가 CPU 없이 직접 메모리-장치 간 데이터 전송을 수행합니다.
- 대량 데이터 전송에 필수 (디스크, 네트워크, GPU)
- CPU는 전송 시작/완료만 처리
버스 아키텍처
PCIe 5.0: x16 슬롯 = 128 GB/s 양방향
NVMe (PCIe):최대 7 GB/s 순차 읽기 (Gen4)
USB 4.0: 최대 40 Gbps
DDR5-6400: 최대 51.2 GB/s (채널당)
8. 병렬 아키텍처
Flynn의 분류
| 분류 | 명령어 스트림 | 데이터 스트림 | 예시 |
|---|---|---|---|
| SISD | 단일 | 단일 | 단일 코어 CPU |
| SIMD | 단일 | 다중 | GPU, AVX 벡터 연산 |
| MISD | 다중 | 단일 | 결함 허용 시스템 |
| MIMD | 다중 | 다중 | 멀티코어 CPU, 클러스터 |
멀티코어와 캐시 일관성
여러 코어가 같은 데이터를 캐시에 갖고 있을 때 일관성을 유지해야 합니다.
MESI 프로토콜 (Modified, Exclusive, Shared, Invalid):
Modified (M): 이 코어만 수정된 최신 복사본 보유
Exclusive (E): 이 코어만 보유, 메모리와 동일
Shared (S): 여러 코어가 읽기 전용 복사본 보유
Invalid (I): 유효하지 않음 (다른 코어가 수정함)
상태 전환:
- 코어 A가 S 상태 데이터 쓰기 → A는 M으로 전환, 다른 코어는 I로 무효화
- 코어 B가 I 상태 데이터 읽기 → 버스 스누핑으로 A에서 전달
OpenMP 병렬 프로그래밍
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
// 병렬 배열 합산
int main() {
int n = 1000000;
long long sum = 0;
int *arr = malloc(n * sizeof(int));
for (int i = 0; i < n; i++)
arr[i] = i + 1;
// reduction으로 안전한 병렬 합산
#pragma omp parallel for reduction(+:sum) schedule(static)
for (int i = 0; i < n; i++) {
sum += arr[i];
}
printf("Sum = %lld\n", sum); // 500000500000
// 병렬 for + 작업 분배
#pragma omp parallel
{
int tid = omp_get_thread_num();
int nthreads = omp_get_num_threads();
printf("Thread %d of %d\n", tid, nthreads);
}
free(arr);
return 0;
}
// 행렬 곱셈 병렬화 (캐시 친화적 + OpenMP)
void matmul(float *A, float *B, float *C, int N) {
#pragma omp parallel for collapse(2) schedule(dynamic, 64)
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
float sum = 0.0f;
for (int k = 0; k < N; k++) {
sum += A[i*N + k] * B[k*N + j];
}
C[i*N + j] = sum;
}
}
}
NUMA 아키텍처
NUMA(Non-Uniform Memory Access)는 각 CPU 소켓이 로컬 메모리를 갖는 구조입니다.
소켓 0 (Core 0-15) ←→ Local DRAM (64GB) [~80ns]
↕ QPI/Infinity Fabric
소켓 1 (Core 16-31) ←→ Local DRAM (64GB) [~80ns]
소켓 0에서 소켓 1의 메모리 접근: ~160ns (2배 지연)
numactl을 사용하면 프로세스를 특정 NUMA 노드에 고정할 수 있습니다.
9. GPU 아키텍처
GPU vs CPU 설계 철학
| 항목 | CPU | GPU |
|---|---|---|
| 코어 수 | 수십 개 | 수천-수만 개 |
| 코어 복잡도 | 매우 복잡 (OoO, 분기예측) | 단순 |
| 캐시 크기 | 크고 복잡 | 작고 단순 |
| 설계 목표 | 단일 스레드 지연 최소화 | 처리량(Throughput) 최대화 |
| 용도 | 범용 직렬 연산 | 대규모 병렬 연산 |
SIMT 실행 모델
SIMT(Single Instruction, Multiple Thread)는 GPU의 핵심 실행 모델입니다.
워프(Warp): 32개 스레드의 묶음 (NVIDIA)
→ 32개 스레드가 동일한 명령어를 동시에 실행
→ 각 스레드는 서로 다른 데이터에 작용
워프 스케줄러: 지연 발생 시(메모리 접근) 즉시 다른 워프로 전환
→ 수천 개 워프를 빠르게 전환하여 지연을 숨김 (Latency Hiding)
NVIDIA GPU 계층 구조
GPU
├── GPC (Graphics Processing Cluster) x 8
│ └── SM (Streaming Multiprocessor) x 7-12
│ ├── CUDA Core x 128 (FP32 연산)
│ ├── Tensor Core x 4 (행렬 연산, AI)
│ ├── RT Core x 1 (레이 트레이싱)
│ ├── Warp Scheduler x 4
│ ├── Register File (256KB)
│ └── Shared Memory / L1 Cache (128-256KB)
└── L2 Cache (공유, 수십 MB)
NVIDIA H100 (Hopper, 2022):
- 132개 SM, 16,896개 CUDA Core
- 528개 Tensor Core (4세대, FP8 지원)
- 80GB HBM3 메모리, 3.35 TB/s 대역폭
- 4 PetaFLOPS FP8 Tensor Core 성능
CUDA 프로그래밍 기초
#include <cuda_runtime.h>
#include <stdio.h>
// GPU 커널: 벡터 덧셈
__global__ void vectorAdd(float *a, float *b, float *c, int n) {
// 스레드 글로벌 인덱스 계산
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
c[idx] = a[idx] + b[idx];
}
}
int main() {
int n = 1 << 20; // 1M 원소
size_t size = n * sizeof(float);
// 호스트(CPU) 메모리
float *h_a = (float*)malloc(size);
float *h_b = (float*)malloc(size);
float *h_c = (float*)malloc(size);
// 디바이스(GPU) 메모리
float *d_a, *d_b, *d_c;
cudaMalloc(&d_a, size);
cudaMalloc(&d_b, size);
cudaMalloc(&d_c, size);
// 호스트 → 디바이스 복사
cudaMemcpy(d_a, h_a, size, cudaMemcpyHostToDevice);
cudaMemcpy(d_b, h_b, size, cudaMemcpyHostToDevice);
// 커널 실행: 블록당 256 스레드
int threadsPerBlock = 256;
int blocksPerGrid = (n + threadsPerBlock - 1) / threadsPerBlock;
vectorAdd<<<blocksPerGrid, threadsPerBlock>>>(d_a, d_b, d_c, n);
// 디바이스 → 호스트 복사
cudaMemcpy(h_c, d_c, size, cudaMemcpyDeviceToHost);
cudaFree(d_a); cudaFree(d_b); cudaFree(d_c);
free(h_a); free(h_b); free(h_c);
return 0;
}
GPU 메모리 최적화
// 공유 메모리를 활용한 행렬 곱 최적화
#define TILE_SIZE 16
__global__ void matmulShared(float *A, float *B, float *C, int N) {
__shared__ float tileA[TILE_SIZE][TILE_SIZE];
__shared__ float tileB[TILE_SIZE][TILE_SIZE];
int row = blockIdx.y * TILE_SIZE + threadIdx.y;
int col = blockIdx.x * TILE_SIZE + threadIdx.x;
float sum = 0.0f;
for (int t = 0; t < N / TILE_SIZE; t++) {
// 타일을 공유 메모리로 로드
tileA[threadIdx.y][threadIdx.x] = A[row * N + t * TILE_SIZE + threadIdx.x];
tileB[threadIdx.y][threadIdx.x] = B[(t * TILE_SIZE + threadIdx.y) * N + col];
__syncthreads(); // 모든 스레드 로드 완료 대기
for (int k = 0; k < TILE_SIZE; k++)
sum += tileA[threadIdx.y][k] * tileB[k][threadIdx.x];
__syncthreads();
}
if (row < N && col < N)
C[row * N + col] = sum;
}
Tensor Core와 AI 가속
Tensor Core는 행렬 곱셈-누산(MMA: Matrix Multiply-Accumulate)을 하드웨어에서 가속합니다.
일반 CUDA Core: FP32 MAC 1개/사이클
Tensor Core(4세대): 16x16x16 행렬 곱 = 4096 FP16 MAC/사이클
NVIDIA cuBLAS, cuDNN, TensorRT는 Tensor Core를 자동으로 활용합니다.
10. 최신 아키텍처 트렌드
칩렛(Chiplet) 설계
단일 대형 다이(monolithic die) 대신 여러 작은 칩렛을 인터포저로 연결합니다.
- AMD EPYC (Genoa): 12개 CCD(Core Complex Die, 5nm) + 1개 IOD(I/O Die, 6nm)
- Intel Meteor Lake: CPU + GPU + SoC 타일 분리
- 장점: 수율 향상, 공정 최적화, 비용 절감
- 기술: TSMC CoWoS, Intel EMIB, UCIe 표준
HBM (High Bandwidth Memory)
DRAM을 3D로 적층하여 초고대역폭을 달성합니다.
HBM3E (2024): 9.6 Gbps/핀, 8-스택 = 1.2 TB/s (패키지당)
GDDR6X (RTX 4090): 21 Gbps/핀, 384비트 = 1 TB/s
DDR5-6400: 51.2 GB/s (채널당, CPU 기준)
NPU/TPU: AI 전용 가속기
- Google TPU v5p: 459 TFLOPS BF16, Mesh 연결
- Apple Neural Engine: iPhone 15 Pro, 35 TOPS INT8
- Qualcomm Hexagon: 스마트폰 NPU, 75 TOPS
- Intel Gaudi 3: 1835 TFLOPS BF16
RISC-V의 부상
오픈소스 ISA로 저전력 임베디드부터 데이터센터 서버까지 확장 중:
- SiFive, StarFive, Alibaba T-Head
- RISC-V International: 3,000+ 회원사
- Linux 5.19 공식 지원, Android 포팅 완료
11. 성능 최적화 실전
캐시 친화적 프로그래밍
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#define N 4096
float A[N][N], B[N][N], C[N][N];
// 비효율적: 열 우선 접근 (캐시 미스 많음)
void matmul_naive() {
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
for (int k = 0; k < N; k++)
C[i][j] += A[i][k] * B[k][j]; // B[k][j] 접근이 비연속
}
// 효율적: 블록 타일링 (캐시 재사용)
#define BLOCK 64
void matmul_tiled() {
for (int ii = 0; ii < N; ii += BLOCK)
for (int jj = 0; jj < N; jj += BLOCK)
for (int kk = 0; kk < N; kk += BLOCK)
for (int i = ii; i < ii+BLOCK && i < N; i++)
for (int j = jj; j < jj+BLOCK && j < N; j++)
for (int k = kk; k < kk+BLOCK && k < N; k++)
C[i][j] += A[i][k] * B[k][j];
}
SIMD 벡터화 (AVX2)
#include <immintrin.h> // AVX2
// AVX2로 float 8개를 동시에 덧셈
void vector_add_avx2(float *a, float *b, float *c, int n) {
int i;
for (i = 0; i <= n - 8; i += 8) {
__m256 va = _mm256_loadu_ps(&a[i]); // 8x float 로드
__m256 vb = _mm256_loadu_ps(&b[i]);
__m256 vc = _mm256_add_ps(va, vb); // 8x 동시 덧셈
_mm256_storeu_ps(&c[i], vc); // 8x float 저장
}
// 나머지 원소 처리
for (; i < n; i++)
c[i] = a[i] + b[i];
}
12. 퀴즈
Q1. 파이프라이닝에서 데이터 해저드(Data Hazard)가 발생하는 원인과 해결 방법은?
정답: 이전 명령어의 쓰기(Write)가 완료되기 전에 다음 명령어가 해당 레지스터를 읽으려 할 때(RAW: Read After Write) 발생합니다.
해결 방법:
- 전방전달(Forwarding): EX/MEM 단계 결과를 바로 다음 명령어 EX 입력으로 직접 전달하여 스톨 없이 해결.
- 스톨(Stall): NOP(버블)을 삽입하여 파이프라인을 일시 중단. 성능 저하.
- 코드 재배치: 컴파일러가 독립적인 명령어를 사이에 끼워 지연 없이 처리.
- 로드-유스 해저드의 경우 1사이클 스톨이 불가피합니다 (MEM 단계 결과를 EX 입력으로 전달 불가).
Q2. Direct-mapped 캐시와 4-way Set-associative 캐시의 차이점은?
정답: Direct-mapped 캐시는 메모리의 각 블록이 캐시의 딱 한 슬롯에만 매핑되는 반면, 4-way Set-associative는 동일한 인덱스를 가진 4개의 슬롯 중 하나에 매핑될 수 있습니다.
핵심 차이:
- Direct-mapped: 충돌 미스(Conflict Miss) 발생 가능. 구현 단순, 비용 낮음.
- 4-way SA: 충돌 미스 감소. LRU 등 교체 정책 필요. 비용 증가.
- 실제 CPU L1 캐시는 보통 4-way 또는 8-way를 사용하며, 완전 연관(Fully-associative)은 TLB에만 사용됩니다.
Q3. TLB(Translation Lookaside Buffer)의 역할과 TLB 미스 시 처리 과정을 설명하시오.
정답: TLB는 가상 주소를 물리 주소로 변환하는 최근 결과를 캐싱하는 완전 연관 캐시입니다. 메모리에 있는 페이지 테이블 접근 오버헤드를 줄여줍니다.
TLB 미스 처리:
- TLB에 해당 가상 페이지 번호(VPN)가 없음
- 하드웨어(x86) 또는 소프트웨어(RISC-V, MIPS) 페이지 테이블 워크 수행
- 다단계 페이지 테이블을 순서대로 조회 (PML4 → PDPT → PD → PT)
- 최종 물리 프레임 번호(PFN) 획득
- TLB에 새 항목 추가 (기존 항목 교체 필요 시 LRU 적용)
- 원래 메모리 접근 재시도
- 전체 과정: 수백 사이클 소요 → TLB 히트율(99%+) 유지가 중요
Q4. GPU의 워프(Warp) 발산(Divergence)이란 무엇이며 성능에 어떤 영향을 주는가?
정답: 워프 내 32개 스레드가 서로 다른 분기(if-else)를 취할 때 워프 발산이 발생합니다.
동작 방식:
- SIMT 모델에서 워프의 모든 스레드는 같은 명령어를 실행해야 합니다.
- if 블록 실행 시: if를 취한 스레드만 활성화, else 스레드는 마스킹(비활성)
- else 블록 실행 시: else를 취한 스레드만 활성화, if 스레드는 마스킹
- 두 경로를 직렬로 실행하므로 최악 2배 시간 소요
해결 방법:
- 워프 내 스레드들이 같은 분기를 취하도록 데이터 정렬
- 분기 대신 수학적 연산으로 대체 (조건부 선택)
- 쿠다 쉐이더의 분기 최소화 설계
Q5. Amdahl의 법칙을 이용하여, 전체 코드의 80%를 병렬화했을 때 최대 이론 속도 향상을 계산하시오.
정답: 무한히 많은 프로세서를 사용한다고 가정하면 최대 5배 속도 향상입니다.
계산:
- 병렬화 비율 f = 0.8 (80%)
- 순차 비율 = 1 - 0.8 = 0.2 (20%)
- 병렬화 배율 s를 무한대로 보내면: f/s → 0
- Speedup = 1 / ((1 - f) + f/s) = 1 / (0.2 + 0) = 1 / 0.2 = 5배
의미: 아무리 많은 코어를 추가해도, 순차 실행 부분(20%)이 전체 속도를 5배로 제한합니다. 이것이 순차 병목을 제거하는 것이 병렬 최적화의 핵심인 이유입니다.
참고 자료
- Patterson, D. & Hennessy, J. (2020). Computer Organization and Design: RISC-V Edition (2nd ed.). Morgan Kaufmann.
- Hennessy, J. & Patterson, D. (2019). Computer Architecture: A Quantitative Approach (6th ed.). Morgan Kaufmann.
- RISC-V International. (2024). The RISC-V Instruction Set Manual. https://riscv.org/technical/specifications/
- NVIDIA. (2023). CUDA C++ Programming Guide. https://docs.nvidia.com/cuda/cuda-c-programming-guide/
- ARM Ltd. (2024). ARM Architecture Reference Manual. https://developer.arm.com/documentation/
- Intel. (2024). Intel 64 and IA-32 Architectures Software Developer's Manual.