작성자 프로필
mouuaw
코드 깎는 다람쥐
2023.06.03

빠른 CPU를 위한 설계 기법

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

클럭

컴퓨터 부품들은 '클럭 신호'에 맞춰 움직입니다. 그래서 클럭 신호가 빠르면 컴퓨터 부품들은 빠른 박자에 맞춰서 움직이게 됩니다. 그래서 클럭 속도가 높은 CPU는 일반적으로 성능이 좋습니다.

클럭 속도는 헤르츠(Hz) 단위로 측정합니다. 이는 1초에 클럭이 몇 번 반복되는지를 나타냅니다. 클럭은 매번 일정하게 유지되지 않습니다. 기본 클럭 속도최대 클럭 속도가 있는데, 고성능을 필요로 할 때는 클럭속도를 높이게 됩니다. 설정에 의해서 최대 클럭 속도를 더 끌어올릴수 있는데 이를 오버클럭킹이라고 합니다.

코어와 멀티코어

클럭 속도 외에 CPU 성능을 높이는 방법에는 코어와 스레드 수를 늘리는 방법이 있습니다.

코어를 이해하려면 현대적인 관점에서 CPU라는 용어를 재해석해야 합니다. 앞서 CPU를 '명령어를 실행하는 부품'이라고 했습니다. 하지만 오늘날 CPU는 많은 기술적 발전을 했고, 그 결과 CPU 내부에는 '명령어를 실행하는 부품'을 얼마든지 만들 수 있게 되었습니다.

우리가 지금까지 CPU의 정의로 사용하던 '명령어를 실행하는 부품'은 이제 코어라는 용어로 사용됩니다.

코어를 여러 개 포함하고 있는 CPU를 멀티코어 또는 멀티코어 프로세서라고 부릅니다. 이는 CPU 내에 명령어를 처리하는 일꾼이 여러 명 있는 것과 같습니다.

코어를 무조건 많이 넣으면 CPU가 빨라질까요? 아쉽게도 그렇지 않습니다. 코어수가 많더라도 업무 분산이 제대로 되지 않으면 CPU 코어를 모두 사용할 수 없기 때문에 성능을 다 낼수 없습니다.

스레드와 멀티스레드

CPU의 멀티스레드 기술을 이해하려면 우선 '스레드'라는 용어를 이해해야 합니다. 스레드는 프로그래밍 언어를 학습할 때도 등장하고, 추후 운영체제를 학습할 때도 등장합니다.

스레드는 CPU에서 사용되는 하드웨어적 스레드가 있고, 프로그램에서 사용되는 소프트웨어적 스레드가 있습니다.

하드웨어적 스레드

스레드를 하드웨어적으로 정의하면 '하나의 코어가 동시에 처리하는 명령어 단위'를 의미합니다. 보통 CPU에서 사용하는 스레드라는 용어는 보통 CPU 입장에서 정의된 하드웨어적 스레드를 의미합니다.

여러 스레드를 지원하는 CPU는 하나의 코어에서 여러 개의 명령어를 동시에 실행할 수 있습니다. 이처럼 하나의 코어로 여러 명령어를 동시에 처리하는 CPU를 멀티스레드 프로세서 또는 멀티스레드 CPU라고 합니다.

소프트웨어적 스레드

소프트웨어적으로 정의된 스레드는 '하나의 프로그램에서 독립적으로 실행되는 단위'를 의미합니다. 보통 프로그래밍 언어나 운영체제를 학습할 때 접하는 스레드는 보통 이렇게 소프트웨어적으로 정의된 스레드를 의미합니다.

정리하면, 스레드의 하드웨어적 정의는 '하나의 코어가 동시에 처리하는 명령어 단위'를 의미하고, 소프트웨어적 정의는 '하나의 프로그램에서 독립적으로 실행되는 단위'를 의미합니다.

한 번에 하나씩 명령어를 처리할 수 있는 1코어 1스레드도 소프트웨어적으로 스레드를 수십 개 실행할 수 있다는겁니다.

멀티스레드 프로세서

이제부터 소프트웨어적 스레드는 스레드, CPU에서 사용되는 스레드는 하드웨어 스레드라고 부르겠습니다.

멀티스레드 프로세서는 하나의 코어로 여러 명령어를 동시에 처리할 수 있습니다. 어떻게 이런일이 가능할까요? 멀티스레드 프로세서의 핵심은 레지스터 입니다. 하나의 코어로 여러 명령어를 동시에 처리하도록 만들려면 프로그램 카운터, 스택 포인터, 데이터 버퍼 레지스터, 데이터 주소 레지스터같이 하나의 명령어를 처리하기 위해 필요한 레지스터를 여러개 가지고 있으면 됩니다.

