이번 포스팅에는 클럭, 멀티코어, 멀티스레드가 각각 무엇인지 알아보고, 이것들이 CPU 속도와 어떤 관계가 있는지 알아보겠다.
클럭(clock)이란 컴퓨터의 모든 부품을 일사불란하게 움질일 수 있게 하는 시간 단위

클럭 속도가 높아지면 CPU는 명령어 사이클을 더 빠르게 반복할 것이고, 다른부품들도 그에 발맞춰 더 빠르게 작동할것이다.
→ 실제로 클럭 속도가 높은 CPU는 일반적으로 성능이 좋다.
→ 그래서 클럭 속도는 CPU 속도 단위로 간주되기도 한다.
클럭 속도는 1초에 클럭이 몇 번 반복되는지를 나타내며, 헤르츠(Hz) 단위로 측정한다.
클럭 속도는 일정하지 않다
클럭 속도는 기본 클럭 속도(Base)와 최대 클럭 속도(Max)로 나뉘어진다. CPU는 계속 일정한 클럭 속도를 유지하기보다는 고성능을 요하는 순간에는 순간적으로 클럭 속도를 높이고, 그렇지 않을 때는 유연하게 클럭 속도를 낮추기도 한다.
오버클럭킹(overclocking): 최대 클럭 속도를 강제로 더 끌어올리는 기법
클럭 속도를 엄청 높이면 무조건 CPU가 빨라지나?
→ 클럭 속도를 무작정 높이면 발열 문제가 더 심각해진다.
→ 클럭 속도를 높이는 것은 분명 CPU를 빠르게 만들지만, 클럭 속도만으로 CPU의 성능을 올리는 것에는 한계가 있다.
클럭 속도를 높이는 방법 외에 CPU의 성능을 높이는 대표적인 해결책으로는 CPU의 코어와 스레드 수를 늘리는 방법이 있다.
우리가 지금까지 CPU의 정의로 알고 있었던 '명령어를 실행하는 부품'은 코어(core)라는 용어로 사용된다.

코어를 여러 개 포함하고 있는 CPU를 멀티코어(multi-core) CPU 또는 멀티코어 프로세서라고 부른다.

코어를 100개로 늘리면 연산 처리 속도는 100배로 빨라질까?
→ CPU의 연산 속도가 꼭 코어 수에 비례하여 증가하지는 않는다.
중요한 것은 코어마다 처리할 명령어들을 얼마나 적절하게 분배하느냐이고 그에 따라서 연산 속도는 크게 달라진다.
스레드(thread)의 사전적 의미: 실행 흐름의 단위
스레드에는 CPU에 사용되는 하드웨어적 스레드가 있고, 프로그램에서 사용되는 소프트웨어적 스레드가 있다.

스레드를 하드웨어적으로 정의하면 하나의 코어가 동시에 처리하는 명령어 단위를 의미한다.
1코어 1스레드
→ 명령어를 실행하는 부품이 하나 있고, 한 번에 하나씩 명령어를 실행하는 CPU

2코어 4스레드
→ 명령어를 실행하는 부품을 두 개 포함하고, 한 번에 네 개의 명령어를 처리할 수 있는 CPU

하나의 코어로 여러 명령어를 동시에 처리하는 CPU를 멀티스레드(multithread) 프로세서 또는 멀티스레드 CPU라고 한다.
인텔이 자신들의 멀티스레드 기술에 하이퍼스레딩(hyper-threading)이라는 명칭을 부여했다.
소프트웨어적으로 정의된 스레드는 하나의 프로그램에서 독립적으로 실행되는 단위를 의미한다.
프로그래밍 언어나 운영체제를 학습할 때 접하는 스레드는 보통 소프트웨어적으로 정의된 스레드를 의미한다.
하나의 프로그램은 실행되는 과정에서 한 부분만 실행될 수도 있지만, 프로그램의 여러부분이 동시에 실행될 수도 있다.

가령 이 기능들을 작동시키는 코드를 각각의 스레드로 만들면 동시에 실행할 수 있다.

한 번에 하나씩 명령어를 처리하는 1코어 1스레드 CPU도 소프트웨어적으로 스레드를 수십 개 실행할 수 있다.
→ 1코어 1스레드 CPU로도 프로그램의 여러 부분을 동시에 실행할 수 있다.
만약 스레드의 사전적 정의(실행 흐름의 단위)만을 암기한다면 '1코어 1스레드 CPU가 여러 스레드로 만들어진 프로그램을 실행할 수 있다'라는 말이 어려울 것이다.
→ 하드웨어적 스레드와 소프트웨어적 스레드는 구분하여 기억하는 것이 좋다.
멀티스레드 프로세서를 실제로 설계하는 일은 매우 복잡하지만, 가장 큰 핵심은 레지스터다.
하나의 코어로 여러 명령어를 동시에 처리하도록 만들려면 하나의 명령어를 처리하기 위해 꼭 필요한 레지스터(프로그램 카운터, 스택 포인터, ...)를 여러개 가지고 있으면 된다.

