카테고리 없음

[혼공컴운] 2주차 | CPU의 작동 원리, CPU 성능 향상 기법

muncaem 2024. 7. 14. 13:23

이번 주차는

▶Chapter 4 CPU의 작동 원리

▶ Chapter 5 CPU 성능 향상 기법 에 대해 정리해볼 것이다.


Chapter 4 CPU의 작동 원리에선 CPU의 구성 요소를 자세하게 학습할 수 있었다.

 

먼저 ALU와 제어장치에 대해 알아보자.

 

ALU 는 앞장에서 배웠듯이 CPU에서 계산을 처리하는 부품이며 산술논리연산장치이다.

ALU가 받아들이는📥정보와 내보내는📤 정보가 있는데 

  • 받아들이는 정보에는
    • 연산을 수행하기 위해 레지스터로부터 피연산자를 받는다.
    • 제어장치로부터 수행할 연산을 알려주는 제어 신호를 받는다.
  • 내보내는 정보에는
    • 연산을 수행한 수행한 결과로 특정 숫자나 문자를 레지스터에 일시적으로 저장한다.
    • 연산 결과에 대한 추가적인 상태 정보(음수, 오버플로우 등)를 플래그라고 하는데 이를 내보낸다.

(+) ALU가 내보내는 대표적인 플래그를 살펴보자.

플래그 종류 의미
부호 플래그 연산한 결과의 부호를 나타낸다. 1일 경우 음수
제로 플래그 연산 결과가 0인지 여부를 나타낸다. 1일 경우 연산 결과 0
캐리 플래그 연산 결과 올림수나 빌림수가 발생했는지를 나타낸다. 1일 경우 발생
오버플로우 플래그 오버플로우가 발생했는지를 나타낸다. 1일 경우 발생
인터럽트 플래그 인터럽트가 가능한지를 나타낸다. 1일 경우 가능
슈퍼바이저 플래그 커널 모드로 실행 중인지, 사용자 모드로 실행 중인지 나타낸다. 1일 경우 커널

 

위의 플래그들은 플래그 레지스터에 저장된다.

 

 

「 제어장치」는 제어 신호를 내보내고 명령어를 해석하는 부품이다.

여기서 제어 신호 」는 컴퓨터 부품들을 관리하고 작동시키기 위한 일종의 전기 신호이다.

  • 받아들이는 정보
    • 클럭 신호
      여기서 클럭이란, 컴퓨터의 모든 부품을 움직일 수 있게 하는 시간 단위이다. 주기에 맞춰 동작한다.
    • 해석해야 할 명령어
      명령에 레지스터에서 CPU가 해석해야 할 명령어를 받는다.
    • 플래그 레지스터의 플래그
      플래그가 나타내는 중요한 참고 사항을  받아들이고 이를 참고하여 제어 신호를 발생시킨다.
    • 제어 버스로 전달된 제어 신호
      제어 버스를 통해 CPU 외부 장치로부터 전달된 제어 신호를 받는다.
  • 전달하는 신호
    • CPU 외부로 (제어 버스를 통해)
      • 메모리에 전달하는 제어 신호
      • 입출력 장치에 전달하는 제어 신호
    • CPU 내부에
      • ALU에 수행할 연산 지시 위한 제어 신호
      • 레지스터에 레지스터 간 데이터 이동을 위한 제어 신호 or 저장된 명령어를 해석하기 위해 제어 신호 보낸다.

 

 