하드웨어 스레드를 이용해 하나의 코어로도 여러 명령어를 동시에 처리할 수 있다고 했는데, 메모리 속 프로그램 입장에서 봤을 때 하드웨어 스레드는 마치 '한 번에 하나의 명령어를 처리하는 CPU'나 다름없습니다. 가령 2코어 4스레드 CPU의 경우 프로그램 입장에선 4개의 명령어를 처리할 수 있기 때문에 4개의 CPU가 있는것처럼 보입니다. 그래서 하드웨어 스레드를 논리 프로세서라고 부르기도 합니다.

명령어 병렬 처리 기법

빠른 CPU를 만들려면 높은 클럭 속도에 멀티코어, 멀티스레드를 지원하는 CPU를 만드는 것도 중요하지만, CPU가 놀지 않고 작동하게 만드는 것도 중요합니다.

이번 절에서는 명령어를 동시에 처리하며 CPU를 쉬지 않고 작동시키는 기법인 명령어 병렬 처리 기법 (ILP) 을 알아보겠습니다. 대표적인 명령어 병렬 처리 기법에는 명령어 파이프 라이닝, 슈퍼스칼라, 비순차적 명령어 처리가 있습니다.

명령어 파이프라인

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

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

여기서 중요한 점은 같은 단계가 겹치지만 않는다면 CPU는 '각 단계를 동시에 실행할 수 있다'는 것입니다. 예를 들어 CPU는 한 명령어를 '인출'하는 동안에 다른 명령어를 '실행'할 수 있고, 한 명령어가 '실행'되는 동안에 연산 결과를 '저장'할 수 있습니다.

이처럼 마치 공장 생산 라인과 같이 명령어들을 명령어 파이프라인에 넣고 동시에 처리하는 기법을 명령어 파이프라이닝이라고 합니다.

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

데이터 위험

데이터 위험은 명령어 간 '데이터 의존성'에 의해 발생합니다. 모든 명령어를 동시에 처리할수는 없습니다. 어떤 명령어는 이전 명령어를 끝까지 실행해야만 비로소 실행할 수 있는 경우가 있습니다. 서로 데이터 의존적인 상황인 명령어 두개를 동시에 실행하려 한다면 파이프라인이 제대로 동작하지 않게 되는데, 이를 데이터 위험이라 합니다.

제어 위험

제어 위험은 주로 분기 등으로 인한 '프로그램 카운터의 갑작스런 변화'에 의해 발생합니다. 기본적으로 프로그램 카운터는 '현재 실행 중인 명령어의 다음 주소'로 갱신됩니다. 하지만 프로그램 실행 흐름이 바뀌어 명령어가 실행되면서 프로그램 카운터 값에 갑작스러운 변화가 생긴다면 명령어 파이프라인에 미리 가지고 와서 처리중이었던 명령어들은 아무 쓸모가 없어집니다. 이를 '제어 위험' 이라고 합니다.

참고로 이를 위해 사용하는 기술 중 하나가 분기 예측입니다. 분기 예측은 프로그램이 어디로 분기할지 미리 예측한 후 그 주소를 인출하는 기술입니다.

구조적 위험

구조적 위험은 명령어들을 겹쳐 실행하는 과정에서 서로 다른 명령어가 동시에 ALU, 레지스터 등과 같은 CPU 부품을 사용하려고 할 때 발생합니다. 구조적 위험은 자원 위험이라고도 부릅니다.

슈퍼스칼라

파이프라이닝은 단일 파이프라인으로도 구현이 가능하지만, 오늘날 대부분의 CPU에서는 여러 개의 파이프라인을 이용합니다. 이처럼 CPU 내부에 여러 개의 명령어 파이프라인을 포함한 구조를 슈퍼스칼라라고 합니다.

슈퍼스칼라 구조로 명령어 처리가 가능한 CPU를 슈퍼스칼라 프로세서 또는 슈퍼스칼라 CPU라고 합니다.

비순차적 명령어 처리

마지막으로 살펴볼 명령어 병렬 처리 기법은 비순차적 명령어 처리 입니다. 비순차적 명령어 처리 기법은 이름에서도 알 수 있듯 명령어들을 순차적으로 실행하지 않는 기법입니다.

지금까지 설명한 명령어 파이프라이닝, 슈퍼스칼라 기법은 모두 명령어의 순차적인 처리를 상정한 방법이었습니다. 하지만 파이프라인이 위험과 같은 예상치 못한 문제들로 인해 이따금씩 명령어는 곧바로 처리되지 못하기도 합니다.

명령어가 서로 의존적일 경우 의존되는 명령어들을 우선 처리하고 다음을 수행해야 합니다. 하지만 종종 이후의 명령어들이 아예 독립적으로 실행이 가능한 경우가 있는데, 이런 경우 해당 명령어들을 순차적으로 처리하지 않고 먼저 처리합니다.

이런식으로 명령어의 순서를 바꿔서 효율적으로 수행하는 방법을 비순차적 명령어 처리라고 합니다.

