CPU 는 어떻게 생겼나 - 다이 평면도에서 코어 속 파이프라인까지

English (N/A)

앞 글에서 트랜지스터로 게이트를 만들고 게이트로 덧셈기를 만들었다. 이 글은 그 위에 올라가는 것, CPU 가 실제로 어떤 모양으로 조직되어 있는지를 구조도로 본다.

결론부터 말하면 CPU 는 "명령을 가져오는 곳, 순서를 섞는 곳, 계산하는 곳, 데이터를 가까이 두는 곳" 네 층이 한 코어를 이루고, 그 코어 여러 개와 커다란 캐시가 한 다이에 올라간 것이다. 각 층이 왜 있는지는 "메모리가 너무 느리다"는 한 문장으로 거의 다 설명된다.

밖에서 안으로

패키지 (밖에서 본 모습)금속 뚜껑약 40 mm뚜껑 밑에 다이 하나다이 평면도 (뚜껑을 열면)메모리컨트롤러코어 0L2 1 MB코어 4L2 1 MB코어 1L2 1 MB코어 5L2 1 MB코어 2L2 1 MB코어 6L2 1 MB코어 3L2 1 MB코어 7L2 1 MBL3 캐시 (공유) 32 MB내장GPUI/OPCIe약 10 mm · 트랜지스터 100억 개 안팎
왼쪽은 메인보드에 꽂는 패키지고, 금속 뚜껑 밑에 손톱만 한 실리콘 다이가 있다. 오른쪽은 그 다이의 배치를 단순화한 것이다. 흔히 CPU 라고 부르는 것은 코어 여러 개와 캐시, 메모리 컨트롤러, GPU 까지 한 조각에 모아 놓은 것이다. 실제 배치는 제품마다 다르다.

메인보드에 꽂는 CPU 는 손바닥 절반 크기의 금속 뚜껑이다. 그 뚜껑 밑에 손톱만 한 실리콘 다이가 있고, 흔히 "CPU" 라고 부르는 건 이 다이 전체다.

다이를 위에서 보면 코어가 여러 개 있고, 그 사이에 코어들이 공유하는 큰 캐시가 있고, 가장자리에 메모리 컨트롤러와 입출력이 있다. 요즘은 GPU 도 여기 같이 들어간다. 계산 자체를 하는 코어는 면적의 절반이 안 되고, 나머지는 데이터를 가까이 두고 나르는 장치다.

CPU, 다이, 코어는 같은 말이 아니다

여기서 단어를 한 번 정리하고 가야 한다. 위 그림에서 "CPU" 라고 부른 게 패키지인지, 다이인지, 코어인지가 헷갈리기 쉽고, 실제로 셋 다 CPU 라고 불린다.

처음에는 구분할 필요가 없었다. 1970년대부터 2000년대 초까지 CPU 칩 하나에는 명령을 실행하는 단위가 딱 하나 들어 있었다. CPU = 칩 = 실행 단위 가 전부 같은 말이었다.

2005년쯤 클럭을 더 올리기 어려워지자 인텔과 AMD 가 실행 단위 두 개를 한 칩에 넣었다. 이때 이름이 문제가 됐다. 컴퓨터 안에서 그 칩이 꽂히는 자리도, 가게에서 사는 제품도 여전히 하나였으니 제품 이름은 CPU 로 두고, 그 안의 실행 단위에 코어 라는 새 단어를 붙였다. "듀얼코어 CPU" 라는 말이 그때 나왔다. 그 뒤로 메모리 컨트롤러와 GPU 가 하나씩 다이 안으로 들어왔는데도 제품 이름은 계속 CPU 였다.

그래서 지금은 같은 단어가 층마다 다른 걸 가리킨다.

  • CPU (제품): 사서 소켓에 꽂는 패키지 하나. i9, 라이젠 9 같은 이름이 붙는 단위다.
  • 다이: 뚜껑 밑 실리콘 조각. 코어와 캐시, 메모리 컨트롤러, GPU 까지 들어 있어서 엄밀히는 SoC(System on Chip)에 가깝다.
  • 코어: 명령을 가져와 실행하는 논리 단위. 교과서가 "CPU 는 제어 유닛과 ALU 와 레지스터" 라고 할 때의 CPU 는 이거다.