하드웨어 스레드를 논리 프로세서(logical processor)라고 부르기도 한다.

4코어 8스레드 CPU를 보면 논리 프로세서가 8이다.
→ 실제 CPU 속에 명령어를 처리하는 부품(코어)은 네 개지만, 메모리 속 프로그램이 보기에는 한 번에 하나의 명령어를 처리하는 부품이 마치 여덟 개 있는 것처럼 보이기 때문에 논리 프로세서가 8개로 나오는 것이다.

- 코어: 명령어를 실행할 수 있는 '하드웨어 부품'
- 스레드: 명령어를 실행하는 단위
- 멀티코어 프로세서: 명령어를 실행할 수 있는 하드웨어 부품이 CPU 안에 두 개 이상 있는 CPU
- 멀티스레드 프로세서: 하나의 코어로 여러 개의 명령어를 동시에 실행할 수 있는 CPU
명령어 병렬 처리 기법(ILP: Instruction-Leel Parellelism):
명령어를 동시에 처리하여 CPU를 한시도 쉬지 않고 작동시키는 기법
명령어 처리 과정을 클럭 단위로 나누어 보면 일반적으로 다음과 같다.
같은 단계가 겹치지만 않으면 CPU는 각 단계를 동시에 실행할 수 있다.
→ 예를 들어 CPU는 한 명령어를 인출하는 동안에 다른 명령어를 실행할 수 있고, 한 명령어가 실행되는 동안에 연산 결과를 저장할 수 있다.

명령어 파이프라이닝(Instruction pipelining):
공장 생산 라인과 같이 명령어들을 명령어 파이프라인(Instruction pipeline)에 넣고 동시에 처리하는 기법
파이프라인 위험(pipeline hazard): 성능 향상에 실패하는 특정 상황

데이터 위험(data hazard): 명령어 간 '데이터 의존성'에 의해 발생하는 경우
모든 명령어를 동시에 처리할 수 없다.
명령어 1: R1 ← R2 + R3 // R2 레지스터 값과 R3 레지스터 값을 더한 값을 R1 레지스터에 저장
명령어 2: R4 ← R1 + R5 // R1 레지스터 값과 R5 레지스터 값을 더한 값을 R4 레지스터에 저장
명령어 1을 수행해야만 명령어 2를 수행할 수 있다.
제어 위험(control hazard): 주로 분기 등으로 인한 프로그램 카운터의 갑작스러운 변화에 의해 발생한 경우
기본적으로 프로그램 카운터는 현재 실행 중인 명령어의 다음 주소로 갱신됩니다.
하지만 프로그램 실행 흐름이 바뀌어 명령어가 실행되면서 프로그램 카운터 값에 갑작스러운 변화가 생긴다면 명령어 파이프라인에 미리 가지고 와서 처리 중이었던 명령어들은 아무 쓸모가 없어진다.

이를 위해 사용하는 기술 중 하나가 분기 예측(branch prediction)이다.
분기 예측: 프로그램이 어디로 분기할지 미리 예측한 후 그 주소를 인출하는 기술
구조적 위험(structural hazard): 명령어들을 겹쳐 실행하는 과정에서 서로 다른 명령어가 ALU, 레지스터 등과 같은 CPU 부품을 사용하려고 할 때 발생하는 경우
자원 위험(resource hazard)라고도 부른다.
슈퍼스칼라(superscalar): CPU 내부에 여러 개의 명령어 파이프라인을 포함한 구조

슈퍼스칼라 구조로 명령어 처리가 가능한 CPU를 슈퍼스칼라 프로세서 또는 슈퍼스칼라 CPU라고 한다.
슈퍼스칼라 프로세서는 매 클럭 주기마다 동시에 여러 명령어를 인출할 수도, 실행할 수도 있어야 한다.
→ 멀티스레드 프로세서는 한 번에 여러 명령어를 인출하고, 해석하고, 실행할 수있기 때문에 슈퍼스칼라 구조를 사용할 수 있다.
슈퍼스칼라 프로세서는 이론적으로 파이프라인 개수에 비례하여 프로그램 처리 속도가 빨라져야 한다.하지만 파이프라인 위험 등의 예상치 못한 문제가 있어 실제로는 반드시 파이프라인 개수에 비례하여 빨라지지는 않는다.
여러 개의 파이프라인을 이용하면 하나의 파이프라인을 사용할 때보다 데이터 위험, 제어 위험, 자원 위험을 피하기가 까다롭다.
비순차적 명령어 처리(OoOE: Out-of-Order Execution): 명령어들을 순차적으로 실행하지 않는 기법. 명령어의 '합법적인 새치기'
1 M(100) ← 1
2 M(101) ← 2
3 M(102) ← M(100) + M(101)
4 M(150) ← 1
5 M(151) ← 2
6 M(152) ← 3
3번 명령어를 실행하기 위해서는 1번과 2번 명령어 실행이 끝날 때까지 기다려야 한다.