다음으로 레지스터에 대해 알아보자.

 CPU마다 다양한 종류의 레지스터가 있지만 많은 CPU가 공통으로 포함하고 있는 8개의 레지스터가 있다.

  •  프로그램 카운터 (= 명령어 포인터)
    메모리에서 읽어 들일 명령어의 주소를 저장한다.
    1000이 저장되어 있으면 메모리에서 가져올 명령어가 1000번지에 있다는 것을 의미.

  • 명령어 레지스터
    방금 메모리에서 읽어 들인 명령어를 임시로 저장하는 레지스터이다. 
    제어장치는 이 레지스터 안의 명령어를 받아서 해석한 뒤 제어 신호를 내보낸다.

  • 메모리 주소 레지스터
    메모리의 주소를 저장하는 레지스터이다. CPU가 읽어 들이고자 하는 주소 값을 주소 버스로 보낼 때 여기를 거친다.
    1000번지를 읽기 위해 주소 버스로 1000번지를 내보내야 함. 이를 위해 메모리 주소 레지스터에 1000 저장
    제어 버스를 통해 메모리 읽기 제어 신호가, 주소 버스를 통해 메모리 주소 레지스터 값이 메모리로 보내짐

  • 메모리 버퍼 레지스터 (= 메모리 데이터 레지스터)
    메모리에 쓰고 싶은 값이나 메모리로부터 전달받은 값이 여기를 거친다. 데이터 버스로 주고 받을 값이 이를 통한다.
    1000번지에 저장된 값이 데이터 버스를 통해 메모리 버퍼 레지스터로 전달됨
    프로그램 카운터 값이 증가함, 메모리 버퍼 레지스터에 저장된 값이 명령어 레지스터로 이동함


  • 플래그 레지스터
    연산 결과 또는 CPU 상태에 대한 부가적인 정보를 저장한다.

  • 범용 레지스터
    다양하고 일반적인 상황에서 자유롭게 사용할 수 있는 레지스터이다. 데이터와 주소를 모두 저장할 수 있으며 CPU 안에는 여러 개의 범용 레지스터가 있다.

 

스택 포인터와 베이스 레지스터를 정의하기 전에 특정 레지스터를 이용한 주소 지정 방식에 대해 알고가자.

 

1. 스택 주소 지정 방식

스택과 스택 포인터를 이용한 주소 지정 방식이다. 메모리 안에 스택 영역이라는 스택처럼 사용할 수 있는 영역이 정해져 있다. 스택 포인터가 가리키고 있는 주소는 스택의 꼭대기이며 스택의 어디까지 데이터가 채워져 있는지에 대한 표시이다. 

  • 스택포인터
    스택의 꼭대기인 마지막으로 저장한 값의 위치를 저장하는 레지스터이다.

2. 변위 주소 지정 방식

오퍼랜드 필드의 값(변위)과 특정 레지스터의 값을 더하여 유효 주소를 얻어내는 주소 지정 방식이다.

따라서 변위 주소 지정 방식을 사용하는 명령어는 아래와 같은 필드를 가진다.

연산 코드 레지스터 오퍼랜드

 

2-1. 상대 주소 지정 방식

프로그램 카운터 + 오퍼랜드 의 값을 통해 유효 주소를 얻는 방식이다. if문과 유사하게 모든 코드 실행하는 게 아닌 분기하여 특정 주소의 코들르 실행할 때 사용된다.

 

2-2. 베이스 레지스터 주소 지정 방식

베이스 레지스터 값 + 오퍼랜드 의 값을 통해 유효 주소를 얻는방식이다. 베이스 레지스터는 '기준 주소', 오퍼랜드는 '기준 주소로부터 떨어진 거리'이다.  

  • 베이스 레지스터
    기준 주소를 저장하는 레지스터이다.

 

 

 

CPU가 하나의 명령어를 처리하는 흐름인 명령어 사이클과 그 흐름을 방해하는 인터럽트에 대해 알아보자.

「명령어 사이클」이란, 프로그램 속 각각의 명령어들이 반복되며 실행되는 일정한 주기이다.

  • 인출 사이클
    메모리에 있는 명령어를 CPU로 가져오는 단계이다.
  • 실행 사이클
    CPU로 가져온 명령어를 실행하는 단계이다. 제어장치가 명령어 레지스터에 담긴 값을 해석하고 제어 신호를 발생하는 단계다.

일반적으로는 인출 사이클 -> 실행 사이클 -> 인출 사이클 -> 실행 사이클 ... 을 반복하며 실행된다.

  • 간접 사이클
    간접 주소 지정 방식 같이 명령어를 실행하기 위해서 메모리 접근을 한 번 더 해야하는 경우 간접 사이클이 추가된다.

 

 