"16코어 CPU" 는 교과서 기준으로 CPU 16개가 든 제품이고, 운영체제가 "CPU 16개" 라고 표시하는 것도 코어를 센 것이다.

단일 다이 — 인텔 데스크톱 방식패키지다이 1개P코어 8E코어 8L3 · 메모리 컨트롤러내장 GPU · I/O칩렛 — AMD 라이젠 방식패키지코어 다이 1L3 32 MB코어 다이 2L3 32 MBI/O 다이 (다른 공정, 더 싸게)메모리 컨트롤러PCIe · USB · GPU둘 다 소켓에 꽂히는 건 패키지 하나고, 제품명도 "16코어 CPU" 로 같다. 안에 다이가 몇 개인지는 제조사 사정이다.
16코어 CPU 두 가지. 왼쪽은 다이 하나에 전부 넣은 것, 오른쪽은 8코어짜리 코어 다이 두 개와 메모리 컨트롤러·입출력을 모은 I/O 다이를 한 패키지에 붙인 것이다. 소비자가 보는 건 둘 다 패키지 하나다.

그리고 코어 16개가 다이 하나에 있는지도 제품마다 다르다. 인텔 데스크톱 칩은 다이 하나에 전부 넣는다. AMD 라이젠은 8코어짜리 코어 다이를 두 개 놓고, 메모리 컨트롤러와 입출력은 값싼 공정으로 만든 별도의 I/O 다이에 몰아서, 다이 세 개를 한 패키지에 붙인다. 서버용은 코어 다이를 8~12개까지 올린다. 이걸 칩렛이라고 부른다. 큰 다이 하나는 불량률이 높으니 작은 다이 여러 개로 나누는 게 수율에 유리하고, 코어 수를 늘리려면 다이를 더 붙이면 된다.

정리하면 "CPU 는 다이 전체" 라는 말은 정의가 아니라 관행 이다. 정확히는 "CPU 라는 제품 안에 코어라는 원래 의미의 CPU 가 여러 개, 다이 한 개 또는 여러 개에 나뉘어 들어 있다" 가 맞다.

코어 하나는 무엇을 하나

그럼 코어 하나는 정확히 뭘 하는 단위인가. 프로그램의 명령을 한 줄씩 읽어서 실행하는 단위다. 하는 일은 네 가지가 반복되는 것뿐이다.

  1. 메모리에서 다음 명령을 가져온다.
  2. 그 명령이 무슨 뜻인지 해석한다. "이 두 레지스터를 더해라" 같은 식이다.
  3. 실행한다. 덧셈이면 ALU 가 더하고, 메모리 읽기면 주소를 계산해서 캐시에 요청한다.
  4. 결과를 레지스터에 쓰고 다음 명령으로 넘어간다.

운영체제는 실행 중인 프로그램, 정확히는 스레드를 코어에 하나씩 배정한다. 코어가 16개면 스레드 16개가 동시에 돌 수 있고, 스레드 하나짜리 프로그램은 코어를 아무리 많이 줘도 하나만 쓴다. 코어 하나의 빠르기가 싱글코어 성능이고 코어 수가 멀티코어 성능이다.

코어 0실행 회로인출·해석·실행·쓰기레지스터 · ALUL1 명령L1 데이터L2 1~2 MB (이 코어 전용)코어 1실행 회로인출·해석·실행·쓰기레지스터 · ALUL1 명령L1 데이터L2 1~2 MB (이 코어 전용)코어 2실행 회로인출·해석·실행·쓰기레지스터 · ALUL1 명령L1 데이터L2 1~2 MB (이 코어 전용)… 코어 15L3 캐시 16~36 MB — 모든 코어가 공유프로그램 A 의 스레드프로그램 B 의 스레드(비어 있음)코어 하나 = 실행 회로 + L1 + L2. 운영체제는 스레드를 코어에 하나씩 배정한다.
코어를 셀 때의 단위. 실행 회로에 L1 캐시 두 개와 L2 캐시 하나가 붙은 덩어리가 코어 하나다. L3 는 코어 밖에 있고 모두가 나눠 쓴다. 운영체제는 실행할 스레드를 코어에 하나씩 얹는다.