명령어들 중에 서로 데이터 의존성이 전혀 없는, 순서를 바꿔 처리해도 수행 결과에 영향을 미치지 않는 명령어들이 있다.
3번을 뒤로 미룸으로써 실행 순서를 바꿈
1 M(100) ← 1
2 M(101) ← 2
4 M(150) ← 1
5 M(151) ← 2
6 M(152) ← 3
3 M(102) ← M(100) + M(101)
이렇게 명령어를 순차적으로만 실행하지 않고 순서를 바꿔 실행해도 무방한 명령어를 먼저 실행하여 명령어 파이프라인이 멈추는 것을 방지하는 기법을 비순차적 명령어 처리 기법이라고 한다.

명령어의 세세한 생김새, 명령어로 할 수 있는 연산, 주소 지정 방식 등은 CPU마다 조금씩 차이가 있다.
CPU가 이해할 수 있는 명령어들의 모음을 명령어 집합(instruction set) 또는 ISA(Instruction Set Architecture, 명령어 집합 구조)라고 한다.
즉, CPU마다 ISA가 다를 수 있다.

ISA가 다르다는 건 CPU가 이해할 수 있는 명령어가 다르다는 뜻이고, 명령어가 달라지면 어셈블리어도 달라진다.

제어장치가 명령어를 해석하는 방식, 사용되는 레지스터의 종류와 개수, 메모리 관리 방법 등 많은 것이 달라진다.
→ CPU 하드웨어 설계에도 큰 영향을 미친다.
ISA는 CPU의 언어임과 동시에 CPU를 비롯한 하드웨어가 소프트웨어를 어떻게 이해할지에 대한 약속
CISC(Complex Instruction Set Computer):
복잡하고 다양한 명령어들을 활용하는 CPU 설계 방식
가변 길이 명령어
CISC는 다양하고 강력한 기능의 명령어(상대적으로 적은 수의 명령어로도 프로그램을 실행할 수 있음)의 집합을 활용한다.
intel의 노트북 속 CPU의 x86-64는 대표적인 CISC 기반의 ISA이며 코드 길이가 ARM보다 짧다.(그림)
같은 소스 코드를 컴파일해도 CPU마다 생성되는 실행 파일의 크기가 다를 수 있다.

명령어 수행 시간이 길고 가지각색이기 때문에 파이프라이닝이 효율적으로 명령어를 처리할 수 없다.
현대 CPU에서 명령어 파이프라인은 높은 성능을 내기 위해 절대 놓쳐서 안 되는 핵심기술이기 때문에 아주 치명적인 약점이다.

CISC가 복잡하고 다양한 명령어를 활용할 수 있다고는 하지만, 대다수의 복잡한 명령어는 그 사용 빈도가 낮다.
→ CISC 명령어 집합 중 불과 20%정도의 명령어가 사용된 전체 명령어의 80%가량을 차지한다.
CISC 명령어 집합이 다양하고 복잡한 기능을 지원하지만 실제로는 자주 사용되는 명령어만 쓰였다.
RISC(Reduced Instruction Set Computer):
단순하고 적은 종류의 고정 길이 명령어 집합을 활용하는 CPU의 설계 방식

RISC는 메모리에 직접 접근하는 명령어를 load, store 두 개로 제한할 만큼 메모리 접근을 단순화하고 최소화를 추구한다.
→ load-store 구조라고 부르기도 한다.
→ CISC보다 주소 지정 방식의 종류가 적은 경우가 많다.
RISC는 메모리 접근을 단순화, 최소화하는 대신 레지스터를 적극적으로 활용한다.
→ CISC보다 레지스터를 이용하는 연산이 많다.
→ 일반적인 경우보다 범용 레지스터 개수도 많다.
아이폰의 CPU ARM은 RISC 기반의 대표적인 ISA다.(그림)

개발자를 하고 있지만 CPU의 성능을 좌우하는 요소를 알지 못했는데 이번 기회에 배우게 되었다. 특히 스레드라는 많지 들어봤지만 여러 관점으로서 어떤 차이가 있는지와 CPU의 성능에 큰 도움이 되는 명령어 파이프라이닝을 알게 되었다.
스레드의 사전적 의미: 실행 흐름의 단위
- 하드웨어적 스레드: 하나의 코어가 동시에 처리하는 명령어 단위
- 소프트웨어적 스레드: 하나의 프로그램에서 독립적으로 실행되는 단위
명령어 파이프라이닝:
공장 생산 라인과 같이 명령어들을 명령어 파이프라인에 넣고 동시에 처리하는 기법