Instruction Pipeline
- 명령어 파이프라인(Instruction Pipeline)은 순차적으로 명령어를 읽어 실행하는 프로세서에 적용되는 기술로, 한번에 하나의 명령어만 실행하는 것이 아닌, 하나의 명령어가 실행되는 도중에 다른 명령어 실행을 시작하는 식으로 동시에 여러개의 명령어를 실행하도록 해서 CPU가 프로그램 처리하는 성능을 높혀줄 수 있도록 하드웨어적으로 구현되어 있는 기능
명령어 처리에는 기본적으로 5단계로 이루어져 있다(단, 모든 명령어들이 위 과정을 모두 거치는 것은 아니다)
1. IF: 메모리로부터 Instruction Fetch (메모리로부터 명령어 불러옴)
2. ID: Instruction Decode & register read (명령어 해석, 레지스터 read)
3. EX: EXcute operation(작업 실행) 혹은 calculate address(주소 계산)
4. MEM: access MEMory operand (메모리에 접근)
5. WB: Write result Back to register (레지스터에 결과를 다시 기록)
그렇다면 왜 명령어 처리를 하는데에 이렇게 단계를 쪼개놨을까? 만약에 명령어 하나를 처리하는데 위와 같은 단계로 쪼개지 않는다면 어떻게 될까?
이렇게 한 명령어( lw $1, 100($0) )가 끝날 때 까지, 다음 명령어( lw $2, 200($0 )가 기다려야 하는 것을 볼 수 있다.
참고로 명령어에는 여러 종류가 있다. 메모리 참조 명령어(lw, sw), 산술 논리 명령어(AND, OR, ADD, SUB), 분기 명령어(BEQ, j)가 있다.
따라서 파이프라이닝은 병렬성을 이용해서 단위 시간당 처리하는 처리량을 늘려서 성능을 개선하는 방식이다.
Hazard
그런데 파이프라이닝을 사용하다보면 Structural Hazard, Data Hazard, Control Hazard라는 문제가 생길 수 있다.
Structural Hazard
- 서로 다른 명령어가 같은 하드웨어(프로세스 내 자원)에 동시에 접근하려고 해서 생기는 문제(프로세스 내 자원은 한번에 한 명령어로 인한 접근밖에 허용하지 못함)
예를 들어서 Data/Instruction Memory가 분리되어 있지 않은 Single Memory를 사용한다면, MEM Stage에서 메모리에 접근할 때, IF Stage에서 메모리에 접근하지 못하는 Structural Hazard가 발생한다
-> 아주 단순한 해결방법으로는 Stall 시키면 된다(성능상 손해). 아니면 Data Memory와 Instruction Memory가 따로 분리되어 있으면 위와 같은 Hazard를 해결할 수 있다
Data Hazard
- 명령어 실행을 위해 필요한 data가 준비되지 않아서 명령어를 실행할 수 없는 경우
2번째 Stage인 ID Stage에서 Register값을 읽어오니까 SUB instruction에서 $1 레지스터에 있는 값을 읽어와야 하는데, 이때는 이전 Instruction인 ADD의 결과가 저장되어 있지 않기 때문에(ADD instruction이 WB Stage를 아직 거치지 않았기 때문에) 이 경우, Data hazard가 발생했다고 말한다.
->해결 방법으로는 마찬가지로 성능상 손해를 감수하면서 Stall 시키는 방법도 있고, Data Forwarding을 하면 일부 케이스에서 해결 가능하다. Data Forwarding은 아래 그림처럼 연산이 끝나면 바로 필요한 곳으로 보내주는 것을 말한다
Control Hazard
- 분기 명령어(beq instruction)가 어떤 명령어로 점프할지 결정되는 Stage는 MEM Stage인데, 그렇다면 분기 명령어가 분기 여부를 결정할 때 까지 fetch되어서 파이프라인을 거치고 있을텐데, 분기가 결정 됐을 때 버려져야 할(=flush 되어야 할) 명령어가 생긴다. 이런 현상을 Control Hazard라고 한다
Control Hazard도 마찬가지로 Stall로 성능상 손해를 보면서 해결할 수 있다. 부분적인 다른 해결 방법중 하나가 Branch Prediction같은 기법이다
Branch Prediction
- branch가 어디로 분기할 것인지 확실히 알기 전에 미리 추측하는 기법. Branch Prediction에도 여러 기법이 있으며, 예측이 틀렸을 때 또한 페널티도 존재한다. Branch Prediction에는 Compile Time에 결정되는 Static한 방식과 Run Time에 결정되는 Dynamic한 방식도 존재한다.
먼저 Static한 방법에는 대표적으로 2가지가 있다
1. Always-Not-Taken
항상 분기되지 않는다고 예측해서 PC = PC + 4로 계산한다. 당연한 이야기지만, 예측이 틀리면 잘못 예측한 Instruction들을 Flush를 해야하므로 페날티가 존재한다.
위의 그림에서 Inst(h)가 Branch Instruction이면 EXE State에서 Target Address가 계산돼서 분기 여부가 결정된다. Always-Not-Taken으로 Branch Prediction을 했는데 참이라는 결과가 나올 경우 t3에 Inst(i), Inst(j)가 Flush되고, IF Stage에 분기된 Target Address에 Instruction이 Fetch된다
2. Branch Target Buffer(BTB)
쉽게 생각해서 Page Table같은 cache 방식을 이용하는 것이다. 처음에 BTB history에 아무것도 없다면 예측이 불가능하므로 target address가 EX Stage에서 계산된 후 BTB history에 tag(PC)와 target address가 저장된다.
branch instruction이 history에 채워지고 난 후 branch instruction이 들어오면 update된 table 내용에 따라 예측을 한다. 예측이 틀리면 table을 update한다.
non-branch instruction이 들어올 때에는 항상 pc = pc + 4로 update한다
Dynamic 방식에는 대표적으로 One-bit/Two-bit Scheme가 있다
먼저 One-bit Prediction은 예측이 틀리면 바로 예측을 바꾸는 것이다. 예를 들어 하드웨어가 분기가 될 것이라고 예측했는데 실제로는 안된다면 다음부터는 분기가 안될것이라고 예측하는 것이다.
Two-bit Prediction은 예측이 틀려도 한번은 그대로 예측하는 것이다. 예를 들어 하드웨어가 분기가 될 것이라고 예측했는데 실제로는 안되어도 한번 더 분기가 될 것이라고 예측하는 것이다. 만약에 연속 2번 분기가 될 것이라 예측했는데 틀리면 그때부터는 분기가 안될 것이라고 예측한다.
************************************************************************************************************************************
여담으로 CPU가 프로그램을 처리하는데에 대한 성능에 영향을 주는데에는 3가지 요소가 있다.
- 명령어 개수
- 평균 CPI(Clock Cycle Per Instruction)
- 클럭 속도
명령어 개수는 효율적인 알고리즘으로 프로그램을 구현하면 명령어 개수를 줄일 수 있을 것이다. 예를 들면 효율적인 Sorting이나, 길찾기 알고리즘을 쓰면 프로그램 실행시 더 적은 명령어를 실행하게 될 것이다. 또한 클럭 속도는 컴퓨터 구매할 때 CPU 성능에 몇 Hz라고 적힌 부분을 봤을 것이다. 그것이 클럭 속도다. 평균 CPI는 명령어 하나를 처리하는데 걸리는 필요한 클럭 사이클 수를 의미한다. 예를 들어, Load Instruction은 ALU Instruction보다 더 많은 클럭 사이클을 요구한다.
https://www.geeksforgeeks.org/correlating-branch-prediction/
'공부 > System Programming & Computer Structure' 카테고리의 다른 글
Call Stack, Callee/Callee-saved register (0) | 2022.09.07 |
---|---|
CPU구조와 내부 구성요소들의 역할 (0) | 2022.01.21 |