코어의 경계가 어디까지인지도 이 그림으로 정한다. 캐시는 세 층인데 소속이 다르다.

  • L1: 코어 안 깊숙이. 명령용과 데이터용으로 나뉘고 각 3248 KB. 지연 45 사이클.
  • L2: 코어에 하나씩 딸린 전용 캐시. 1~2 MB. 지연 14 사이클쯤. 앞의 다이 평면도에서 코어 칸 안에 "L2 1 MB" 라고 쓴 게 이거다.
  • L3: 코어들이 공유하는 큰 캐시. 16~36 MB. 지연 45 사이클쯤. 평면도 가운데의 큰 칸이다.

L1 과 L2 는 코어 하나가 독점하고 L3 는 모두가 나눠 쓴다. 그래서 "코어 하나" 는 실행 회로에 L1 과 L2 까지 붙은 덩어리 를 말한다. 코어 면적에서 실제 계산 회로보다 L2 캐시가 더 넓은 경우도 흔하다. 이 글의 나머지는 그 코어 하나를 뜯는다.

코어 하나의 네 층

프론트엔드 — 명령을 가져와서 해석한다L1 명령 캐시32 KB인출16 B/사이클디코더 ×4~6명령 → 마이크로옵마이크로옵캐시분기 예측기다음 주소 추측비순차 엔진 — 순서를 바꿔 빈 유닛에 넣는다재정렬 버퍼 (ROB)진행 중인 명령 수백 개스케줄러피연산자 준비된 것부터물리 레지스터 파일정수 ×200 · 벡터 ×200실행 유닛 — 실제 계산이 일어나는 곳 (매 사이클 여러 개 동시에)정수 ALU ×4덧셈·논리·비교곱셈·나눗셈정수부동소수점·벡터 ×3SIMD 256~512 bit로드·스토어 ×3주소 계산 + 메모리 접근메모리 쪽L1 데이터 캐시 48 KB지연 4~5 사이클L2 캐시 1~2 MB지연 ~14 사이클→ L3 · 메모리 컨트롤러 · DRAM코어 밖
코어 하나를 위에서 아래로 자른 논리 구조. 프론트엔드가 명령을 가져와 잘게 쪼개고, 비순차 엔진이 준비된 것부터 실행 유닛에 던지고, 결과는 레지스터 파일과 캐시로 돌아간다. 숫자는 최근 데스크톱 코어의 대략적인 값이다.

코어 하나를 논리적으로 자르면 네 층이 나온다.

프론트엔드 는 명령을 가져와서 해석하는 곳이다. 명령 캐시에서 한 번에 16바이트씩 가져오고, 디코더 4~6개가 동시에 명령을 마이크로옵이라는 더 작은 단위로 쪼갠다. 여기서 눈여겨볼 게 분기 예측기 다. if 문의 결과를 알려면 계산이 끝나야 하는데, 그때까지 기다리면 파이프라인이 텅 빈다. 그래서 과거 패턴으로 "이번엔 이쪽으로 갈 거다"라고 추측하고 먼저 가져온다. 맞으면 이득이고 틀리면 되감는다. 요즘 예측기는 95% 이상 맞춘다.

비순차 엔진 은 명령의 순서를 바꾸는 곳이다. 왜 바꾸는지는 아래에서 따로 본다. 재정렬 버퍼가 진행 중인 명령 수백 개를 붙들고 있고, 스케줄러가 그중 피연산자가 준비된 것부터 실행 유닛에 던진다.

실행 유닛 은 실제 계산이 일어나는 곳이다. 정수 ALU 가 4개쯤, 곱셈·나눗셈, 부동소수점과 벡터 유닛, 그리고 메모리 주소를 계산하고 읽고 쓰는 로드·스토어 유닛이 여러 개 있다. 한 사이클에 이 중 여러 개가 동시에 돈다. ALU 자체는 다음 글에서 따로 뜯는다.

