[컴퓨터 구조론 이론] CPU의 성능 향상 기법

#cs
Written by Sungbin2026년 2월 23일 · 5 min read

시리즈의 글 (8개)

  1. [컴퓨터 구조론 이론] 컴퓨터 구조 시작하기
  2. [컴퓨터 구조론 이론] 데이터
  3. [컴퓨터 구조론 이론] 명령어
  4. [컴퓨터 구조론 이론] CPU의 작동 원리
  5. [컴퓨터 구조론 이론] CPU의 성능 향상 기법
  6. [컴퓨터 구조론 이론] 메모리와 캐시 메모리
  7. [컴퓨터 구조론 이론] 보조기억장치
  8. [컴퓨터 구조론 이론] 입출력 장치

banner

본 포스팅은 인프런의 개발자를 위한 컴퓨터공학 1: 혼자 공부하는 컴퓨터구조 + 운영체제를 참조하여 작성한 글입니다.

빠른 CPU를 위한 설계 기법

이번에는 클럭, 멀티코어, 멀티스레드가 각각 무엇인지 알아보고 이들이 CPU 속도와 어떤 관계가 있는지 알아보도록 하겠다.

클럭

우리가 CPU를 설계하는 엔지니어라고 생각해보자. 그러면 좋은 CPU를 만들기 위해서는 클럭신확 빠르게 반복되면 CPU를 비롯한 컴퓨터 부품둘은 그 만큼 빠른 박자에 맞춰서 움직일까? 정답은 꼭 그런것은 아니지만 일반적으로 맞다.

클럭 속도란 Hz단위로 측정이된다. Hz는 1초에 클럭이 반복되는 횟수이며 클럭이 똑-딱하고 1초에 한번 반복되면 1Hz이고 1초에 100번 반복된다면 100Hz로 표기를 한다. 실제 인텔 CPU같은 경우 1초에 기본적으로 25억번, 최대 많게는 49억번이 반복된다.

Hz가 정해졌다라고 해도 CPU가 딱 거기에 맞게 클럭이 움직이는게 아니라 상황에 따라 달리 움직여 최대 클럭 속도도 표기를 하는 것이다.

그러면 클럭 속도를 무조건 높이면 무조건 CPU가 빨라질까? 정답은 아니다. 왜냐하면 필요 이상으로 클럭을 높이면 발열이 심각하기 때문이다.

대표적으로 이러한 작업의 예시로 게임 혹은 영상 편집이 존재한다.

코어와 멀티코어

그러면 클럭속도를 늘리는 방법 이외에 어떤 방법이 있을까? 코어 수를 늘리는 방법이 있고 스레드 수를 늘리는 방법도 존재한다. 그러면 코어와 스레드에 대해서 자세히 살펴보자.

현대적인 관점에서 CPU라는 용어는 재해석이 되곤 한다. 원래 CPU는 명령어를 해석하고 실행하는 부품이라고 알고 있다. 원래 전통적으로는 이러한 부품이 1개만 존재했다. 하지만 오늘날 CPU는 명령어를 실행하는 부품이 여러개 존재한다. 그 중에 하나가 바로 코어이다.

또한, 코어를 여러개 포함하고 있는 CPU를 멀티코어 또는 멀티코어 프로세서라고 부른다. 그러면 코어수를 늘리면 그것에 비례하여 CPU의 성능이 좋아질까? 그것은 아니다. 중요한 것은 코어마다 처리할 명령어들을 얼마나 적절하게 분배하느냐이고 그에 따라 연산속도는 크게 달라진다.

스레드와 멀티스레드

스레드란, 용어사전을 찾아보면 '실행 흐름의 단위'라고 정의되어 있다. 하지만 정확히는 스레드는 2가지 종류로 나눠져 있다. 바로 하드웨어 스레드와 소프트웨어 스레드이다. 하드웨어 스레드란 하나의 코어가 동시에 처리하는 명령어 단위를 말한다. 그래서 1코어 1스레드는 한 번에 하나씩 명령어를 실행하는 CPU라고 한다. 반면 여러 스레드를 지원하는 CPU는 하나의 코어로도 여러개의 명령어를 동시에 실행할 수 있다. 이처럼 하나의 코어로 여러 명령어를 동시에 처리하는 CPU를 멀티스레드 프로세서 또는 멀티스레드 CPU라고 한다.

참고로 멀티스레드와 함께 우리가 자주 접할 용어로 하이퍼 스레딩이라는 용어인데 이것은 인텔 CPU가 멀티스레드를 사용하는 기술을 뜻한다.

