CS/컴퓨터구조

[컴퓨터구조] CPU 설계 기법 (2)

nowkoes 2023. 6. 13. 00:00

<본 카테고리는 "혼자 공부하는 컴퓨터구조 + 운영체제" 책과 강의를 기반으로 작성하였습니다>


병렬 처리 기법과 ISA

병렬 처리 기법

 이전 포스팅에서 CPU 성능을 향상하기 위한 몇 가지 기본 전략을 논의했었다. 클럭 속도를 높이는 것, 멀티코어나 멀티스레드를 지원하는 아키텍처를 사용하는 것 등이 그 예다. 그러나 CPU 성능 향상은 단순히 이러한 방법에만 의존하는 것이 아니라, 효율적인 작동 방식도 중요하게 고려해야 한다.

 

 이번 게시글에서는 명령어 병렬 처리 기법에 초점을 맞추어 설명해 보겠다. 명령어 병렬 처리 기법(Instructuin-level parallelism)이란 여러 명령어를 동시에 처리함으로써 CPU가 휴식 없이 지속적으로 작동하게 하는 방법을 의미한다. 명령어 파이프라이닝, 슈퍼스칼라, 그리고 비순차적 명령어 처리 등이 이 기법의 대표적인 예시다.

 

명령어 파이프라이닝

 명령어 파이프라이닝(pipelining)은 파이프라인(Pipeline)이라는 개념에서 시작한다. 원래 파이프라인이란, 석유나 천연가스를 수송하는 데 사용되는 관로를 의미한다. 그러나 컴퓨터 공학에서 파이프라이닝은 생산 라인처럼 여러 단계의 공정을 동시에 수행함으로써 작업 효율성을 높이는 것을 말한다. 일반적으로 CPU가 명령어를 처리하는 과정을 클럭 단위로 나누면 다음 네 단계로 나뉜다. 

 

  1. 명령어 인출(fetch)
  2. 명령어 해석(decode)
  3. 명령어 실행(execute)
  4. 결과 저장(write back)

 

 

 여기서 주목할 점은 같은 단계가 겹치지 않는다면, CPU는 각 단계를 동시에 실행할 수 있다는 것이다. 이를 가능하게 하는 기법이 바로 파이프라이닝인데, 이를 통해 CPU는 한 클럭 주기 동안 다른 명령어의 단계를 처리할 수 있게 된다. 이렇게 함으로써 CPU는 시간을 절약하고 효율성을 향상할 수 있다.

 

 

 대부분의 현대 CPU는 단일 파이프라인이 아닌, 여러 파이프라인을 이용한 슈퍼스칼라(superscalar) 구조를 사용한다. 이론적으로는 파이프라인의 개수가 늘어날수록 프로그램 처리 속도도 증가하지만, 실제로는 파이프라인 개수와 처리 속도가 완전히 비례하지 않는다. 이는 각각의 파이프라인 간의 의존성, 명령어의 복잡성 등 다양한 요인에 의해 영향을 받기 때문이다. 

 

 파이프라이닝은 기본적으로 효율적인 방법이지만, 때로는 성능 향상에 실패하는 상황이 발생한다. 이런 상황을 파이프라인 위험(hazard)이라고 부른다. 이 위험은 주로 세 가지 형태로 나타난다.

 

 먼저 데이터 위험(data hazard)이다. 이는 명령어 간 데이터 의존성에 의해 발생한다. 즉, 명령어가 다른 명령어의 결과에 의존하는 경우, 동시에 실행하면 문제가 발생할 수 있다는 것이다. 예를 들면, 명령어 A가 101번 레지스터의 값을 100번 레지스터에 저장하는 동안, 명령어 B는 100번 레지스터의 값과 103번 레지스터의 값을 더한다. 이때 명령어 B는 A의 실행 결과를 기다려야 정확한 연산이 가능하다. 명령어 A가 완료되기 전에 B를 실행하면 올바르지 않은 결과가 나온다.

 

 다음으로 제어 위험(control hazard)이 있다. 주로 분기 등으로 인해 프로그램 카운터가 급격히 변화할 때 이 위험이 발생한다. 프로그램 카운터는 기본적으로 현재 실행 중인 명령어의 다음 주소를 가리키는데, 프로그램의 실행 흐름이 JUMP 같은 명령어를 만나면 갑작스럽게 바뀐다. 이럴 경우, 파이프라인에 이미 적재되어 처리 중인 명령어들은 무의미해진다. 이런 제어 위험을 해결하기 위해 분기 예측(branch prediction) 같은 기술을 사용해 파이프라인에 적절한 명령어를 사전에 로드하도록 한다.

 

 마지막으로 구조적 위험(structual hazard)이 있다. 명령어들이 겹쳐 실행되는 과정에서 동일한 CPU 구성요소를 동시에 사용하려고 할 때 발생한다. 이런 상황은 주로 리소스의 공유와 관련이 있으며, 하드웨어 설계에서 적절한 리소스 할당을 통해 최소화할 수 있다.

 