캐시 는 코어에 붙은 작은 메모리다. L1 은 코어 안에, L2 는 코어 바로 옆에 있다.

명령 하나가 지나가는 길

위 그림은 복잡하니 뼈대만 남겨보자. 교과서에 나오는 단일 사이클 데이터패스다.

PC명령메모리+4레지스터 파일32개 × 32 bitadd r3, r1, r2읽기 r1읽기 r2쓰기 r3ALUr1r2연산 선택데이터메모리주소MUX결과를 r3 에 쓴다제어 유닛주황 = add 명령 하나가 지나가는 길 · 점선 = 이 명령에서는 안 쓰는 길 · 보라 = 제어 신호
코어의 뼈대만 남긴 교과서식 데이터패스. add r3, r1, r2 한 개가 지나가는 길을 주황으로 칠했다. PC 가 가리키는 자리에서 명령을 읽고, 제어 유닛이 해석해서 스위치를 놓고, 레지스터 두 개가 ALU 로 들어가고, 결과가 레지스터로 돌아온다. 그동안 PC 는 4 가 더해져 다음 명령을 가리킨다.

add r3, r1, r2 한 개가 지나가는 길을 따라가면 이렇다.

  1. PC(프로그램 카운터)가 다음 명령의 주소를 들고 있다. 그 주소로 명령 메모리를 읽는다.
  2. 제어 유닛이 명령을 해석한다. "add 니까 ALU 는 덧셈, 결과는 레지스터로" 같은 스위치 설정이 나온다.
  3. 레지스터 파일에서 r1 과 r2 를 읽는다. 레지스터 파일은 32개짜리 작은 메모리인데, 읽기 포트 두 개와 쓰기 포트 하나가 있어서 한 사이클에 둘 읽고 하나 쓴다.
  4. ALU 가 더한다.
  5. 결과가 레지스터 파일의 r3 에 쓰인다. 그동안 PC 에는 4 가 더해져 다음 명령을 가리킨다.

이 다섯 단계가 CPU 의 전부다. 나머지 모든 복잡함은 이걸 더 빨리 하려는 장치 다.

파이프라인 — 단계를 겹친다

사이클 →123456789명령 1IFIDEXMEMWB명령 2IFIDEXMEMWB명령 3IFIDEXMEMWB명령 4IFIDEXMEMWB명령 5IFIDEXMEMWB5사이클째: 5개가 동시에 진행 중IF 인출ID 해석EX 실행MEM 메모리WB 쓰기
명령 하나는 다섯 단계를 거치지만, 각 단계가 다른 명령을 동시에 처리하므로 사이클마다 하나씩 끝난다. 세탁기·건조기·다림질을 한 사람이 순서대로 하지 않고 세 사람이 동시에 돌리는 것과 같다.

위 다섯 단계를 한 명령이 다 끝나고 다음 명령을 시작하면 사이클마다 다섯 단계 중 하나만 일한다. 그러지 말고 단계마다 다른 명령을 처리하게 하면 사이클마다 명령 하나가 끝난다. 이게 파이프라인이다.

파이프라인 단계 수를 늘리면 각 단계가 짧아져서 클럭을 올릴 수 있다. 그래서 실제 코어는 5단계가 아니라 15~20단계다. 대신 분기 예측이 틀렸을 때 버려야 하는 명령도 그만큼 많아진다. 분기 예측기가 그렇게 공들여진 이유가 이거다.

비순차 실행 — 기다리는 동안 딴 일을 한다

0481216사이클순서대로 실행① 메모리 읽기 (캐시 미스)DRAM 기다림 ~300 사이클② ①의 값 + 1③ 무관한 곱셈④ 무관한 덧셈비순차 실행① 메모리 읽기 (캐시 미스)DRAM 기다림③ 무관한 곱셈먼저 한다④ 무관한 덧셈② ①의 값 + 1②는 ①의 결과가 있어야 하지만 ③④는 아니다. 비순차 엔진은 이 의존 관계를 보고 ③④를 기다리는 동안 먼저 처리한다.
코어가 명령 순서를 바꾸는 이유. 메모리를 기다리는 수백 사이클 동안 관계없는 계산을 먼저 해두면 그만큼 시간이 절약된다. 재정렬 버퍼는 결과를 원래 순서대로 되돌려 놓아서 프로그램 입장에서는 순서대로 실행된 것처럼 보이게 한다.