그러면 하드웨어 스레드와 반대의 개념인 소프트웨어 스레드를 알아보자. 소프트웨어 스레드는 하나의 프로그램에서 독립적으로 실행되는 단위를 의미한다. 만약 아래의 기능들을 만든다고 해보자.

  • 사용자로부터 입력받은 내용을 화면에 보여주는 기능
  • 사용자가 입력한 내용이 맞춤법에 맞는지 검사하는 기능
  • 사용자가 입력한 내용을 수시로 저장하는 기능

이 기능들을 작동시키는 코드를 각각의 스레드로 만들면 동시에 실행이 가능하다. 정리하면 스레드의 하드웨어적 정의는 하나의 코어가 동시에 처리하는 명령어 단위를 의미하고 소프트웨어적 정의는 하나의 프로그램에서 독립적으로 실행되는 단위를 의미한다. 그래서 1코어 1스레드 CPU도 여러 소프트웨어적 스레드를 만들어서 처리할 수 있다.

멀티스레드 프로세서

멀티스레드 프로세서를 만드는 일은 매우 복잡하다. 하지만 가장 중요한 핵심만 잘 구현하면 그리 어려운 것도 아니다. 그 핵심이 바로 레지스터이다. 하나의 코어로 여러 명령어를 동시에 처리하도록 만들러면 프로그램 카운터, 스택 포인터, 메모리 버퍼 레지스터, 메모리 주소 레지스터와 같이 하나의 명령어를 처리하기 위해 꼭 필요한 레지스터를 여러 개 가지고 있으면 된다. 이렇게 하나의 명령어를 실행하기 위해 꼭 필요한 레지스터들을 편의상 레지스터 세트라고 표기한다.

하드웨어 스레드를 이용해 하나의 코어로도 여러 명령어를 동시에 처리할 수 있다고 하였다. 그러나 메모리 속 프로그램의 입장에서 보았을 때 하드웨어 스레드는 한번에 하나의 명령어를 처리하는 CPU나 다를것이 없다. 그래서 하드웨어 스레드를 논리 프로세서라고 부르기도 한다.

명령어 병렬 처리 기법

이번에는 명령어를 동시에 처리하여 CPU를 한시도 쉬지 않고 작동시키는 기법인 명령어 병렬 처리 기법을 알아보도록 하겠다. 대표적인 명령어 병렬 처리 기법에는 명령어 파이프라이닝, 슈퍼 스칼라, 비순차적 명령어 처리가 존재한다.

명령어 파이프라인

명령어 파이프라인을 이해하려면 하나의 명령어가 처리되는 전체 과정을 비슷한 시간 간격으로 나눠봐야 한다. 명령어 처리 과정을 클럭 단위로 나누어 보면 일반적으로 다음과 같이 나눌 수 있다.

  • 명령어 인출
  • 명령어 해석
  • 명령어 실행
  • 결과 저장

여기서 중요한 점은 같은 단계가 겹치지만 않는다면 CPU는 각 단계를 동시에 실행할 수 있다라는 것이다. 바로 명령어를 겹쳐서 수행하면 된다. 명령어를 겹쳐서 수행하면 명령어를 하나하나 실행하는 것보다 훨씬 효율적일 것이다. 이처럼 마치 공장 생산 라인같이 명령어들을 명령어 파이프라인에 넣고 동시에 처리하는 기법을 명령어 파이프라이닝이라고 한다. 만약 명령어 파이프라이닝 기법을 사용하지 않고 모든 명령어를 순차적으로만 처리한다면 아마 매우 비효율적일 것이다.

파이프라이닝은 높은 성능을 가져오지만 특정 상황에서는 성능 향상에 실패하는 경우도 있다. 이러한 상황을 파이프라인 위험이라고 부른다. 파이프라인 위험에는 크게 데이터 위험, 제어 위험, 구조적 위험이 있다.

데이터 위험은 명령어간의 의존성에 의해 야기를 한다. 예를 들어 명령어1에서 A레지스터와 B레지스터를 더하는 명령어가 있고 명령어2에서 명령어1의 결과를 가지고 연산을 한다하면 이것은 동시에 처리가 불가능하다. 이처럼 모든 명령어를 동시에 처리가 불가능하다.(이전 명령어를 끝까지 실행해야만 비로소 실행할 수 있는 경우)

제어 위험은 프로그램 카운터의 갑작스러운 변화가 발생할 때 발생한다. 기본적으로 프로그램 카운터는 현재 실행 중인 명령어의 다음 주소로 갱신된다. 하지만 프로그램 실행 흐름이 바뀌어 명령어가 실행되면서 프로그램 카운터 값에 갑작스러운 변화가 생긴다면 명령어 파이프라인에 미리 가지고 와서 처리 중이었던 명령어들은 아무 쓸모가 없어진다. 이것이 바로 제어 위험이다. 물론 이런 위험을 방지하기 위해 CPU가 분기 예측을 한다. 분기 예측이란 어느 분기로 점프를 할지 미리 예측을 하는 기법을 말한다.

