사람은 좀 더 나은 방법을 항상 고민하게 됩니다.
어떻게 하면 한정된 자원으로 최대의 효율을 뽑아낼 수 있을까?
이런 생각이 컴퓨터로 오게 되면
한정된 CPU로 얼마나 많은 작업을 돌릴 수 있을지 연구하게 됩니다.
그 연구의 결실로 나오게 된 작업방식을 파이프라이닝이라고 합니다.
(해당 글에 앞서, clock에 대한 개념을 먼저 잡고 가면 좋습니다.
관련 글을 첨부합니다)
2023.10.12 - [CS지식 학습] - CPU Clock은 무엇이고 어떻게 활용될까?
세탁물의 예시를 들어보겠습니다.
세탁-탈수-건조-옷장 이 선수대로 옷을 넣을 수 있습니다.
만약 맨 처음 그림처럼 작업을 한다면
오후 6시에서 새백2시에 작업이 끝이 납니다.
하지만 아래처럼 병렬식으로 작업을 진행한다면
훨씬 더 빠르게 끝날 수 있습니다.
왜 이런 작업이 가능할까요?
그건 각각의 작업이 '구분되어있기'때문입니다.
세탁-탈수-건조-옷장 이 과정은 전부 따로따로 구분이 되어있어
내가 A옷을 세탁하면서 B옷은 건조기에 돌려도 아무 상관없습니다.
이제 컴퓨터 프로세스로 돌아오겠습니다.
3가지 명령어에 대해서
각각을 순차적으로 실행하는 버전과
명령어를 병렬적으로 실행한 버전 두 가지를 들었습니다.
한눈에 보기에도 아래 과정이 더 짧아 보입니다.
수많은 작업을 병렬적으로 수행했기 때문에
CPU입장에서 시간을 적게 들여서
더 많은 작업을 수행할 수 있습니다.
파이프라인은 다음의 5단계를 병렬적으로 수행합니다.
IF:명령어 불러오기
->데이터에 저장된 명령어를 불러옵니다.
명령어의 다음지점을 가리키기 위해
4를 더한 후 이후에 다음 명령어를 수행합니다.
ID:명령어를 해석하고 레지스터파일 읽기
->레지스터 파일 각각을 어떻게 활용할지 정합니다.
EX:실행 및 주소계산
->명령어를 실행합니다.
if문, 조건문 등으로 인해 분기가 정해지면 다음 실행주소가 달라지기 때문에
이에 대한 주소를 계산합니다.
MEM:데이터메모리접근
->해당 명령어로 반영된 데이터메모리 주소에 접근합니다.
WB:레지스터에 다시 쓰기
->레지스터에 메모리영역에 저장된 값을 다시 추가합니다.
위에서 파이프라인 각각에
수행단계가 있다는 걸 알게 되었습니다.
그럼 각각을 실행한 결과를 어떻게 넘겨줄까요?
각 단계 사이마다
레지스터가 결과를 저장해 다음단계로 넘겨줍니다.
예를 들어 IF/ID레지스터는
IF단계에서 실행한 결과를 저장하고
그 결과를 ID단계에 넘겨주는 식으로요.
파이프라이닝 구조를 활용하면
구조에 의한 오류가 발생하고
이를 hazard라고 부릅니다.
대표적인 케이스를 살펴보겠습니다.
가장 첫 번째로 Data Hazard입니다.
가장 첫 줄에 x2 레지스터에 x1, x3값을 빼준 값을 넣습니다.
이 x2값이 register에 반영이 되는 시간은
그림에서 cc5 clock이후입니다.
문제는 2번째 줄에서 4번째 줄 명령어를 실행할 때
이 x2값이 반영되지 않는다는 점입니다.
두 번째 줄->cc3에서 레지스터 값을 불러오는데 아직 x2값이 반영 안 됨
세 번째 줄->cc4에서 레지스터값을 불러오는데 아직 x2값이 반영 안 됨
네 번째 줄->cc5에서 레지스터값을 불러오는데 아직 x2값이 반영 안 됨
이런 구조적 문제로 인해, 데이터값을 제대로 불러올 수 없는 문제가 발생합니다.
하지만 여기서 이런 생각을 할 수 있습니다.
"계산한 결과를 바로바로 당겨서 보면 해결할 수 있는 거 아닌가??"
그렇게 나온 개념이 "forwarding"이며
그림과 같이 수정할 수 있습니다.
첫 번째 줄에서 수행한 결과를 미리 가져와서
두 번째, 세 번째 수행에도 이를 반영할 수 있는 방식입니다.
위 그림은 Forwarding Unit을 추가한 파이프라인 논리회로입니다.
EX/MEM MEM/WB의 값을 forwarding 해주어
다음 clock에 바로 반영되게끔 해줍니다.
메모리에 반영한 걸 다시 레지스터에 쓰는 동작보다
바로바로 레지스터에 반영하면 병렬 수행이 가능해
연산효율이 더 증가하기 때문에 다음의 구조를 활용합니다.
이 방법으로 두 번째 세 번째 명령에 대한 병렬연산이 가능하지만
네 번째 줄의 연산은 수행하지 못합니다.
여전히 x2값이 반영이 안 되었기 때문이죠
이때는 delay를 주는 방법이 있습니다.
"nop"라는 빈값의 명령어를 두 번째 줄에 삽입해서
("bubble"이라고도 부릅니다)
이후 다른 명령어의 clock cycle를 하나 미루는 방식이 있습니다.
이 방법은 전체 사이클을 하나 증가시키지만
clock에 의한 반영시간을 맞춰주어
병렬수행이 가능하도록 만듭니다.
컴퓨터에는 분기동작을 수행하는 경우도 많습니다.
if문이라든가 반복문처럼
특정 조건에 따라 명령을 수행해야 하는 경우도 생깁니다.
그림과 같은 케이스에서는
첫 번째 줄에 조건이 만족하고 분기주소가 결정되는 건 Mem단계입니다.
그전까지 2,3,4라인의 명령어들은 순차적으로 실행이 됩니다.
만약에 조건을 만족한다면 72번지 명령을 수행해야 하는데,
그럼 2,3,4라인의 명령어들은 분기가 끝날 때까지
flush를 해주어 명령을 버리는(discard),
딜레이가 길어지는 문제가 발생합니다.
이런 문제를 control hazard라고 부릅니다.
이 문제에 대해 앞서 data Hazard를 해결했을 때와 유사하게
별도의 unit을 추가하는 것으로 해결할 수 있습니다.
beq 명령이 실행이 될 때
해당 명령에 대한 다음 명령어값을 hazard detection unit에 반영합니다.
반영한 값을 IF단의 PC에 입력해서 다음명령주소를 가리키게 만듭니다.
이 방식으로 dealy를 최소화해서
다음 명령이 수행할 ld x4 50이 있는 값으로 명령을 수행하도록 만듭니다.
int a[8]={0,1,2,3,4,5,6,7};
printf("%d",a[8]); //주소값 잘못 참조한 stack over flow error
printf("%d",b);//정의되지 않은 값을 사용해서 error
우리가 프로그래밍을 하다보면
코딩실수로 인한 error를 만나게 됩니다.
그 중 대표적으로
주소를 잘못참조해서 발생하는 stack over flow라든가
정의가 안된 값을 사용하는 등의 에러가 있지요.
이런 에러가 발생하면
명령자체를 flush(동작안하게 만들기)해야합니다.
주소값을 잘못참조하거나
정의되지 않은 변수를 활용할 경우
flush를 시켜주는 동작을 만들고
예외처리를 해주는 동작으로 주소를 바꿔줍니다.
이제 여러분이 사용하는 컴퓨터는
CPU를 2개이상 활용하는 경우가 많을 겁니다.
이제 각 CPU에 명령을 보내 병렬적으로 처리를 한다했을 때
어떤 순서로 처리를 해야 서로 데이터 병목이 생기지 않는지 고려하기 위해서는
파이프라인의 개념을 알아야합니다.
컴파일러는 컴파일과정에서
'최적화'를 수행합니다.
최적화는 프로그래머가 작성한 코드 동작의 결과를 그대로 나오게하되
순서 및 코드생략을 통해 효율을 더 높여줍니다.
최적화를 할 때 파이프라인 기법에 따라
코드의 순서를 변경하되
Hazard가 없도록 기계어를 변환합니다.
운영체제는 CPU의 작업에 대해서 관여합니다.
어떻게하면 자원을 효율적으로 사용하고
어떻게하면 예외를 처리할지 등등
CPU의 전반적인 작업을 다룹니다.
파이프라인기법에 따라 자원을 효율적으로 나누고
어떤 예외를 처리할지 등을 다룹니다.
참고자료
Computer Organization and Design RISC-V edition
숭실대학교 박민호 교수님 강의자료
프로세스 vs 쓰레드, 뭐가 다르고 어떻게 활용될까?? (0) | 2023.11.02 |
---|---|
캐시메모리는 무엇이고 어떻게 활용이 될까?? (1) | 2023.10.23 |
CPU Clock은 무엇이고 어떻게 활용될까? (0) | 2023.10.12 |
메모리와 명령어의 상호작용, 왜 알아야할까?? (1) | 2023.10.10 |
2진수계산,왜 알아야하고 어떻게 사용할까?? (1) | 2023.10.08 |
댓글 영역