파이프라인이 잘 돌려면 매 사이클 명령이 들어와야 하는데, 메모리를 읽는 명령이 캐시에 없으면 DRAM 까지 다녀와야 하고 그게 300사이클쯤 걸린다. 순서대로 실행하면 그 뒤의 명령이 전부 멈춘다.

비순차 엔진은 명령 사이의 의존 관계를 본다. "②는 ①의 결과가 필요하지만 ③④는 아니다"를 알 수 있으면 ③④를 먼저 한다. 재정렬 버퍼가 결과를 원래 순서대로 다시 정렬해서 내보내므로 프로그램 입장에서는 아무 일도 없었던 것처럼 보인다.

이게 요즘 코어 트랜지스터의 큰 몫을 먹는다. 명령 수백 개의 의존 관계를 매 사이클 추적하고, 레지스터 이름을 물리 레지스터 200개에 다시 배정하고, 예측이 틀리면 되감는다. 계산 자체보다 계산을 계속 시키기 위한 관리 에 더 많은 회로가 들어간다.

메모리 계층 — 거리가 곧 시간이다

접근 지연 (로그 눈금) →레지스터코어 안 · 수백 개 × 8 B1 사이클 이하L1 캐시코어 안 · 32~48 KB4~5 사이클L2 캐시코어 옆 · 1~2 MB약 14 사이클L3 캐시다이 공유 · 16~36 MB약 45 사이클DRAM패키지 밖 · 16~64 GB약 300 사이클 (~80 ns)SSD보드 위 · 1~4 TB약 30만 사이클 (~80 μs)코어에서 멀어질수록 크고 느리다. 자주 쓰는 것을 가까운 층에 복사해 두는 것이 캐시의 전부다.
코어 안의 레지스터부터 보드 위 SSD 까지. 가로 막대 길이가 접근 지연이고 로그 눈금이다. DRAM 한 번 다녀오는 동안 코어는 덧셈 수백 개를 할 수 있으므로, 다이 면적의 절반 가까이를 캐시에 쓰는 것이 남는 장사다.

위 얘기가 전부 "메모리가 느리다"로 귀결되므로 마지막으로 그 거리감을 보자. 레지스터는 1사이클, L1 은 4~5, L2 는 14, L3 는 45, DRAM 은 300 안팎이다. 단위가 사이클이라는 걸 기억하자. 코어는 그 한 사이클에 덧셈을 서너 개 한다.

그래서 다이 면적의 절반 가까이를 캐시에 쓴다. 최근에 쓴 데이터는 다시 쓸 확률이 높고, 옆에 있는 데이터도 곧 쓸 확률이 높다는 두 가지 경험칙에 기대서, 자주 쓰는 걸 가까운 층에 복사해 둔다. 프로그램이 빠른지 느린지는 이 계층을 얼마나 잘 타느냐로 갈리는 경우가 많다.

정리

CPU 구조도를 한 줄로 요약하면 덧셈기 하나를 쉬지 않게 만들기 위한 장치들의 집합 이다.

명령을 미리 가져오고(프론트엔드, 분기 예측), 단계를 겹치고(파이프라인), 순서를 바꾸고(비순차 엔진), 데이터를 가까이 둔다(캐시). 이 넷이 코어 하나의 네 층이고, 그 코어를 여러 개 놓고 공유 캐시로 묶은 것이 다이 하나다.

이어서

실행 유닛의 핵심인 ALU 가 안에서 어떻게 생겼는지는 ALU 는 어떻게 생겼나에서 따로 뜯는다. 그 ALU 를 이루는 게이트와 덧셈기는 트랜지스터로 논리 게이트 만들기에, 게이트를 이루는 트랜지스터는 트랜지스터는 어떻게 생겼나에 정리해뒀다.

참고