지난 포스팅까지 각 CPU 타입마다 주어진 연산을 어떤 과정과 명령어들로 처리하는지 정리하여보았다.
이번에는 이 각각의 명령어 하나하나를 어떤 과정으로 처리하는지 정리하고자 한다.
1. 명령어 처리
하나의 명령어를 실행하는 과정을 Instruction Cycle 이라고 한다.
(실행주기 or 실행과정)
이를 간단하게 수도코드로 표현하면 아래와 같다.
pc = 0; // pc => program counter
do {
instruction = memory[pc++]; // Instruction fetch (명령어 인출)
decode(instruction); // Instruction decoding (명령어 해석)
fetch(operands); // Operand fetch (피연산자 인출)
execute; // Execution (실행)
store(result); // Store the result (결과 저장)
} while (instruction != stop);
말로 풀면 아래와 같이 되겠다.
프로그램 카운터(PC)가 현재 실행할 명령어가 있는 메모리 주소를 가리키므로,
PC가 가리키는 메모리 위치에서 명령어 데이터를 가져온다. (명령어 인출)
가져온 명령어를 해석한다. (명령어 해석)
(어디부터 어디까지는 명령이고, 어디부터 어디까지는 가져올 데이터 주소고...)
해석한 결과를 토대로 연산할 데이터를 가져온다. (Operand 인출)
가져온 데이터로 연산을 수행한다. (실행)
연산의 결과값을 레지스터에 저장한다. (결과 저장)
이 내용에서 기억할 것은, 하나의 명령어를 처리하는데 이렇게 5가지의 과정을 거치고 있다는 것이다.
2. Pipelining Analogy
학교에서는 간단하게 세탁기 예시를 들어주었다.
우리가 옷을 빨래한다고 해보자.
만약 세탁/헹굼/탈수 가 All in one 으로 들어있어서
이 모든 과정이 끝나야 새로운 빨래를 할 수 있다면 아래와 같은 순서로 빨래가 진행된다.
세탁 > 헹굼 > 탈수 (빨래1 완료) > 세탁 > 헹굼 > 탈수 (빨래2 완료) > ...
그리고 실제 세탁기도 물론 이렇게 동작한다.
하지만 빨래의 양이 한두벌이 아니라 수백 수천벌이라고 생각해보자.
전문 세탁회사를 운영한다면 이는 효율적이지가 않을 것이다.
물론 세탁기 대수를 늘려버린다면 그것도 하나의 해결책이겠지만
언제나 시간과 공간, 돈의 제약은 존재하는 법이다.
이걸 효율적으로 처리하기 위한 방법으로 고안된 것이 '파이프라이닝' 기법이다.
파이프라이닝은 '빨래' 라는 하나의 과정을
세탁 / 헹굼 / 탈수
3개의 세부 단계 (Stage 라고 한다.) 로 구분하고,
각 Stage를 전담하는 전문 기계를 두어 처리하는 것과 같다.
세탁1 > 헹굼1 > 탈수1 > 빨래1 끝
세탁2 > 헹굼2 > 탈수2 > 빨래2 끝!
세탁3 > 헹굼3 > 탈수3 > 빨래3 끝!
....
세탁기는 세탁만 처리하고있고,
헹굼기는 헹굼만 하고,
탈수기는 탈수만 한다.
세탁기에서 세탁1이 끝나면 헹굼은 헹굼기에게 넘기고, 세탁기는 세탁2를 한다.
헹굼이 끝나면 그 빨래는 탈수기로 넘기고, 다른 세탁이 완료된 빨래를 헹군다.
이렇게 단계를 나누어서 처리하는 것으로, 하나의 세탁기를 통으로 사용할 때보다 같은 시간에 더 많은 빨래를 할 수 있다.
하나의 세탁기를 사용할 때는 세탁과 헹굼이 끝나도, 탈수를 하는동안 세탁과 헹굼의 기능을 사용하지 못한다는 문제점이 있었는데, 이를 역할을 나눔으로서 리소스의 낭비를 줄인 셈이다.
그래서 하나의 큰 과정을 n개의 세부과정으로 나누어 파이프라이닝 방식으로 처리하면
전체 처리과정에 드는 시간이 1/n 에 근접하게 줄어든다.
이를 명령어로 설명한다면, CPU = 세탁기, 명령어 = 빨래 이다.
아까 1번에서 살펴본 내용을 적용한다면,
명령어 처리라는 하나의 큰 과정을
명령어 인출 > 명령어 해석 > 피연산자 인출 > 실행 > 결과 저장
이렇게 5가지 세부 단계로 쪼개고,
각각의 역할을 담당하는 파트를 만들어서 전체 명령어 처리 시간을 줄이는 방법이 파이프라이닝이다.
(명령어 인출만 하는 놈, 명령어 해석만 하는 놈, 피연산자 인출만 하는 놈...)
물론 프로세서마다 명령어 처리를 어떻게 쪼개는지는 다르다.
(SPARC는 4개 단계로 쪼갠다.)
이 이미지는 MIPS 라는 RISC 유형의 프로세서 구조이다.
IF > ID > EX > MEM > WB
5가지 단계로 명령어를 처리하고 있다.
(Instruction Fetch > Instruction Decoding > Execution > Memory > Write Back)
아무튼 이 파트에서 중요한 부분은
'파이프라이닝' 이라는 기법을 이용해서 전체 명령어 처리 시간을 줄일 수 있다
라는 점이 되겠다.
3. SPARC 의 Pipelining Stage 예시
SPARC는 아래와 같이 4가지 단계로 명령어를 처리한다.
1. Fetch (F)
명령어를 받아와서 디코딩을 하고, 디코딩 결과 필요한 피연산자까지 레지스터에서 가져온다.
즉, 명령어와 명령어 실행시 필요한 데이터를 다 가져온다.
2. Execute (E)
그렇게 다 가져온 데이터를 명령어에 맞게 연산한다.
연산에는 '산술연산', '논리연산', '분기주소 계산', '메모리주소 계산' 등이 있다.
산술연산과 논리연산은 말 그대로 연산이고,
분기 주소 계산은, if , else 같은 분기문이 실행될 때, 어디로 건너뛸 지 그 주소를 계산하는 것이고,
메모리주소 계산은 연산 결과를 저장하거나, 데이터를 메모리에서 불러올 때,
그 메모리 주소 값을 계산하는 것이다.
3. Memory (M)
계산한 주소를 토대로 실제 메모리에 접근하여 읽고 쓰기를 진행한다.
4. Write Back (W)
연산 결과를 레지스터에 쓴다.
파이프라인을 사용할 때와 사용하지 않을 때, SPARC의 Stage를 처리하는 과정 차이를 보여준다.
다음 포스팅에서는 Pipelining 을 사용하는 경우 발생할 수 있는 Hazard 에 대해 정리할 예정이다.
'CS > 어셈블리' 카테고리의 다른 글
[SPARC] 7. SPARC Architecture & Registers (0) | 2023.09.22 |
---|---|
[SPARC] 6. Pipelining Hazard (2) | 2023.09.21 |
[SPARC] 4. Stack Machine, Single Register Machine, Multiple Register Machine (0) | 2023.09.14 |
[SPARC] 3. Computer System Organization (CPU Machine Type) (0) | 2023.09.13 |
[SPARC] 2. Computer System Organization (2) | 2023.09.08 |