구조적 위험이란 서로 다른 명령어가 같은 CPU 부품(ALU, 레지스터)를 쓰려고 할 때 발생한다.

슈퍼스칼라

파이프라이닝은 단일 파이프라인으로 구현이 가능하지만 오늘날 대부분의 CPU에서는 여러 개의 파이프라인을 이용한다. 이처럼 CPU 내부에 여러 개의 명령어 파이프라인을 포함한 구조를 슈퍼스칼라라고 한다. 이론적으로 파이프라인 개수에 비례하여 처리속도는 증가한다. 하지만 파이프라인 위험도의 증가로 인해 파이프라인 개수에 비례하여 처리속도가 증가하지는 않는다.

비순차적 명령어 처리

비순차적 명령어 처리는 비유적으로 '합법적 새치기'라고 표현한다. 예를 들어 아래의 명령어가 있다고 하자.

  • M(100) - 1
  • M(101) - 2
  • M(102) - M(100) + M(101)
  • M(150) - 1
  • M(151) - 2
  • M(152) - 3

여기서 3번째 명령어는 1번과 2번이 이루어져야 한다. 하지만 순서를 의존성 없이 아래와 같이 변경해도 된다.

  • M(100) - 1
  • M(101) - 2
  • M(150) - 1
  • M(151) - 2
  • M(152) - 3
  • M(102) - M(100) + M(101)

이렇게 되면 의존성 없이 결과는 동일하지만 파이프라이닝 단계는 줄어서 더 효율적이다. 이런것을 비순차적 명령어 처리 기법이라고 한다. 그렇다고 아무 명령어나 순서를 바꿔서 수행할 수는 없다. 비순차적 명령어 처리가 가능한 CPU는 명령어들이 어떤 명령어와 데이터 의존성을 가지고 있는지, 순서를 바꿔 실행을 할 수 있는 명령어에는 어떤 것들이 있는지를 판단할 수 있어야 한다.

명령어 집합 구조, CISC와 RISC

파이프라이닝하기 쉬운 명령어란 무엇일까? 명령어가 어떻게 생겨야 파이프라이닝에 유리할까? 이와 관련해 CPU의 언어인 ISA와 각기 다른 성격의 ISA를 기반으로 설계된 CISC, RISC를 알아보자.

명령어 집합 (ISA)

CPU는 명령어를 실행한다. 그런데 이 세상의 모든 CPU가 똑같이 생긴 명령어를 실행할까? 그것은 아니다. 명령어의 세세한 생김새, 연산 주소 지정 방식등은 CPU마다 다르다. 즉, 인텔에서의 명령어와 맥북의 명령어가 다른 것처럼 말이다.

명령어 집합: CPU가 이해할 수 있는 명령어들의 모음

즉, 이렇게 보면 명령어 집합은 CPU의 언어로도 볼 수 있다.

명령어가 달라지면 그에 대한 나비효과로 많은 것들이 달라진다. 명령어 해석 방식, 레지스터의 종류와 개수, 파이프라이닝 용이성등 정말 많은 것이 달라지는 것이다. 또한 이런 명령어 집합들은 현대에 와서 대표적으로 CISC와 RISC로 나뉜다. 마치 영어와 한국어처럼 말이다.

CISC

CISC란 복잡한 명령어 집합을 활용하는 CPU이다. x86, x86-64는 CISC기반 명령어 집합 구조를 나타낸다. CISC는 복잡하고 다양한 명령어들을 활용한다. 또한 명령어의 형태와 크기에 다양한 가변 길이 명령어를 활용한다. 또한, 다양하고 강력한 명령어를 활용함으로 상대적으로 적은 수의 명령어로도 프로그램을 실행할 수 있다. 이런 CISC가 나왔을 때는 메모리를 최대한 아끼며 개발해야 했던 시절이였고 이 시절에는 인기가 높았으니 명령어 파이프라이닝이 불리하다는 치명적인 단점이 존재했다.

명령어가 복잡하고 다양한 기능을 제공하는 탓에 명령어의 크기와 실행되기까지의 시간이 일정하지 않다. 복잡한 명령어때문에 명령어 하나를 실행하는데에 여러 클럭 주기가 필요했다. 게다가 대다수의 복잡한 명령어는 사용빈도가 매우 낮았다.

RISC

그래서 나온 것이 RISC이다. RISC는 명령어의 종류가 적고 짧고 규격화된 명령어를 사용한다. 이를 고정길이 명령어 방식이라고 한다. RISC는 메모리 접근을 최소화하고 있는 기법이며 이로 인해 다양한 범용 레지스터가 많다. 다만, 명령어 종류가 CISC보다 적기 때문에 더 많은 명령어로 프로그램을 동작시킨다.