CPU 는 어떻게 생겼나 - 다이 평면도에서 코어 속 파이프라인까지
앞 글에서 트랜지스터로 게이트를 만들고 게이트로 덧셈기를 만들었다. 이 글은 그 위에 올라가는 것, CPU 가 실제로 어떤 모양으로 조직되어 있는지를 구조도로 본다.
결론부터 말하면 CPU 는 "명령을 가져오는 곳, 순서를 섞는 곳, 계산하는 곳, 데이터를 가까이 두는 곳" 네 층이 한 코어를 이루고, 그 코어 여러 개와 커다란 캐시가 한 다이에 올라간 것이다. 각 층이 왜 있는지는 "메모리가 너무 느리다"는 한 문장으로 거의 다 설명된다.
밖에서 안으로
메인보드에 꽂는 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개" 라고 표시하는 것도 코어를 센 것이다.
그리고 코어 16개가 다이 하나에 있는지도 제품마다 다르다. 인텔 데스크톱 칩은 다이 하나에 전부 넣는다. AMD 라이젠은 8코어짜리 코어 다이를 두 개 놓고, 메모리 컨트롤러와 입출력은 값싼 공정으로 만든 별도의 I/O 다이에 몰아서, 다이 세 개를 한 패키지에 붙인다. 서버용은 코어 다이를 8~12개까지 올린다. 이걸 칩렛이라고 부른다. 큰 다이 하나는 불량률이 높으니 작은 다이 여러 개로 나누는 게 수율에 유리하고, 코어 수를 늘리려면 다이를 더 붙이면 된다.
정리하면 "CPU 는 다이 전체" 라는 말은 정의가 아니라 관행 이다. 정확히는 "CPU 라는 제품 안에 코어라는 원래 의미의 CPU 가 여러 개, 다이 한 개 또는 여러 개에 나뉘어 들어 있다" 가 맞다.
코어 하나는 무엇을 하나
그럼 코어 하나는 정확히 뭘 하는 단위인가. 프로그램의 명령을 한 줄씩 읽어서 실행하는 단위다. 하는 일은 네 가지가 반복되는 것뿐이다.
- 메모리에서 다음 명령을 가져온다.
- 그 명령이 무슨 뜻인지 해석한다. "이 두 레지스터를 더해라" 같은 식이다.
- 실행한다. 덧셈이면 ALU 가 더하고, 메모리 읽기면 주소를 계산해서 캐시에 요청한다.
- 결과를 레지스터에 쓰고 다음 명령으로 넘어간다.
운영체제는 실행 중인 프로그램, 정확히는 스레드를 코어에 하나씩 배정한다. 코어가 16개면 스레드 16개가 동시에 돌 수 있고, 스레드 하나짜리 프로그램은 코어를 아무리 많이 줘도 하나만 쓴다. 코어 하나의 빠르기가 싱글코어 성능이고 코어 수가 멀티코어 성능이다.
코어의 경계가 어디까지인지도 이 그림으로 정한다. 캐시는 세 층인데 소속이 다르다.
- L1: 코어 안 깊숙이. 명령용과 데이터용으로 나뉘고 각 32
48 KB. 지연 45 사이클. - L2: 코어에 하나씩 딸린 전용 캐시. 1~2 MB. 지연 14 사이클쯤. 앞의 다이 평면도에서 코어 칸 안에 "L2 1 MB" 라고 쓴 게 이거다.
- L3: 코어들이 공유하는 큰 캐시. 16~36 MB. 지연 45 사이클쯤. 평면도 가운데의 큰 칸이다.
L1 과 L2 는 코어 하나가 독점하고 L3 는 모두가 나눠 쓴다. 그래서 "코어 하나" 는 실행 회로에 L1 과 L2 까지 붙은 덩어리 를 말한다. 코어 면적에서 실제 계산 회로보다 L2 캐시가 더 넓은 경우도 흔하다. 이 글의 나머지는 그 코어 하나를 뜯는다.
코어 하나의 네 층
코어 하나를 논리적으로 자르면 네 층이 나온다.
프론트엔드 는 명령을 가져와서 해석하는 곳이다. 명령 캐시에서 한 번에 16바이트씩 가져오고, 디코더 4~6개가 동시에 명령을 마이크로옵이라는 더 작은 단위로 쪼갠다. 여기서 눈여겨볼 게 분기 예측기 다. if 문의 결과를 알려면 계산이 끝나야 하는데, 그때까지 기다리면 파이프라인이 텅 빈다. 그래서 과거 패턴으로 "이번엔 이쪽으로 갈 거다"라고 추측하고 먼저 가져온다. 맞으면 이득이고 틀리면 되감는다. 요즘 예측기는 95% 이상 맞춘다.
비순차 엔진 은 명령의 순서를 바꾸는 곳이다. 왜 바꾸는지는 아래에서 따로 본다. 재정렬 버퍼가 진행 중인 명령 수백 개를 붙들고 있고, 스케줄러가 그중 피연산자가 준비된 것부터 실행 유닛에 던진다.
실행 유닛 은 실제 계산이 일어나는 곳이다. 정수 ALU 가 4개쯤, 곱셈·나눗셈, 부동소수점과 벡터 유닛, 그리고 메모리 주소를 계산하고 읽고 쓰는 로드·스토어 유닛이 여러 개 있다. 한 사이클에 이 중 여러 개가 동시에 돈다. ALU 자체는 다음 글에서 따로 뜯는다.
캐시 는 코어에 붙은 작은 메모리다. L1 은 코어 안에, L2 는 코어 바로 옆에 있다.
명령 하나가 지나가는 길
위 그림은 복잡하니 뼈대만 남겨보자. 교과서에 나오는 단일 사이클 데이터패스다.
add r3, r1, r2 한 개가 지나가는 길을 따라가면 이렇다.
- PC(프로그램 카운터)가 다음 명령의 주소를 들고 있다. 그 주소로 명령 메모리를 읽는다.
- 제어 유닛이 명령을 해석한다. "add 니까 ALU 는 덧셈, 결과는 레지스터로" 같은 스위치 설정이 나온다.
- 레지스터 파일에서 r1 과 r2 를 읽는다. 레지스터 파일은 32개짜리 작은 메모리인데, 읽기 포트 두 개와 쓰기 포트 하나가 있어서 한 사이클에 둘 읽고 하나 쓴다.
- ALU 가 더한다.
- 결과가 레지스터 파일의 r3 에 쓰인다. 그동안 PC 에는 4 가 더해져 다음 명령을 가리킨다.
이 다섯 단계가 CPU 의 전부다. 나머지 모든 복잡함은 이걸 더 빨리 하려는 장치 다.
파이프라인 — 단계를 겹친다
위 다섯 단계를 한 명령이 다 끝나고 다음 명령을 시작하면 사이클마다 다섯 단계 중 하나만 일한다. 그러지 말고 단계마다 다른 명령을 처리하게 하면 사이클마다 명령 하나가 끝난다. 이게 파이프라인이다.
파이프라인 단계 수를 늘리면 각 단계가 짧아져서 클럭을 올릴 수 있다. 그래서 실제 코어는 5단계가 아니라 15~20단계다. 대신 분기 예측이 틀렸을 때 버려야 하는 명령도 그만큼 많아진다. 분기 예측기가 그렇게 공들여진 이유가 이거다.
비순차 실행 — 기다리는 동안 딴 일을 한다
파이프라인이 잘 돌려면 매 사이클 명령이 들어와야 하는데, 메모리를 읽는 명령이 캐시에 없으면 DRAM 까지 다녀와야 하고 그게 300사이클쯤 걸린다. 순서대로 실행하면 그 뒤의 명령이 전부 멈춘다.
비순차 엔진은 명령 사이의 의존 관계를 본다. "②는 ①의 결과가 필요하지만 ③④는 아니다"를 알 수 있으면 ③④를 먼저 한다. 재정렬 버퍼가 결과를 원래 순서대로 다시 정렬해서 내보내므로 프로그램 입장에서는 아무 일도 없었던 것처럼 보인다.
이게 요즘 코어 트랜지스터의 큰 몫을 먹는다. 명령 수백 개의 의존 관계를 매 사이클 추적하고, 레지스터 이름을 물리 레지스터 200개에 다시 배정하고, 예측이 틀리면 되감는다. 계산 자체보다 계산을 계속 시키기 위한 관리 에 더 많은 회로가 들어간다.
메모리 계층 — 거리가 곧 시간이다
위 얘기가 전부 "메모리가 느리다"로 귀결되므로 마지막으로 그 거리감을 보자. 레지스터는 1사이클, L1 은 4~5, L2 는 14, L3 는 45, DRAM 은 300 안팎이다. 단위가 사이클이라는 걸 기억하자. 코어는 그 한 사이클에 덧셈을 서너 개 한다.
그래서 다이 면적의 절반 가까이를 캐시에 쓴다. 최근에 쓴 데이터는 다시 쓸 확률이 높고, 옆에 있는 데이터도 곧 쓸 확률이 높다는 두 가지 경험칙에 기대서, 자주 쓰는 걸 가까운 층에 복사해 둔다. 프로그램이 빠른지 느린지는 이 계층을 얼마나 잘 타느냐로 갈리는 경우가 많다.
정리
CPU 구조도를 한 줄로 요약하면 덧셈기 하나를 쉬지 않게 만들기 위한 장치들의 집합 이다.
명령을 미리 가져오고(프론트엔드, 분기 예측), 단계를 겹치고(파이프라인), 순서를 바꾸고(비순차 엔진), 데이터를 가까이 둔다(캐시). 이 넷이 코어 하나의 네 층이고, 그 코어를 여러 개 놓고 공유 캐시로 묶은 것이 다이 하나다.
이어서
실행 유닛의 핵심인 ALU 가 안에서 어떻게 생겼는지는 ALU 는 어떻게 생겼나에서 따로 뜯는다. 그 ALU 를 이루는 게이트와 덧셈기는 트랜지스터로 논리 게이트 만들기에, 게이트를 이루는 트랜지스터는 트랜지스터는 어떻게 생겼나에 정리해뒀다.
참고
- Central processing unit - Wikipedia — CPU 의 구성 요소와 동작 개요
- Multi-core processor - Wikipedia — 듀얼코어 등장 배경과 "코어" 용어
- Chiplet - Wikipedia — 코어 다이와 I/O 다이를 나누는 칩렛 구성
- Zen 4 - Wikipedia — 라이젠 7950X 의 CCD 2개 + IOD 구성
- Instruction pipelining - Wikipedia — 5단계 파이프라인과 단계 수 증가의 대가
- Out-of-order execution - Wikipedia — 재정렬 버퍼와 스케줄러
- Branch predictor - Wikipedia — 분기 예측의 원리와 적중률
- Cache hierarchy - Wikipedia — L1·L2·L3 의 크기와 지연
- Golden Cove - Wikipedia — 최근 데스크톱 코어의 디코더 폭, ROB 크기, 실행 포트 수
- Latency Numbers Every Programmer Should Know — 메모리 계층 접근 지연 수치