안녕하세요, 이번 챕터는 조금 쉬어가는 느낌입니다.
클럭, 코어, 스레드
데스크탑이나 노트북의 CPU를 알아보시다 보면 클럭, 코어, 스레드(쓰레드, Thread) 라는 용어들을 많이 접하실 거에요.
이것들이 무엇이고 어떻게 쓰이는지 간략하게 정리해보겠습니다.
먼저 CPU 입니다. CPU는 판매되는 중앙처리장치 패키지 전체를 지칭하는 용어입니다. 실제 연산을 하는 건 그 안의 코어입니다.
컴퓨터 안에서 부품들은 많은 것들이 동시에, 빠르게 움직입니다. 앞뒤 순서가 뒤집어지면 그 컴퓨터가 내놓는 연산 결과는 믿을 수가 없겠죠. 이런 순서를 지켜주기 위해서 컴퓨터의 부품들은 클럭 신호를 기준으로 움직입니다. 메트로놈 같이 순서를 맞춰주는 기준이 있다는 것입니다.
모두가 하나의 메트로놈을 기준으로 일을 하는데, 메트로놈의 박자가 빨라지면 다들 일하는 속도가 빨라지겠죠? CPU의 코어도 클럭 속도가 빨라지면 그만큼 처리 속도가 늘어납니다. 개인용 프로세서는 2023년 현재 최대 5GHz 수준인데, 이 속도는 1기가 가 10억이고 1Hz 는 초당 1회의 진동이므로 초당 50억 번 진동하는 메트로놈에 맞춰서 일을 한다는 거죠. 일을 빨리 하면 그만큼 힘든 것처럼, 클럭도 높아지면 CPU를 뜨겁게 만들며 전력을 더 소모합니다. CPU가 견딜 수 있는 온도에는 한계가 있으므로 무작정 클럭을 높이기는 어렵습니다.
더 높은 성능을 항상 원하지만, 클럭을 무작정 높일 수는 없으니까 적절한 클럭에서 일하는 코어를 늘리게 됩니다. 여러 일을 동시에 처리하면 성능이 더 좋아지겠죠. 다만 무작정 좋아지는 것은 아니고 그만큼 일이 잘 분할, 분배되어야 합니다. 조별 과제를 할 때 인원이 늘어난다고 그에 비례해서 생산성이 높아지지 않죠..
스레드라는 말은, 글자 그대로는 '실' 이라는 뜻 입니다. 어떤 프로그램이 실행되는 것이 실 처럼 이어지는 것처럼 보이죠. 이러한 실행 흐름 하나를 스레드라고 합니다. 우리가 컴퓨터를 쓰면서 하는 작업들은 아주 많고, 사용하는 프로그램의 수도 코어의 수보다도 많죠. 그리고 그 프로그램에서 동시에 여러 실행의 흐름이 발생하므로 수백, 수천의 쓰레드를 CPU가 처리해줘야 합니다.
우리가 지금까지 배워 온 코어에 대한 내용을 생각해보면, 한 코어는 하나의 스레드만 처리 할 수 있을 것 같습니다. 하지만 멀티스레드 처리가 가능한 코어를 가진 CPU가 출시되었습니다. 4코어 8스레드 CPU 같은 말을 들어보셨나요? 코어는 4개인데 이 CPU가 한번에 처리하는 스레드는 8개라는 말입니다. 이런 말에서 쓰인 스레드처럼, CPU가 한번에 처리 가능한 수의 스레드를 하드웨어적 스레드라고 부릅니다. 프로그램의 입장에서는 하드웨어 스레드와 실제 물리적인 코어 수(Physical core)를 구분할 수 없으므로 이러한 하드웨어적 스레드를 논리 프로세서(Logical processor)라고도 부릅니다.
참고
나중에 하나의 코어에서 두 개의 스레드를 처리하는 멀티 스레드 기술을 인텔이 개발합니다.
인텔은 이 기술에 하이퍼 스레딩(Hyper threading)이라는 이름을 붙였습니다.
한 코어에서 멀티 스레드를 처리한다고 해서 코어당 성능이 N 배가 되는 향상은 아닙니다.
여러 스레드를 한 코어에서 처리한다는 것을 간단히 말하면.. 인출, 실행, 간접 그리고 인터럽트 사이클 사이에서 코어가 쉬는 시간을 줄여서 최대한 효율을 끌어내는 기술입니다.
코어에서 처리할 수 있는 스레드의 수는 하드웨어적 스레드의 수이지만,
우리가 사용하는 여러 소프트웨어들은 실제로는 더 많은 스레드들의 처리를 필요로 합니다.
이런 소프트웨어의 관점에서 본 스레드들은 소프트웨어적 스레드라고 부릅니다.
소프트웨어적 스레드는 하나의 프로그램에서 실행되고 있는 독립된 실행들을 말합니다.
참고
메모리 다채널 구성 등 대역폭에 대해서 재밌는 비유를 하신 분이 있어서 영상 링크를 남겨봅니다.
명령어 병렬 처리 기법
바로 위에서 언급한 내용을 정리해보면,
- CPU의 성능은 클럭이 빠를수록 좋은데 클럭을 높이면 뜨거워지는 문제가 있어요
- 그래서 클럭은 제한하되 코어를 늘려서 일을 나눠서 처리해 성능을 높여요
라는 것이 골자입니다.
그런데 일을 나눠서 처리하는 것이 쉬운 일이 아닙니다.
이 분산을 더 잘하려하드웨어와 소프트웨어 개발자들이 명령어의 병렬 처리 기법을 발전시켰지요.
병렬처리 방법은 대표적으로 아래와 같은 방법들이 있습니다.
- 파이프라이닝
- 슈퍼스칼라
- 비순차적 명령어 처리
각각에 대해서 간단하게 알아보겠습니다.
파이프라이닝
파이프라이닝은 명령어 처리 사이클에서 놀고 있던 타이밍이 있던 회로들을 사용하는 방법입니다. 명령어의 인출, 해석, 실행, 결과 저장으로 대표되는 명령어 처리 사이클에서 어떤 회로들은 인출 때에만 일하고, 해석 때는 쉬는 등 노는 타이밍이 생길 수 있습니다. 이런 쉬는 타이밍에 다른 명령어의 처리 가능한 사이클을 처리해주는 것이 파이프라이닝입니다.
각 사이클별 명령어들을 처리해주는 파이프라인 같이 생기지 않았나요?
이렇게 파이프라이닝이 적용되었을 때, 병렬처리에 실패하는 경우도 생길 수 있습니다. 이를 파이프라이닝 위험(Pipelining hazard)라고 하며 크게 세가지로 구조화하는데요,
- 데이터 위험(Data hazard): 데이터 간 의존성 있는 경우는 동시에 처리 불가합니다.
- 제어 위험(Control hazard): 프로그램 흐름의 분기로 인해서 흐름이 변경되는 경우, 병렬 처리 중 이었던 명령어는 더 이상 사용 불가합니다. (프로그램 카운터의 갑작스러운 변화)
분기를 미리 예측할 수 있다면 제어 위험을 줄일 수 있습니다. (Branch prediction) - 구조적 위험(Structural hazard): 자원 위험(Resource hazard)라고도 부르는데, 서로 다른 명령어가 동시에 같은 ALU, Register 같은 자원을 쓰려고 할 때를 말합니다.
슈퍼스칼라
슈퍼스칼라는 다중 파이프라이닝 구조를 말합니다. 다중 파이프라이닝으로 명령어 병렬처리가 가능한 CPU를 슈퍼스칼라 프로세서나 슈퍼스칼라 CPU라고 부릅니다. 슈퍼스칼라 방식을 사용하는 CPU는 파이프라인 위험을 줄이기 위해 더욱 복잡한 설계를 갖게 됩니다.
비순차적 명령어 처리
마지막으로 살펴볼 명령어 병렬처리 기법은 비순차적 명령어 처리(OoOE; Out-of-order execution)입니다. 보통 OoOE로 줄여 발하는 기법으로 오늘날의 CPU에서는 대부분 사용하며 성능 향상에 크게 기여한 기법입니다.
비순차적 명령어 처리는 명령어의 순서를 일부 변경하여 파이프라이닝의 효율을 더욱 높이는 방법입니다.
순서를 어떻게 변경하길래 효율이 높아질까요? 어떤 명령어는 이전의 명령어들이 인출, 해석, 실행, 저장의 모든 사이클을 다 처리해야 실행될 수 있을 것입니다. 그럼 그와 상관 없이 데이터 의존성이 없는 명령어를 먼저 파이프라인에서 실행시키는 것입니다.
데이터 의존성이 있는 명령어의 예시:
다음과 같은 명령어들이 있다고 합시다.
...
명령어1 - 메모리 주소1에 값1을 저장
명령어2 - 메모리 주소2에 값2를 저장
명령어3 - 메모리 주소1과 메모리 주소2에 있는 값을 읽어와서 더하고, 그 값을 메모리 주소3에 저장
명령어4 - 메모리 주소4에 값3을 저장
명령어5 - 메모리 주소5에 값4를 저장
...
이 경우 명령어3보다 명령어 4, 5를 먼저 실행하면 더 빠르겠죠?
명령어3은 명령어1, 2의 사이클이 저장까지 완료되어야만 인출이 가능하니까요.
물론 쉬운 설계는 아닐겁니다. 명령어간 의존성 없음을 판단해야 하니까요.
CISC, RISC 알아보기
여기까지 CPU의 클럭, 코어, 스레드부터 명령어 병렬처리 기법에 대해 알아봤습니다. 스레드에서 명령어를 처리하는 것이 아주 중요하다는 것을 느낄 수 있는데요. 그러면 이 '명령어' 자체에 대해서도 한번 알아보고 갑시다.
이전 챕터[3]에서 명령어는 연산 코드와 오퍼랜드로 이뤄졌다고 했습니다. 명령어의 기본적인 구조는 대부분 유사하나 CPU 제조사마다 실제 명령어는 아주 다를 수 있습니다. 그 차이는 명령어 집합 구조(ISA; Instruction Set Architecture)에 따라 나타납니다. CISC와 RISC는 이런 명령어 집합 구조를 크게 두 종으로 분류한 것입니다.
ISA(Instruction set) 명령어 집합 구조
2023년 현재 나이가 30이 넘어가시는 분들 중에 인텔 CPU를 안 써보신 분은 아마 없으실 겁니다. 인텔의 명령어 집합 구조는 x86 혹은 x86-64 입니다. 이런 명령어 집합 구조가 호환되지 않으면 실행 파일들 또한 호환되지 않습니다. ISA는 CPU들의 명령어 언어라고 할 수 있죠.
RISC와 CISC
위에서 언급한 ISA에 따라서 컴퓨터를 크게 두 가지로 구분할 수 있는데요, 복잡한(길이가 긴) 명령어를 사용하는 CISC(Complex Instruction Set Computer)와 일정한 길이의 명령어를 사용하는 RISC(Reduced Instruction Set Computer)입니다. 왜 CISC와 RISC를 갑자기 언급하는지 궁금하실 수 있습니다. 병렬처리와 같은 CPU 성능은 ISA, 즉 명령어 집합 구조에 따라서 많은 영향을 받기 때문입니다.
노트북(랩탑)이나 데스크탑 컴퓨터에서 많이 쓰는 CPU는 대부분 x86-64 명령어 집합으로 이런 컴퓨터들은 CISC입니다. 명령어가 복잡하다는 건 길이가 길다는 것이고, 여기에 더해서 각 명령어마다의 길이도 다를 수 있습니다. 병렬처리를 위한 파이프라이닝, 슈퍼스칼라, 비순차적 명령어 처리의 적용이 어려워질 수 있다는 것입니다. 병렬 처리의 효용이 떨어지면 성능이 떨어지고, 성능이 떨어지면 당연히 전성비(전력 대 성능비)도 떨어지게 됩니다. CISC는 가변 길이 명령어로, RISC는 고정 길이 명령어로 보셔도 됩니다.
RISC는 고정된 길이의 명령어를 사용하는데 이에 따라 명령어 파이프라이닝에 훨씬 유리합니다. (대부분 1클럭 내외로 실행되는 명령어로 설계됩니다.) CISC는 명령어가 규격화되어 있기에 파이프라이닝에 최적화되고, 명령어의 수 자체도 CISC보다 적은 ISA입니다. 메모리에 접근하는 명령어도 load와 store 두 개로 제한할 정도로 단순화와 최소화를 추구합니다. (이런 점에서 RISC를 load-store 구조라고 부르기도 합니다.) 이러한 명령어 개수의 제한을 해결하기 위해 레지스터를 CISC보다 더 많이 이용해야 합니다. 같은 결과를 얻을 때도 CISC보다 더 많은 명령어를 처리해야 합니다.
애플은 자사 MacOS용 CPU를 CISC에서 RISC용 CPU로 자체설계하여 바꾸고 굉장히 높은 성능 향상을 얻어냈습니다. m1부터 m2, m3 까지 출시된 2023년 말의 현재, 애플의 선택이 틀리지 않았다는 걸 알 수 있습니다.