「인터럽트」란, CPU의 작업을 방해하는 신호이다.

CPU가 꼭 주목해야 할 때 or CPU가 얼른 처리해야 할 다른 작업이 생겼을 때 발생한다.

 

인터럽트는 다음과 같이 구분할 수 있다.

  • 동기 인터럽트 (= 예외 exception)
    CPU에 의해 발생하는 인터럽트이다.
    예상치 못한 상황에 마주쳤거나 오류와 같은 예외적인 상황에 마주쳤을 때 발생한다.

  • 비동기 인터럽트 (= 하드웨터 인터럽트 라고 하자.)
    입출력 장치에 의해 발생하는 인터럽트이다.
    동작 완료를 나타내는 알림과 같은 인터럽트이다. 하드웨터 인터럽트를 이용하면 CPU는 주기적으로 입출력 장치의 완료 여부를 확인할 필요가 없으며 완료 인터럽트를 받을 때까지 다른 작업을 처리할 수 있으므로 명령어를 효율적으로 처리할 수 있다.

(+) 하드웨어 인터럽트 처리 순서

  1. 입출력 장치는 CPU에 인터럽트 요청 신호를 보낸다.
    *인터럽트 요청 신호: 끼어들어도 되는지 CPU에 물어보는 신호
  2. CPU는 실행 사이클이 끝나고 명령어를 인출하기 전 항상 인터럽트 여부를 확인한다.
  3. CPU는 인터럽트 요청을 확인하고 인터럽트 플래그를 통해 현재 인터럽트를 받아들일 수 있는지 여부를 확인한다.
    *수용하기 위해선 인터럽트 플래그가 활성화되어 있어야 함, 불가능이면 인터럽트 요청을 무시
    *무시할 수 없는 인터럽트 요청은 정전이나 하드웨어 고장으로 인한 인터럽트
  4. 인터럽트를 받아들일 수 있다면 CPU는 지금까지의 작업을 백업한다.
  5. CPU는 인터럽트 벡터를 참조하여 인터럽트 서비스 루틴을 실행한다.
    *인터럽트 서비스 루틴: 인터럽트를 처리하기 위한 프로그램, 어떤 인터럽트가 발생했을 때 해당 인터럽트를 어떻게 처리하고 작동해야 할지에 대한 정보로 이루어진 프로그램
    * 인터럽트 벡터: 인터럽트 서비스 루틴을 식별하기 위한 정보, 인터럽트 서비스 루틴의 시작 주소를 알 수 있음
  6. 인터럽트 서비스 루틴 실행이 끝나면 4에서 백업해둔 작업을 복구하여 실행을 재개한다.
    *인터럽트 서비스 루틴도 명령어와 데이터로 이루어져 있으므로 레지스터를 사용함, 따라서 인터럽트 이후 프로그램을 재개하기 위해 필요한 모든 내용을 스택에 백업

 


다음으로 Chapter 5 CPU 성능 향상 기법에 대해 알아보자.

 

CPU 성능 향상을 위한 요소로 「클럭」이 있다.

클럭 속도가 높아지면 CPU는 명령어 사이클을 더 빠르게 반복할 것이고 다른 부품들도 더 빠르게 작동할 것이다.

따라서 클럭 속도가 높은 CPU는 일반적으로 성능이 좋다. 클럭 속도는 1초에 몇번 반복되는지 나타내는 Hz단위로 측정한다.

 

고성능을 요구할 경우 순간적으로 클럭 속도를 높이고 그렇지 않을 때는 유연하게 클럭 속도를 낮춘다.

최대 클럭 속도를 강제로 더 끌어올리는 기법도 있는데 이 오버클럭킹이라고 한다.

 

하지만 클럭 속도를 무작정 높이면 발열 문제가 심각해지므로 클럭 속도만으로 CPU 성능을 올리는데에는 한계가 있다.

 

 

다음으로 「코어」가 있다.

