CPU 코어: 파이프라인
앱 아이콘을 누르면 CPU는 메모리에서 32비트짜리 숫자를 하나 가져온다. 그 숫자는 "레지스터 6번과 7번을 더해 5번에 넣어라" 같은 아주 작은 지시다. 이런 지시를 1초에 수십억 개 처리하는 비결은 한 명령을 빨리 끝내는 것이 아니라, 여러 명령을 겹쳐서 처리하는 데 있다. 빨래방에서 첫 빨래가 건조기에 들어가면 바로 다음 빨래를 세탁기에 넣는 것처럼. 하지만 겹치는 순간 문제가 생긴다. 앞 명령의 결과가 아직 안 나왔는데 뒷 명령이 그 값을 써야 한다면? 다음에 실행할 명령이 무엇인지 분기를 계산해 봐야 안다면? 이 장에서는 명령어의 비트를 직접 뒤집어 보고, 5단 파이프라인을 한 사이클씩 돌리며 이 문제들과 그 해법을 만난다.
- RISC-V 명령어가 opcode·레지스터·즉치값 필드로 이루어진 비트열임을 해독하며 확인한다.
- 단일 사이클 데이터패스의 한계와, 5단 파이프라인이 처리량을 높이는 원리를 설명한다.
- 데이터 해저드·제어 해저드가 만드는 거품과 플러시를 시뮬레이터로 관찰하고, 포워딩의 효과를 잰다.
- 2비트 포화 카운터 분기 예측기가 패턴마다 얼마나 맞히는지 직접 돌려 본다.
- 실행 시간 = 명령어 수 × CPI × 클럭 주기(철의 법칙)로 성능을 계산하고, 파이프라인 깊이의 최적점을 찾는다.
명령어는 비트다
CPU가 알아듣는 말의 목록을 명령어 집합 구조(ISA, Instruction Set Architecture)라 한다. 스마트폰 CPU는 대부분 Arm의 AArch64를 쓰고, 최근에는 누구나 쓸 수 있는 공개 ISA인 RISC-V가 마이크로컨트롤러에서 시작해 점점 큰 코어로 퍼지고 있다. 이 장에서는 구조가 깔끔한 RISC-V로 이야기한다.
RISC-V의 기본 명령어는 모두 32비트다. 맨 아래 7비트 opcode가 명령의 종류(산술, 적재, 저장, 분기…)를 정하고, 나머지 비트는 종류에 따라 정해진 자리에 레지스터 번호와 상수(즉치값(Immediate))를 담는다. 레지스터 번호 자리가 형식과 상관없이 늘 같은 비트에 있다는 점이 중요하다. 해독기가 명령의 종류를 알아내기 전에 미리 레지스터 파일을 읽기 시작할 수 있기 때문이다. 아래에서 명령을 고르거나, 비트를 직접 눌러 뒤집어 보자.
add의 30번 비트(funct7의 둘째 비트)를 누르면 sub이 된다. rd 칸을 바꾸면 결과를 쓸 레지스터가, opcode 칸을 바꾸면 명령의 종류 자체가 바뀐다. 분기(beq)의 즉치값은 비트가 여기저기 흩어져 있는데, 이것도 하드웨어를 단순하게 하려는 배치다(부호 비트가 늘 31번에 있다).명령어가 하는 일은 크게 네 가지다. 레지스터끼리 계산하기(add, sub, and…), 메모리에서 레지스터로 가져오기(lw, load), 레지스터에서 메모리로 내보내기(sw, store), 다음에 실행할 명령의 주소를 바꾸기(beq, branch). 계산은 오직 레지스터에서만 하고 메모리와는 적재·저장으로만 주고받는 이 방식을 적재-저장 구조(Load-store architecture)라 한다. RISC 계열 ISA의 공통점이며, 파이프라인을 깔끔하게 만드는 바탕이다.
AArch64와 RISC-V는 명령어 길이가 4바이트로 고정이라, 해독기가 다음 명령이 어디서 시작하는지 고민할 필요가 없다. 한 사이클에 8개, 10개 명령을 동시에 해독하는 넓은 코어를 만들기 쉽다. 반면 x86은 명령어 길이가 1~15바이트로 들쭉날쭉해서 먼저 길이부터 알아내야 하고, 복잡한 명령은 내부에서 RISC 비슷한 마이크로옵(µop) 여러 개로 쪼갠다. 그래서 x86 코어는 한 번 해독한 마이크로옵을 저장해 두는 캐시를 크게 두어 해독 비용을 피한다. 겉모습(ISA)과 속 구조(마이크로아키텍처)는 별개라는 점이 핵심이다.
명령어가 지나가는 길: 데이터패스
명령어 하나를 처리하는 과정은 다섯 단계로 나눌 수 있다. ① IF(인출, Instruction Fetch): 프로그램 카운터(PC)가 가리키는 주소에서 명령어를 읽는다. ② ID(해독, Instruction Decode): 필드를 나누고 레지스터 파일에서 피연산자를 읽는다. ③ EX(실행, Execute): ALU가 계산하거나 메모리 주소를 만든다. ④ MEM(메모리): 적재·저장이면 데이터 메모리에 접근한다. ⑤ WB(기록, Write Back): 결과를 레지스터 파일에 쓴다. 이 회로 덩어리 전체를 데이터패스(Datapath)라 한다.
lw(800 ps)에 클럭을 맞춰야 하므로 다른 명령은 시간을 버린다. 파이프라인으로 바꾸면 단계 사이에 레지스터(세로 막대)가 들어가고 클럭은 가장 느린 단계(200 ps + 레지스터 오버헤드 20 ps)로 짧아진다.단일 사이클 설계는 이해하기 쉽지만 낭비가 크다. 한 명령이 인출 회로를 쓰는 동안 ALU와 데이터 메모리는 놀고 있고, 클럭은 가장 느린 명령에 맞춰야 한다. 2장에서 본 파이프라이닝을 여기에 적용하면 어떨까? 다섯 단계 사이에 플립플롭을 끼워 넣으면 각 단계가 서로 다른 명령을 동시에 처리할 수 있다.
파이프라인: 빨래방의 지혜
빨래 네 바구니를 생각하자. 세탁, 건조, 개기, 정리에 각각 30분이 걸린다. 한 바구니를 끝까지 마친 뒤 다음 바구니를 시작하면 네 바구니에 8시간이 걸린다. 하지만 첫 바구니가 건조기에 들어가자마자 둘째 바구니를 세탁기에 넣으면 3시간 30분이면 끝난다. 바구니 하나가 끝나는 데 걸리는 시간(지연 시간)은 그대로 2시간인데, 일정 시간에 끝나는 바구니 수(처리량(Throughput))가 늘었다.
CPU에서 "바구니"는 명령어, "세탁기·건조기"는 IF·ID·EX·MEM·WB 다섯 단계다. 이상적이라면 매 사이클 명령어 하나가 끝나므로 명령어당 사이클 수, 즉 CPI(Cycles Per Instruction)가 1이 된다. 그런데 빨래와 달리 명령어는 서로 얽혀 있다. 뒷 명령이 앞 명령의 결과를 필요로 하기도 하고, 앞 명령(분기)이 끝나 봐야 뒷 명령이 무엇인지 알 수 있기도 하다. 이렇게 파이프라인이 매 사이클 다음 명령을 진행하지 못하게 막는 상황을 해저드(Hazard)라 한다.
데이터 해저드와 포워딩
해저드는 세 종류다. 같은 자원을 두 명령이 동시에 쓰려는 구조적 해저드(예: 메모리 포트가 하나뿐인데 인출과 적재가 겹칠 때), 앞 명령의 결과를 뒷 명령이 기다리는 데이터 해저드, 분기의 결과를 몰라 다음 명령을 정하지 못하는 제어 해저드. 5단 파이프라인은 명령어 메모리와 데이터 메모리를 나누어(실제로는 L1 명령 캐시와 L1 데이터 캐시) 구조적 해저드를 피한다. 남은 둘을 차례로 보자.
add x1, x2, x3 바로 뒤에 sub x4, x1, x5가 온다고 하자. sub는 ID 단계에서 x1을 읽어야 하는데, add는 그때 막 EX 단계에서 x1을 계산하는 중이고 레지스터 파일에 쓰는 것은 두 사이클 뒤 WB 단계다. 이것이 데이터 해저드(Data hazard)다. 가장 단순한 해결은 sub를 ID에서 기다리게 하는 것이다. 그 사이 EX 이후 단계에는 아무 일도 하지 않는 빈칸, 즉 거품(Bubble, Stall)이 흘러간다.
더 똑똑한 방법이 있다. x1의 값은 add의 EX 단계가 끝나는 순간 이미 ALU 출력에 나와 있다. 레지스터 파일까지 돌아갈 필요 없이 그 값을 바로 다음 명령의 ALU 입력으로 옆길로 보내 주면 된다. 이것이 포워딩(Forwarding, Bypassing)이다. 아래에서 앞 명령의 종류와 두 명령 사이 거리, 포워딩 여부를 바꿔 거품이 몇 개 생기는지 보자.
lw는 값이 MEM 단계가 끝나야 나오므로, 바로 다음 명령이 그 값을 쓰면 포워딩이 있어도 거품 하나를 피할 수 없다. 이것이 유명한 적재-사용 해저드(Load-use hazard)다.lw x4, 0(x1) 바로 뒤에 add x3, x3, x4가 있는 반복문이 있다. 회로는 그대로 두고 거품을 없앨 방법이 있을까?
답 보기
있다. x4와 상관없는 다른 명령(예를 들어 다음 주소를 계산하는 addi x1, x1, 4)을 lw와 add 사이로 옮기면 된다. 이처럼 컴파일러가 명령 순서를 바꿔 해저드를 피하는 것을 명령어 스케줄링이라 한다. 다음 절 시뮬레이터의 "배열 합" 과 "배열 합 (순서 바꿈)" 프로그램을 비교해 보자. 다음 장의 비순차 실행 코어는 이 일을 하드웨어가 실행 중에 스스로 한다(4장).
5단 파이프라인 시뮬레이터
이제 모든 조각을 한 기계에 모으자. 아래 시뮬레이터는 RISC-V의 일부 명령(add sub and or xor slt addi andi ori lw sw beq bne blt bge nop)을 실제로 실행하면서, 매 사이클 어느 명령이 어느 단계에 있는지 그린다. 분기는 EX 단계에서 결정되고, 예측이 틀리면 그 사이에 잘못 가져온 두 명령을 버린다(플러시(Flush)). 프로그램을 골라 한 사이클씩 넘겨 보고, 포워딩과 분기 예측을 바꿔 가며 CPI가 어떻게 변하는지 확인하자. 프로그램은 직접 고칠 수도 있다.
RISC-V 5단 파이프라인
"배열 합"을 포워딩 켜고 끝까지 돌리면 CPI가 1을 꽤 넘는다. 원인은 두 가지다. 반복마다 lw 직후 add가 그 값을 쓰는 적재-사용 거품 1개, 그리고 반복문 끝의 bne가 "안 감"으로 예측되었다가 실제로는 매번 뒤로 가서 생기는 플러시 2개다. 예측기를 2비트로 바꾸면 두 번째 반복부터 분기를 맞히기 시작하고, "순서 바꿈" 프로그램은 거품도 없앤다. 두 가지를 모두 하면 CPI가 1에 가까워진다.
이 시뮬레이터가 바로 스마트폰 효율 코어의 뼈대다. Arm의 Cortex-A55·A520 같은 작은 코어는 명령을 프로그램 순서대로 처리하는 순차 실행(In-order) 파이프라인에 한 사이클 두 개씩 발행하는 정도를 더한 구조다. 순서를 바꾸는 복잡한 회로가 없어 면적이 큰 코어의 1/4~1/5에 불과하고, 같은 일을 훨씬 적은 에너지로 한다. 대신 적재-사용 거품이나 캐시 미스를 만나면 꼼짝없이 기다린다. 그 기다림을 줄이는 것이 다음 장 큰 코어의 일이다.
위쪽 다섯 칸은 지금 사이클에 각 단계에 들어 있는 명령이다. 빈칸이면 거품이다. 아래 표는 시간에 따른 파이프라인 도표로, 같은 열(사이클)을 세로로 읽으면 그 순간 기계의 단면이 된다. 흐린 줄에 ✕가 있는 명령은 분기 예측이 틀려 버려진 명령이다. 화살표는 포워딩이 실제로 쓰인 곳이다.
분기 예측: 아직 모르는 미래에 걸기
프로그램에는 평균 5~7개 명령에 하나꼴로 분기가 있다. 분기의 결과는 EX 단계에 가서야 알 수 있으므로, 그때까지 파이프라인 앞쪽은 무엇을 가져와야 할지 모른다. 기다리면 분기마다 사이클을 잃는다. 그래서 CPU는 추측한다. 일단 한쪽 길을 골라 계속 가져오고 실행하다가, 틀렸으면 그 일을 모두 버리고 올바른 길로 돌아간다. 맞히는 비율이 높을수록 손해가 적다.
가장 유명한 예측기는 분기마다 2비트짜리 카운터를 하나 두는 2비트 포화 카운터(2-bit saturating counter)다. 분기가 가면 카운터를 하나 올리고, 안 가면 하나 내린다(0과 3에서 멈춘다). 카운터가 2 이상이면 "감"으로 예측한다. 한 번 어긋났다고 바로 생각을 바꾸지 않으므로, 반복문 끝에서 한 번 빠져나오는 정도로는 흔들리지 않는다.
반복문 끝 분기는 9번 "감", 1번 "안 감"을 되풀이한다. 1비트 예측기와 2비트 예측기는 각각 몇 %를 맞힐까?
답 보기
1비트는 마지막 "안 감"에서 한 번 틀리고, 다음 반복문 첫 "감"에서 또 틀린다(지난번이 "안 감"이었으므로). 10번 중 8번, 80%. 2비트는 "안 감" 한 번에 강한 감(3)→약한 감(2)으로만 내려가서 다음 "감"을 여전히 맞힌다. 10번 중 9번, 90%. 위 그림에서 반복문 패턴의 정확도를 확인해 보자.
방향(감/안 감)만 맞혀서는 부족하다. "감"이라고 예측했다면 어디로 가는지도 인출 단계에서 바로 알아야 한다. 그래서 분기 명령의 주소와 그 목적지를 기억하는 작은 캐시, BTB(Branch Target Buffer)를 둔다. 함수에서 돌아가는 ret처럼 목적지가 매번 바뀌는 분기는 호출할 때마다 돌아올 주소를 쌓아 두는 반환 주소 스택(Return address stack)으로 맞힌다. 위 시뮬레이터의 "항상 감"과 "2비트"는 BTB가 늘 맞는다고 가정한 것이다.
현대 고성능 코어의 예측기는 이보다 훨씬 정교하다. 최근 수십~수천 개 분기의 결과(전역 기록)와 주소를 섞어 여러 표를 동시에 찾고, 가장 믿을 만한 표의 답을 고르는 TAGE 계열이나 신경망을 흉내 낸 퍼셉트론 예측기를 쓴다. 덕분에 보통 프로그램에서 정확도가 95~99%에 이른다. 그래도 남은 1~5%가 중요하다. 깊은 파이프라인에서 예측 실패 한 번은 10~20사이클을 버리는 일이기 때문이다.
성능 방정식과 파이프라인의 깊이
CPU 성능을 하나의 식으로 묶어 보자. 프로그램 하나를 실행하는 시간은 명령어 수, 명령어당 사이클, 사이클 하나의 길이를 곱한 것이다. 이 식을 흔히 철의 법칙(Iron law of performance)이라 부른다. 세 항은 서로 다른 사람이 책임진다. 명령어 수는 ISA와 컴파일러, CPI는 마이크로아키텍처, 클럭은 회로와 공정(2장)이다.
2장에서 본 대로 파이프라인을 깊게 하면 클럭은 오르지만, 단마다 레지스터 오버헤드를 내야 하고, 분기 예측이 틀렸을 때 버리는 단의 수도 늘어난다. 깊이에 따라 클럭은 오르고 CPI는 나빠진다. 그 곱이 최소가 되는 지점, 즉 최적 깊이가 있다. 2000년대 초 연구들(Hartstein·Puzak, Sprangle·Carmean 등)은 성능만 보면 최적 깊이가 FO4 6~8개 남짓의 단 지연, 대략 20~30단이라는 결과를 냈지만, 전력까지 고려하면 훨씬 얕아진다. 펜티엄 4가 31단까지 갔다가 다음 세대(Core)에서 14단 안팎으로 돌아온 것이 그 증거다.
파이프라인 깊이의 최적점
| 코어 | 연도 | 구조 | 파이프라인 단 수 | 분기 예측 실패 벌점 |
|---|---|---|---|---|
| MIPS R2000 | 1985 | 순차, 단일 발행 | 5 | ~1 (지연 슬롯) |
| Intel Pentium 4 (Prescott) | 2004 | 비순차 | 31 | 약 30 이상 |
| Intel Core 2 | 2006 | 비순차, 4폭 | 약 14 | 약 15 |
| Arm Cortex-A53 / A55 | 2012 / 2017 | 순차, 2폭 (효율 코어) | 8 | 약 7~8 |
| Arm Cortex-A76 | 2018 | 비순차, 4폭 | 약 13 | 약 11 |
| Apple Firestorm (M1/A14) | 2020 | 비순차, 8폭 | 비공개 | 약 13~14 (측정 추정) |
단 수와 벌점은 공식 발표와 공개 측정(마이크로벤치마크) 자료의 대략값이며, 같은 코어도 측정 방법(어느 단계에서 예측 실패가 드러나는가, 마이크로옵 캐시 적중 여부)에 따라 몇 사이클씩 다르다.
지금까지 이상적인 파이프라인의 CPI는 1이었다. 매 사이클 명령 하나만 들여보내기 때문이다. 이 벽을 넘으려면 한 사이클에 명령을 여러 개 들여보내야 하고(슈퍼스칼라), 그러면 해저드가 훨씬 많아져서 명령 순서를 하드웨어가 바꿔야 한다(비순차 실행). 스마트폰의 큰 코어가 작은 코어보다 3~4배 큰 이유가 거기에 있다. 4장에서 이어 간다.
핵심 정리
- 명령어는 opcode·레지스터 번호·즉치값 필드로 나뉜 32비트 비트열이다. RISC-V는 레지스터 필드 위치를 고정해 해독을 빠르게 한다.
- 파이프라인은 명령 하나의 지연 시간을 줄이지 않고, 여러 명령을 겹쳐 처리량을 단계 수만큼 높인다. 가장 느린 단계가 박자를 정한다.
- 데이터 해저드는 포워딩으로 대부분 없애지만 적재-사용은 거품 하나가 남는다. 컴파일러 스케줄링이나 비순차 실행으로 숨긴다.
- 제어 해저드는 분기 예측으로 숨긴다. 2비트 카운터는 반복문에 강하고, 전역 기록을 쓰면 규칙적인 패턴도 맞힌다. 틀리면 플러시 벌점을 낸다.
- 실행 시간 = 명령어 수 × CPI × 클럭 주기. 파이프라인을 깊게 하면 클럭과 CPI가 맞바뀌어, 성능과 전력을 함께 보면 최적 깊이는 10~20단 남짓이다.
확인 퀴즈
Q1. RISC-V에서 opcode가 명령어의 몇 번 비트에 있는가?
Q2. 단계 지연이 각각 200·100·200·200·100 ps인 5단 파이프라인(레지스터 오버헤드 무시)의 클럭 주기는?
Q3. 포워딩이 있는 5단 파이프라인에서도 거품이 생기는 경우는?
Q4. 분기 비율 20%, 예측 정확도 95%, 예측 실패 벌점 15사이클, 다른 해저드가 없다면 CPI는?
Q5. 패턴 T, N, T, N, …을 되풀이하는 분기에 대해 2비트 포화 카운터(약한 안 감 상태에서 시작)의 장기 정확도는?
Q6. 펜티엄 4 이후 고성능 CPU의 파이프라인이 다시 얕아진 가장 큰 이유는?