비순차적 명령어 처리

 비순차적 명령어 처리(Out-of-order execution, OoOE)는 말 그대로 명령어들을 순차적으로 실행하지 않는 기법이다. 지금까지 설명했던 기법들은 모두 여러 명령어의 순차적인 처리를 가정한 방법이었다. 그러나 예상치 못한 상황이 발생하면 명령어는 즉시 처리되지 못하고, 이로 인해 파이프라인이 멈출 수 있다.

 

 다음은 메모리 주소를 이용한 간단한 명령어들이다. 메모리 주소 N은 M(N)으로, 메모리 N번지에 M을 저장하라는 명령어는 M(N) ← M으로 표기한다.

 

1. M(100) ← 1
2. M(101) ← 2
3. M(102) ← M(150) + M(101) // 반드시 M(150)과 M(101)이 결정되어야 하는데, M(150)이 비어 에러
4. M(150) ← 1
5. M(151) ← 2
6. M(152) ← 3

 

 이 명령어에서, 컴퓨터는 3번 명령어를 처리하면서 메모리 150번지의 값을 찾지 못해 에러를 일으킨다. 만약 M(102)의 값이 M(150) + M(101)이 아니라 M(101) + M(102)라면, 1번과 2번 명령어가 완료될 때까지 3,4,5,6번 명령어들은 기다려야 한다.

 

 그런데, 명령어들 중에서는 데이터 의존성이 없어서 순서를 바꿔도 실행 결과에 영향을 미치지 않는 것들이 있다. 이 경우에서는 1, 2, 4, 5, 6번 명령어가 그렇다. 이런 명령어들을 앞으로 끌어올리고, 의존성이 있는 명령어를 뒤로 미룬다면 어떻게 될까?

 

1. M(100) ← 1
2. M(101) ← 2
3. M(150) ← 1
4. M(151) ← 2
5. M(152) ← 3
6. M(102) ← M(150) + M(101)

 

 위와 같이 명령어의 순서를 바꾸어서 실행하면, M(102) 명령어를 처리하면서 파이프라인이 멈추는 상황을 방지할 수 있다. 이런 방식을 비순차적 명령어 처리라고 부른다. 물론, 모든 명령어의 순서를 임의로 바꾸는 것은 안된다. CPU는 데이터 의존성을 가진 명령어와 그렇지 않은 명령어를 구분해야 한다. 이것이 비순차적 명령어 처리 기법의 핵심이다.

 더불어 이 기법은 일반적으로 CPU의 복잡도를 높이고, 효율적인 스케줄링 알고리즘이 필요하다는 단점이 있다. 그러나 이를 통해 프로세서 성능은 크게 향상될 수 있으므로 많은 최신 프로세서에서는 이 기법을 사용한다.


CISC와 RISC

ISA

 명령어 집합(Instruction set) 또는 명령어 집합 구조(Instruction Set Architecture, ISA)CPU가 해석하고 실행할 수 있는 명령어들의 집합을 의미한다. 전 세계에는 다양한 CPU 제조사들이 있으며, 각 CPU는 서로 다른 규격, 기능, 그리고 구조를 가지고 있다. CPU의 ISA가 다르다는 것은 CPU가 이해하고 실행할 수 있는 명령어가 다르다는 뜻이며, 이는 해당 CPU에 맞는 어셈블리어 역시 서로 다르게 된다. 이를 쉽게 이해하려면, 고급 언어로 작성된 동일한 소스 코드가 CPU의 ISA에 따라 다른 어셈블리어 코드로 변환되는 것을 살펴보면 된다

 

C++ 소스 코드

 

 예를 들어, C++로 작성된 위와 같은 코드가 있다고 가정하자. 이 코드는 두 개의 정수 x와 y를 입력받아 그 합을 출력하는 간단한 프로그램이다.

 

x86-64 ISA(좌)와 ARM ISA(우)

 이 소스 코드를 x86-64 ISA를 사용하는 CPU와 ARM ISA를 사용하는 CPU에서 각각 실행한다면, 동일한 코드임에도 불구하고 컴파일러는 이를 각각 다른 어셈블리어 코드로 변환하게 된다. 위의 이미지는 두 가지 ISA에 따른 어셈블리어 코드의 차이를 보여준다.

 

 같은 고급 코드를 기반으로 하지만, CPU가 해석하고 실행할 수 있는 명령어가 다르므로 어셈블리어 코드 또한 다르다. 이는 CPU의 명령어를 해석하는 방식, 즉 제어 장치의 작동 방식에 큰 영향을 미치며, 이는 반대로 CPU 및 관련 하드웨어 설계에도 중요한 영향을 미친다. 이런 이유로, ISA는 CPU가 사용하는 언어이자, 하드웨어가 소프트웨어를 어떻게 해석하고 실행하는지에 대한 규약이라고 볼 수 있다. 따라서 ISA의 선택은 하드웨어의 성능과 소프트웨어의 호환성 등 많은 측면에 영향을 미치게 된다. 그래서 특정 애플리케이션을 위한 최적의 성능을 달성하기 위해서는 가장 적절한 ISA를 사용하는 CPU를 선택하는 것이 중요하다.

 