CPU의 정의로 알고 있던 '명령어를 실행하는 부품'은 오늘날 코어라는 용어로 사용된다.

따라서 오늘날 CPU는 명령어를 실행하는 부품 -> 명령어를 실행하는 부품을 여러 개 포함하는 부품 으로 명칭의 범위가 확장되었다.

 

* 멀티코어 CPU (= 멀티코어 프로세서): 코어를 여러 개 포함하고 있는 CPU이다. CPU 내에 명령어를 처리하는 일꾼이 여러 명 있는 것과 같다.

 

코어마다 처리할 연산이 적절히 분배되지 않는다면 CPU의 연산 속도가 코어 수에 비례하여 증가하지 않기 때문에 코어마다 처리할 명령어들을 얼마나 적절하게 분배하느냐에 따라 연산 속도가 달라진다.

 

 

「스레드와 멀티스레드」에 대해 알아보자.

먼저 스레드란, CPU에서 사용되는 하드웨어적 스레드가 있으며 프로그램에서 사용되는 소프트웨어적 스레드가 있다.

  • 하드웨어적 스레드
    하나의 코어가 동시에 처리하는 명령어 단위이다.
    * 멀티스레드 프로세서 (= 멀티스레드 CPU): 하나의 코어로 여러 명령어를 동시에 처리하는 CPU
  • 소프트웨어적 스레드
    하나의 프로그램에서 독립적으로 실행되는 단위이다. 독립적으로 동작하는 기능들의 코드를 각각의 스레드를 만들면 동시에 실행할 수 있다.

「멀티스레드 프로세서」의 가장 큰 핵심은 레지스터이다.

하나의 명령어를 처리하기 위해 꼭 필요한 레지스터를 여러 개 가지고 있으면 된다. 이를 편의상 레지스터 세트라고 할 때, 레지스터 세트가 2개이면 하나의 코어에서 2개의 명령어가 동시에 실행된다.

 

따라서 2코어 4스레드 CPU는 한 번에 4개의 명령어를 처리할 수 있다.

하지만 메모리 속 프로그램 입장에서 봤을 때, 한 번에 하나의 명령어를 처리하는 CPU가 4개 있는 것처럼 보인다. 그렇기 때문에 하드웨어 스레드를 논리 프로세서 라고 부르기도 한다.

 

 

 

 

이번에는 CPU가 놀지 않고 시간을 알뜰하게 쓰며 작동하게 하는 「명령어 병렬 처리 기법」에 대해 알아보자.


먼저, 명령어의 처리 과정을 클럭 단위로 나누면

1. 명령어 인출

2. 명령어 해석

3. 명령어 실행

4. 결과 저장 과 같이 나눌 수 있다.

 

중요한 점은 같은 단계가 겹치지만 않으면 CPU는 각 단게를 동시에 실행할 수 있다.

 

1. 마치 공장 생상 라인과 같이 명령어들을 「명령어 파이프라인」에 넣고 동시에 처리하는 기법을 「명령어 파이프라이닝」이라고 한다. 

 

파이프라이닝이 높은 성능을 가져오지만 성능 향상에 실패하는 특정 상황이 있다.

  • 데이터 위험
    명령어 간 데이터 의존성에 의해 발생한다. 어떤 명령어는 이전 명령어를 끝까지 실행해야만 실행할 수 있다. 이렇게 데이터 의존적인 두 명령어를 동시에 실행하려고 하면 제대로 작동하지 않는 것을 데이터 위험이라고 한다.
  • 제어 위험
    분기 등으로 인한 프로그램 카운터의 갑작스러운 변화에 의해 발생한다. 실행 흐름이 바뀌면 명령어 파이프라인에 미리 가지고 와서 처리 중이던 명령어들이 쓸모가 없어지는데 이를 제어 위험이라고 한다.
    * 분기 예측: 프로그램이 어디로 분기할지 미리 예측한 후, 그 주소를 인출하는 기술
  • 구조적 위험 (= 자원 위험)
    명령어들을 겹쳐 실행하는 과정에서 서로 다른 명령어가 동시에 ALU, 레지스터 등과 같은 CPU 부품을 사용하려고 할 때 발생한다.

