CPU의 성능 향상 기법
빠른 CPU를 위한 설계 기법
어떻게 하면 CPU의 속도를 보다 빠르게 만들 수 있을까?
앞서 컴퓨터 부품들은 '클럭 신호'에 맞춰 움직이며, CPU는 '명령어 사이클'이라는 정해진 흐름에 맞춰 명령어들을 실행한다고 배웠다.
그러면 클럭 신호를 빠르게 하면 될까?
항상 그런 것은 아니지만 일반적으로는 그렇다. 클럭 신호를 빠르게 하면 CPU를 비롯한 컴퓨터 부품들은 빨라진 클럭 신호에 맞춰 움직인다.
그래서 클럭 속도는 CPU의 속도 단위로 간주되기도 한다.
- 클럭 속도 : 헤르츠(Hz) 단위로 측정
- 헤르츠(Hz) : 1초에 클럭이 반복되는 횟수
- 클럭이 1초에 한 번 반복되면 1Hz
- 클럭이 1초에 100번 반복되면 100Hz
클럭 신호를 계속 높이면 CPU가 마냥 빨라지기만 할까?
꼭 그렇지는 않다. 필요 이상으로 클럭을 높이면 발열이 심각해진다.
그렇다면 클럭 속도를 늘리는 방법 말고는 어떤 방법이 있을까? 그것은 코어 수를 늘리는 방법(듀얼, 멀티코어 ...)과 스레드 수를 늘리는 방법이 있다.(멀티 스레드...)
코어
- 전통적으로 '명령어를 실행하는 부품'은 원칙적으로 하나만 존재
- 하지만 오늘날 CPU에는 '명령어를 실행하는 부품'이 여러 개 존재
- '명령어를 실행하는 부품'을 코어라는 용어로 사용
만약 코어를 두 개, 세 개, ... 100개 늘리면 연산 속도도 비례해서 빨라질까?
꼭 코어 수에 비례해서 연산 속도가 증가하지 않는다.
스레드
하드웨어 스레드
- 하나의 코어가 동시에 처리하는 명령어 단위
- 논리 프로세서라고도 부른다.
멀티스레드 프로세서를 실제로 설계하는 일은 매우 복잡하지만, 가장 큰 핵심은 레지스터이다.
하나의 코어에 레지스터 세트가 여러 개 있다면 하나의 코어가 여러 명령어를 동시에 처리할 수 있다.
CPU의 코어와 스레드 의 수는 CPU만 알고 있지 메모리 내의 프로그램은 CPU의 코어와 스레드의 수를 알 수 없다. 따라서 하드웨어 스레드를 논리 프로세서라고도 부른다.
소프트웨어 스레드
하나의 프로그램에서 독립적으로 실행되는 단위
문서 프로그램을 개발할 때 아래와 같은 기능이 동시에 실행되도록 하고 싶다고 해보자.
- 사용자로부터 입력받은 내용을 화면에 보여 주는 기능
- 사용자가 입력한 내용이 맞춤법에 맞는지 검사하는 기능
- 사용자가 입력한 내용을 수시로 저장하는 기능
위처럼 3개의 스레드를 만들면 된다.
참고로 1코어 1 스레드 CPU도 여러 소프트웨어적 스레드를 만들 수 있다.
명령어 병렬 처리 기법
명령어 파이프라인
명령어가 처리되는 과정을 비슷한 시간 간격으로 나누어보자
- 명령어 인출
- 명령어 해석
- 명령어 실행
- 결과 저장
참고로 전공서에 따라 인출 -> 실행으로 나누기도 하고 인출 -> 해석 -> 실행 -> 접근 -> 결과 저장으로 나누기도 한다.
같은 단계가 겹치지만 않는다면 CPU는 각 단계를 동시에 실행할 수 있다.
명령어 파이프라인을 사용하지 않는다면 아래와 같을 것이다.
명령어 파이프라인을 사용할 때보다 훨씬 오랜 시간이 걸린다. 따라서 현대 CPU에서는 명령어를 겹쳐서 동시에 실행하는 기법인 명령어 파이프라인이 중요한 기술이 되었다.
명령어 파이프라인을 공부할 때 같이 알아두어야 할 것이 파이프라인 위험이다.
파이프라인 위험
명령어 파이프라인이 성능 향상에 실패하는 경우
데이터 위험
명령어 간의 의존성에 의해 발생한다.
모든 명령어를 동시에 처리할 수는 없는 경우도 있다. 이전 명령어를 끝까지 실행해야만 비로소 실행할 수 있는 경우이다.
이 경우에는 명령어 1을 해석하는 동안 명령어 2를 인출할 수 없다. R1의 값이 결정되어야만 명령어 2를 인출할 수 있기 때문이다.
제어 위험
프로그램 카운터의 갑작스러운 변화로 인해 발생한다.
참고로 이러한 상황을 방지하기 위해 프로그램 카운터가 어디로 jump 할 것인지 미리 예측하는 '분기 예측'(branch prediction)이라는 기술이 있다.
구조 위험
서로 다른 명령어가 같은 CPU 부품(ALU, 레지스터)를 쓰려고 할 때 발생한다.
슈퍼스칼라
- CPU 내부에 여러 개의 명령어 파이프라인을 포함한 구조
- 슈퍼스칼라 기법을 사용하면 이론적으로는 파이프라인 개수에 비례하여 처리 속도가 증가하지만 파이프라인 위험도의 증가로 인해 파이프라인 개수에 비례하여 처리 속도가 증가하지는 않다.
비순차적 명령어 처리
지금까지 설명한 방식들은 모두 순차적으로 명령어를 처리하는 방식이었다.
하지만 위의 명령어를 순차적으로 처리하면 이상적으로 명령어를 겹쳐서 실행할 수 없다.
여기서 의존성이 없는 명령어의 순서를 바꾸게 된다면
아래와 같이 보다 이상적인 형태로 명령어 파이프라인을 실행할 수 있다.
하지만 아무 명령어나 순서를 바꿀 수는 없다.
여기서 1, 3의 순서는 바꿀 수 없다. 왜냐하면 3의 수행을 위해 1이 필요하기 때문이다.
또한, 1, 4의 순서를 바꿀 수없다. 왜냐하면 1을 토대로 3이 수행되고 3을 토대로 4가 수행되기 때문이다.
4, 5처럼 의존성이 없는 명령어만 순서를 바꿀 수 있다.
명령어 집합 구조, CISC와 RISC
명령어가 어떻게 생겨야 명령어 파이프라이닝에 유리할까?
명령어 집합
CPU는 명령어를 실행하는데, 이 세상의 모든 CPU가 똑같이 생긴 명령어를 실행할까? 그렇지 않다. CPU 제조사별로 다르고 같은 제조사에서 만든 CPU라도 명령어의 세세한 생김새, 연산, 주소 지정 방식은 다를 수 있다.
명령어 집합 (구조) : CPU가 이해할 수 있는 명령어들의 모음
명령어 집합 (구조)은 CPU의 언어라고 생각할 수 있다.
명령어가 달라지면 그에 따라 명령어 해석 방식, 레지스터의 종류와 개수, 파이프라이닝의 용이성 등 많은 것들이 달라진다.
명령어 집합의 두 축으로 CISC와 RISC가 있다.
CISC(Complex Instruction Set Computer)
- 복잡한 명령어 집합을 활용하는 컴퓨터(CPU)
- x86, x86-64는 CISC 기반 명령어 집합 구조이다.
- 복잡하고 다양한 명령어를 활용
- 명령어의 형태와 크기가 다양한 가변 길이 명령어를 활용
- 다양하고 강력한 명령어를 활용
- 상대적으로 적은 수의 명령어로도 프로그램을 실행할 수 있다.
- 메모리를 최대한 아끼며 개발해야 했던 시절에 인기가 높았으나 명령어 파이프라이닝이 불리하다는 치명적인 단점이 있다.
- 명령어가 워낙 복잡하고 다양한 기능을 제공하는 탓에 명령어의 크기와 실행되기까지의 시간이 일정하지 않다.
- 복잡한 명령어 때문에 명령어 하나를 실행하는 데에 여러 클럭 주기가 필요하다.
대다수의 복잡한 명령어는 사용 빈도가 낮다.
RISC(Reduced Instruction Set Computer)
명령어의 종류가 적고, 짧고 규격화된 명령어 사용
- RISC는 메모리 접근을 최소화하고 레지스터를 활용하려는 경향이 있다.
- 명령어 종류가 CISC보다 적기에 더 많은 명령어로 프로그램을 동작시킨다.
CISC와 RISC에 대해 정리하면
참고로 현대에는 CISC 기반의 프로세서라도 CPU 내부를 보면 명령어를 실행할 때 잘게 쪼개서 실행하기 때문에 내부적으로는 RISC처럼 사용을 한다.