하지만 아무 명령어나 순서를 바꿔서 수행할 수는 없습니다. 이처럼 비순차적 명령어 처리가 가능한 CPU는 명령어들이 어떤 명령어와 데이터 의존성을 가지고 있는지, 순서를 바꿔 실행할 수 있는 명령어에는 어떤 것들이 있는지를 판단할 수 있어야 합니다.

CISC와 RISC

CPU가 파이프라이닝과 슈퍼스칼라 기법을 효과적으로 사용하려면 CPU가 인출하고 해석하고 실행하는 명령어가 파이프라이닝 하기 쉽게 생겨야 합니다.

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

명령어 집합

CPU의 제조사마다 실행하는 명령어가 다 똑같이 생기진 않았습니다. 명령어의 기본 구조와 원리는 앞서 배운것과 비슷하긴 하지만 제조사마다 조금씩 차이가 있습니다. CPU가 이해할 수 있는 명령어들의 모음을 명령어 집합 또는 명령어 집합 구조 (이하 ISA)라고 합니다. 즉 CPU 마다 ISA가 다를 수 있다는 겁니다.

ISA가 같은 CPU끼리는 서로의 명령어를 이해할 수 있지만, ISA가 다르면 서로의 명령어를 이해하지 못합니다. ISA가 다르면 많은 것들이 달라집니다. 제어장치가 명령어를 해석하는 방식, 사용되는 레지스터의 종류와 개수, 메모리 관리 방법 등등.

우리가 실행하는 프로그램은 명령어로 이루어져 있습니다. ISA는 CPU의 언어임과 동시에 CPU를 비롯한 하드웨어가 소프트웨어를 어떻게 이해할지에 대한 약속이라고도 볼 수 있습니다.

이제 현대 ISA의 양대 산맥인 CISC와 RISC에 대해 알아보겠습니다.

CISC

CISE는 Complex Instruction Set Computer의 약자입니다. 이를 그대로 해석하면 '복잡한 명령어 집합을 활용하는 컴퓨터'를 의미합니다. 여기서 '컴퓨터'를 'CPU'라고 생각해도 좋습니다.

CISC는 다양하고 강력한 기능의 명령어 집합을 활용하기 때문에 명령어의 형태와 크기가 다양한 가변 길이 명령어를 활용합니다. 메모리에 접근하는 주소 지정 방식도 다양해서 아주 특별한 상황에서만 사용되는 독특한 주소 지정 방식들도 있죠.

다양하고 강력한 명령어를 활용한다는 말은 상대적으로 적은 수의 명령어로도 프로그램을 실행할 수 있다는 것을 의미합니다. 이런 장점 때문에 CISC는 메모리를 최대한 아끼며 개발하던 시절에 인기가 높았습니다. 적은수의 명령어로도 프로그램을 실행할 수 있다는 말은 메모리를 절약할 수 있다는것이기 때문입니다.

하지만 CISC는 치명적인 단점이 있는데, 명령어의 크기와 실행시간이 일정하지 않다는 점입니다. 그래서 하나의 명령어를 실행할 때 여러 클럭 주기가 필요할 수도 있습니다.

이는 명령어 파이프라인을 구현하는데 걸림돌이 됩니다. 이는 현대 CPU에서 아주 치명적인 약점이 됩니다.

RISC

CISC의 한계는 우리에게 몇가지 교훈을 줍니다

빠른 처리를 위해선 명령어 파이프라인을 십분 활용해야 한다. 원활한 파이프라이닝을 위해선 명령어의 길이와 수행시간이 짧고 규격화 되어있어야 한다.
자주 쓰이는 명령어만 줄곧 사용하게 되므로, 복잡한 기능보다는 자주 쓰이는 기본적인 명령어를 작고 빠르게 만드는 것이 중요하다.

그래서 나온것이 RISC 입니다. RISC는 Reduced Instruction Set Computer의 약자로 명령어의 종류가 CISC 보다 적습니다. 그리고 짧고 규격화된 명령어, 1클럭 내외로 실행되는 명령어를 지향합니다.

즉, RISC는 고정 길이 명령어를 활용합니다. RISC는 메모리에 직접 접근하는 명령어를 load, store 두 개로 제한할 만큼 메모리 접근을 단순화하고 최소화를 추구합니다.

RISC는 메모리 접근을 단순화, 최소화하는 대신 레지스터를 적극적으로 활용합니다. 그렇기에 CISC보다 레지스터를 이용하는 연산이 많고, 일반적으로 범용 레지스터 개수도 많습니다. 다만 명령어 개수가 적기 때문에 프로그램 실행시 더 많은 명령어가 필요합니다.

스터디 프로필
코드 깎는 다람쥐
의 다른 카테고리
0
👍0
👏0
🤔
댓글 작성