오늘날 대부분의 CPU는 여러 개의 파이프라인을 이용하는데 CPU 내부에 여러 개의 명령어 파이프라인을 포함한 구조를 「슈퍼스칼라」라고 한다.

* 슈퍼스칼라 프로세서 (= 슈퍼스칼라 CPU): 슈퍼스칼라 구조로 명령어 처리가 가능한 CPU

 

2. 오늘날 CPU 성능 향상에 크게 기여한 기법이자 대부분의 CPU가 차용하는 기법인 「비순차적 명령어 처리 기법이 있다. 명령어들을 순차적으로 실행하지 않는 기법으로 명령어의 합법적인 새치기라고 볼 수 있다.

 

순서를 바꿔 실행해도 무방한 명령어를 먼저 실행하여 데이터 의존성에 의해 명령어 파이프라인이 멈추는 것을 방지하는 기법이다.

 

비순차적 명령어 처리가 가능한 CPU는 명령어들이 어떤 명령어와 데이터 의존성을 가지고 있는지,

순서를 바꿔 실행할 수 있는 명령어에는 어떤 것들이 있는지 판단할 수 있어야 한다.

 

 

 

파이프라이닝에 유리한 CPU 언어인 「ISA」를 알아보자.

CPU가 이해하고 실행하는 명령어들은 제조사마다 다르게 생겼다. 여기서 CPU가 이해할 수 있는 명령어들의 모음을 명령어 집합 「ISA」라고 한다. 즉, CPU마다 ISA가 다를 수 있다.

 

ISA가 다르면, CPU가 이해할 수 있는 명령어와 어셈블리어가 다르다는 뜻이다. 따라서 같은 소스 코드로 만들어진 같은 프로그램이어도 ISA가 다르면 CPU가 이해할 수 있는 명령어와 어셈블리어도 달라진다.

 

=> ISA는 일종의 CPU의 언어이다. 또한 CPU를 비롯한 하드웨어가 소프트웨어를 어떻게 이해할지에 대한 약속이다.

 

  • CISC
    복잡한 명령어 집합을 활용하는 컴퓨터를 의미한다.

    다양하고 강력한 기능의 명령어 집합을 활용한다. 따라서
    - 명령어의 형태와 크기가 다양한 가변 길이 명령어를 활용한다. 
    - 상대적으로 적은 수의 명령어로도 프로그램을 실행할 수 있다. 때문에 컴파일된 프로그램의 크기도 작다.

    메모리를 아낄 수 있다는 장점이 있으나 활용하는 명령어가 워낙 복잡하고 다양한 기능을 제공하기에 
    명령어의 크기와 실행되기까지의 시간이 일정하지 않으며 명령어 하나 실행에 여러 클럭 주기를 필요로 한다.

    따라서 명령어 파이프라인을 구현하는데 걸림돌이 된다.

CISC의 한계로 얻은 교훈은 아래와 같다.

1. 빠른 처리를 위해 명령어 파이프라인을 활용해야한다. 

2. 원활한 파이프라이닝을 위해 명령어 길이와 수행 시간이 짧고 규격화되어 있어야 한다.

3. 자주 쓰이는 기본적인 명령어를 작고 빠르게 만드는 것이 중요하다.

 

원칙 하에 다음과 같은 것이 등장했다.

  • RISC
    CISC에 비해 명령어의 종류가 적으며 짧고 규격화된 명령어, 되도록 1클럭 내외로 실행되는 명령어를 지향한다.
    즉, RISC는 고정 길이 명령어를 활용한다.  또한, 메모리 접근을 단순화하고 최소화를 추구하기에 레지스터를 적극적으로 활용한다. 범용 레지스터의 개수도 더 많다.

    하지만 사용 가능한 명령어의 개수가 적기 때문에 CISC보다 많은 명령으로 프로그램을 작동시킨다.