CISC

 현대 ISA의 양대 산맥이라 하면 대부분 CISC와 RISC를 꼽을 것이다. 우선 CISC에 대해 알아보자. CISC는 Complex Instruction Set Computer의 약자로서, 복잡한 명령어 집합을 사용하는 컴퓨터 아키텍처를 가리킨다. 이 아키텍처는 다양하고 복잡한 기능을 수행하는 명령어 집합을 특징으로 하며, 이러한 명령어는 가변 길이를 가집니다. 이 가변 길이 명령어의 이점은 프로그램을 실행하는 데 필요한 명령어 수를 줄일 수 있다는 것이다. 또한 메모리 주소 지정 모드가 풍부하고, 하나의 명령어로 여러 작업을 수행할 수 있기 때문에 메모리를 더 효율적으로 사용할 수 있다.

 

 그러나 CISC 아키텍처는 몇 가지 주요한 단점을 가지고 있다. 첫째, 가변 길이 명령어는 명령어의 해석과 실행에 걸리는 시간이 일정하지 않다는 점을 의미한다. 이는 최적화를 복잡하게 만들고 프로그램의 성능 예측을 어렵게 한다. 둘째, 명령어가 복잡하기 때문에, 각 명령어를 실행하는 데 여러 클럭 주기가 필요할 수 있다. 이는 CPU 파이프라인의 효율성을 저하시키는 주요 요인이 될 수 있다.

 

 

 명령어 파이프라인 기법을 위한 이상적인 명령어는 각 단계에 소요되는 시간이 동일해야 한다. 만약 각 단계의 실행 시간이 다르면, 가장 오래 걸리는 단계에 의해 전체 파이프라인의 처리 속도가 제한되게 된다. 이러 상황에서는 버블(Bubble)이라는 비효율성이 생기게 되는데, 이는 어떤 단계에서 시간 지연이 발생해 그 이후의 단계들이 일시적으로 멈추게 되는 현상을 말한다. 이렇게 되면 파이프라인의 전체적인 처리율이 떨어지게 된다.

 

RISC

 RISC(Reduced Instruction Set Computer) 아키텍처는 CISC의 복잡성을 줄이고, CPU 성능을 최대화하기 위한 반응으로 개발되었다. RISC는 이름에서 알 수 있듯이, 간소화된 명령어 집합을 특징으로 한다. 이 아키텍처는 간결하고 표준화된 명령어를 사용하며, 대부분의 명령어가 한 클럭 사이클 내에 실행될 수 있도록 설계되어 있다. 이는 고정 길이의 명령어를 사용함으로써 가능해진다.

 

 RISC는 메모리 접근을 최소화하고 레지스터 사용을 극대화하는 설계 방향을 가지고 있다. 이렇게 하면 메모리 접근에 따른 시간 지연을 줄일 수 있다. 이를 위해 RISC 시스템은 일반적으로 큰 레지스터 집합을 포함하고 있으며, 이는 CISC 시스템에 비해 더 많은 명령어를 필요로 한다.

 

 추가로 RISC의 설계 원칙 중 하나는 하드웨어보다는 소프트웨어에 많은 책임을 부여하는 것이다. 이는 RISC 프로세서가 더 단순하게 유지되면서도, 고급 언어 컴파일러는 복잡한 작업을 수행하게 됨을 의미한다. 이러한 접근 방식은 빠른 파이프라이닝과 병렬 처리와 같은 최적화 전략을 쉽게 적용할 수 있도록 해, CPU 성능 향상에 기여한다.

 

   CISC RISC
명령어 형태 복잡하고 다양 단순하고 적음
명령어 길이 가변 길이 고정 길이
메모리 효율성 높음(다양한 주소 지정 방식) 낮음(적은 주소 지정 방식)
명령어 수 프로그램을 구성하는 명령어 수가 적음 프로그램을 구성하는 명령어 수가 많음
클럭 주기 여러 클럭에 걸쳐 명령어 수행 대체로 1클럭 내외로 명령어 수행
파이프라이닝 어려움 쉬움

요약

명령어 파이프라인
1. 정의: 생산 라인처럼 여러 단계의 공정을 동시에 수행함으로써 작업 효율성을 높이는 것
2. 특징: 각 단계가 독립적으로 작동하여 병렬 처리
3. 슈퍼스칼라 구조: 여러 개의 명령어 파이프라인을 갖는 구조
4. 파이프라인 위험
 - 데이터 위험, 제어 위험, 구조적 위험

비순차적 명령어 처리 
1. 정의: 명령어를 순차적으로 처리하지 않는 작업
2. 특징
 a. 순서를 바꿔도 되는 명령어부터 처리하여 작업의 효율성 증대
 b. 파이프라인 위험을 줄이는데 기여

ISA
1. 정의: CPU가 이해하는 명령어 집합
2. 종류
 a. CISC: 가변 길이 명령어를 채용하는 복합적인 명령어
 b. RISC: 고정 길이 명령어를 채용하는 간단한 명령어

 

반응형