2/15 월 ~ 2/21 일 : 6시간 33분
○○●○●/●●
컴구 극혐극혐극혐극혐극혐극혐
앞에 3장은 그럭저럭 봤는데 프로세서에서 정신이 혼미해짐
2/17 수 : 2시간 2분
2/19 금 : 41분
2/20 토 : 2시간
2/21 일 : 1시간 50분
4. 프로세서
4.4 단순한 구현
주 제어 유닛 설계
... 앞 주차에 이어서...
2장의 첫 번째 설계 원칙인 "간단하게 하기 위해서는 규칙적인 것이 좋다" 는 여기에서 제어를 명시하는 데도 잘 들어맞는다.
위의 정보를 이용하여 단순한 데이터패스에
명령어 레이블과 또 다른 멀티플렉서(레지스터 파일의 Write register 번호 입력을 위하여)를 추가한다.
그림 4.15는 이 같은 추가 이 외에도
ALU 제어 블록, 상태소자용 쓰기 신호, 데이터 메모리용 읽기 신호, 멀티플렉서용 제어신호를 보여 주고 있다.
모든 멀티플렉서는 두 개의 입력을 가지고 있기 때문에 멀티플렉서는 하나의 제어선을 필요로 한다.
제어선들은 파란색으로 표시되었고 ALU 제어 블록이 추가되었다.
PC는 매 클럭 사이클 끝에서 한 번씩 쓰기가 행해지므로 쓰기 제어신호가 필요하지 않다.
분기 제어회로는 PC에다 증가된 PC 값을 쓸 것인지 분기 목적지 주소를 쓸 것인지 결정한다.
그림 4.15에는 1비트 제어선 7개와 2비트 ALUOp 제어신호 하나가 있다.
그림 4.16은 이 7개의 제어선의 기능을 설명하고 있다.
제어선 각각의 기능에 대하여 살펴보았으니 이제는 제어선들의 값을 어떻게 할지 알아보자.
제어 유닛은 제어신호 중 하나를 제외한 나머지 모두를 명령어의 opcode 필드만 보고 결정할 수 있다.
PCSrc 제어선만은 예외이다. 실행중인 명령어가 branch on equal이며(제어유닛이 판단 가능) 동시에 ALU의 Zero 출력이 참일 경우에만 PCSrc가 인가되어야 한다.
이들 9개의 제어신호들은(그림 4.16의 7개와 ALUOp 두 비트) 제어 유닛의 6개 입력신호(opcode 비트 31:26)에 따라서 결정된다.
제어 유닛과 제어신호가 나와 있는 데이터패스는 그림 4.17에 있다.
제어 유닛의 입력은 명령어의 6비트 opcode 필드이다.
제어 유닛의 출력은 멀티플렉서를 제어하는 데 쓰이는 세 개의 1비트 신호(RegDst, ALUSrc, MemtoReg),
레지스터 파일과 데이터 메모리에서 읽고 쓰는 것을 제어하기 위한 3개의 신호(RegWrite, MemRead, MemWrite),
분기할지 말지를 판단하는 데 쓰이는 1비트 신호(Branch),
ALU를 위한 2비트 제어신호(ALUOp)이다.
분기 제어신호와 ALU의 Zero 출력을 결합하는 데 AND 게이트를 사용한다.
이 AND 게이트 출력은 다음 PC 값을 선택하는 데 쓰인다.
PCSrc는 제어 유닛으로부터 직접 나오는 값이 아니고 만들어야 되는 신호이다.
따라서 다음 그림부터는 이 신호 이름을 빼 버린다.
제어 유닛에 대한 수식이나 진리표를 작성하기 전에 제어 기능을 간략하게 정의하는 것이 유익하다.
제어 신호의 값은 opcode에만 의존하기 때문에,
각각의 opcode값에 대해 각 제어신호가 0, 1, don't care(X) 중 어느 값이 되어야 하는 지를 정의한다.
그림 4.18은 제어신호들이 각각의 opcode에 대해 어떤 값이 되어야 하는지를 나타낸다.
- R-format (add, sub, AND, OR, slt)
이들 명령어 모두의 근원지 레지스터 필드는 rs, rt이고 목적지 레지스터 필드는 rd이다.
이 사실에서 ALUSrc와 RegDst 값이 결정된다.
레지스터에 쓰기를 하지만(RegWrite = 1), 데이터 메모리를 읽거나 쓰지는 않는다.
Branch 제어신호가 0일 때 PC는 무조건 PC+4로 바뀐다.
Branch가 1이고 ALU의 Zero 출력이 1이면 PC에 분기 목적지 주소가 들어간다.
ALUOp 필드가 10이 되어서 funct 필드를 보고 ALU 제어신호를 만들어야 한다는 것을 나타낸다.
- lw, sw
ALUsrc와 ALUOp 값은 주소 계산에 적합하게 정해진다.
MemRead와 MemWrite는 메모리 접근을 할 수 있도록 설정된다.
RegDst와 RegWrite는 적재 명령어가 결과를 rt 레지스터에 저장할 수 있게 정해진다.
- beq
rs와 rt 레지스터 값을 ALU에 보내기 때문에 R형식 연산과 비슷하다.
ALUOp 필드는 같은지 비교하기 위한 뺄셈을 하도록(ALU제어 = 01) 설정된다.
데이터패스의 동작
그림 4.16과 4.18에 포함된 정보를 가지고 제어 유닛의 논리회로를 설계할 수 있다.
설계에 들어가기 전에 각각의 명령어가 데이터패스를 어떻게 사용하는지를 살펴보자.
다음 몇 개의 그림에서 세 가지 명령어 종류들이 데이터패스를 통과하는 흐름을 보인다.
각각의 그림에서 인가된 제어신호와 활성화된 데이터패스 구성 요소는 진하게 표시하였다.
모든 일이 하나의 클럭 사이클에 일어나지만, 명령어 실행을 네 단계로 생각할 수 있다.
이들 단계는 정보의 흐름에 따라 순서가 결정된다.
add $t1, $t2. $t3
1. 명령어를 명령어 메모리에서 가져오고 PC 값을 증가시킨다.
2. 두 레지스터 $t2, $t3를 레지스터 파일로부터 읽는다. 이 단계에서 주 제어 유닛이 제어선의 값들을 계산한다.
3. ALU는 레지스터 파일에서 읽어들인 값들에 대해 연산을 하는데 funct 필드를 사용하여 ALU 제어 신호를 만든다.
4. ALU의 결과 값이 레지스터 파일에 기록되는데, 목적지 레지스터($t1)는 명령어의 비트 15:11을 이용하여 선택한다.
lw $t1, offset($t2)
1. 명령어를 명령어 메모리에서 가져오고 PC 값을 증가시킨다.
2. 레지스터($t2)의 값을 레지스터 파일로부터 읽는다.
3. ALU는 레지스터 파일에서 읽어 들인 값과 명령어의 하위 16비트(offset)을 부호확장한 값과의 합을 구한다.
4. 이 합을 데이터 메모리 접근을 위한 주소로 사용한다.
5. 메모리 유닛에서 가져온 데이터를 레지스터 파일에 기록한다. 목적지 레지스터($t1)는 명령어의 비트 20:16이 지정한다.
beq $t1, $t2, offset
1. 명령어를 명령어 메모리에서 가져오고 PC값을 증가시킨다.
2. 두 레지스터 $t1과 $t2를 레지스터 파일로부터 읽는다.
3. ALU는 레지스터 파일에서 읽어 들인 값들에 대해 뺄셈을 한다.
명령어의 하위 16비트(offset)를 부호확장한 후 2비트 왼쪽 자리이동한 값에다 PC+4값을 더한다.
결과 값이 분기 목적지 주소이다.
4. 어떤 덧셈기의 결과를 PC에 저장할지 ALU의 Zero 출력을 이용하여 판단한다.
제어 유닛의 완성
이제까지 명령어들이 단계별로 어떻게 동작하는지를 알아보았으니
이제는 제어 유닛의 구현에 대하여 알아보자.
제어 유닛의 입력은 6비트의 opcode 필드(Op[5:0])
제어 유닛의 출력은 제어선들이다.
opcode의 이진수 인코딩을 이용하여 각 출력의 진리표를 만들 수 있다.
제어 유닛의 논리를 커다란 진리표 하나로 만든 것이 그림 4.22이다.
이것은 제어 기능을 완벽하게 명시하며 자동화된 방법을 이용하여 게이트로 곧바로 구현할 수 있다.
이제 MIPS 핵심 명령어 집합에 대한 단일 사이클 구현(single-cycle implementation)을 완성하였으니,
명령어 집합의 다른 명령어를 처리하기 위해
어떻게 기본 데이터패스와 제어가 확장될 수 있는지를 보여 주기 위해 점프 명령어를 추가하자.
예제 : 점프 명령어의 구현
점프 명령어를 포함하도록 그림 4.17의 데이터패스와 제어를 확장하라.
새로운 제어선들의 값은 어떻게 결정되는지 설명하라.
점프 명령어는 어떤 면에서는 분기 명령어와 비슷하지만
목적지 PC 값 계산 방식이 다르고 또한 조건 분기가 아니라는 점이 다르다.
목적지 PC 값
- 현재 PC+4의 상위 4비트
- 점프 명령어의 26비트 수치 필드
- 비트 00
그림 4.24는 그림 4.17에 점프 명령어를 위한 제어가 추가된 것을 보여 주고 있다.
증가된 PC 값(PC+4), 분기 목적지 PC, 점프 목적지 PC 중에서
하나를 새로운 PC값의 근원지로 선택하기 위해 멀티플렉서가 추가되었다.
제어신호 jump가 이 멀티플렉서를 제어한다. 즉 opcode가 2일 때만 인가된다.
단일 사이클 구현은 오늘날 왜 사용되지 않는가?
비효율성 때문에 현대적 설계에서는 쓰이지 않는다.
4.5 파이프라이닝에 대한 개관
파이프라이닝(pipelining)은 여러 명령어가 중첩되어 실행되는 구현 기술이다.
오늘날 파이프라이닝은 아주 보편적인 기술이다.
파이프라인 방법 -> 돌림노래
만약 모든 단계가 거의 같은 시간이 걸리며 할 일이 충분히 많다면
파이프라이닝에 의한 속도 향상은 파이프라인의 단계 수와 같다.
명령어 실행을 파이프라이닝한 프로세서에도 같은 원리가 적용된다.
MIPS 명령어는 전통적으로 다섯 단계가 걸린다.
1. 메모리에서 명령어를 가져온다.
2. 명령어를 해독하는 동시에 레지스터를 읽는다. MIPS 명령어는 형식이 규칙적이므로 읽기와 해독이 동시에 일어날 수 있다.
3. 연산을 수행하거나 주소를 계산한다.
4. 데이터 메모리에 있는 피연산자에 접근한다.
5. 결과 값을 레지스터에 쓴다.
따라서 이 장에서 살펴보는 MIPS 파이프라인은 다섯 단계를 가진다.
다음 예제에는 파이프라이닝이 명령어 실행에서 속도를 증가시키는 것을 보여 준다.
예제 : 단일 사이클 대 파이프라인의 성능
이 논의를 구체적으로 하기 위해 파이프라인을 만든다.
이 예제와 이 장의 나머지에서는 load word(lw), store word(sw), add(add), substract(sub), AND(and), OR(or), set-less-than(slt), branch-on-equal(beq) 8개의 명령어에만 관심을 갖도록 한다.
단일 사이클 구현에서 명령어 사이의 평균 시간을 파이프라인 구현의 경우와 비교하라.
단일 사이클 구현에서는 모든 명령어가 한 클럭 사이클 걸린다.
따라서 가장 느린 명령어를 수용할 수 있을 만큼 클럭 사이클이 길어져야 한다.
답
8개의 명령어 각각에 필요한 시간은 그림 4.26에 나와 있다.
단일 사이클 설계는 가장 느린 명령어를 수용해야 한다.
그림 4.27은 그림 4.25와 비슷하게
3개의 워드 적재 명령어의 파이프라이닝되지 않은 실행과 파이프라인 실행을 비교한다.
파이프라이닝되지 않은 실행에서 첫 번째 명령어와 네 번째 명령어 사이의 시간은 3 x 800 = 2400ps 이다.
파이프라인 실행은 첫 번째 명령어와 네 번째 명령어 사이의 시간은 3 x 200ps, 즉 600ps 이다.
명령어 간의 평균 시간이 800ps에서 200ps로 낮아지면서 4배의 속도 향상을 볼 수 있다.
위에 설명한 속도 향상에 관한 논의를 식으로 바꿀 수 있다.
단계들이 완벽하게 균형을 이루고 있으면 파이프라인 프로세서에서 명령어 사이의 시간은 다음과 같다.
이상적인 조건하에 많은 명령어가 있을 경우 파이프라이닝에 의한 속도 향상은 파이프 단계 수와 거의 같다.
다섯 단계 파이프라인은 거의 다섯 배 더 빠르다.
위 식에 따르면 다섯 단계 파이프 라인은 800ps의 파이프라이닝되지 않은 시스템보다 5배 향상된 성능을 제공해야 한다.
즉 클럭 사이클이 160ps가 되어야 한다.
그러나 예제는 단계가 완벽하게 균형 잡혀 있지는 않다는 것을 보여준다.
더구나 파이프라이닝은 어느 정도의 오버헤드를 포함하고 있다.
이 오버헤드의 원인이 어디 있는 지는 금방 알게 될 것이다.
이런 이유로 파이프라인 프로세서에서의 명령어당 시간이 가능한 최솟값보다 커져서 속도 향상은 파이프라인 단계 수보다 작아진다.
더구나 예제에서 4배만큼의 성능 향상이 있다는 주장은 3개의 명령어에 대한 전체 실행시간에 반영되어 있지 않다.
실행시간은 1400ps 대 2400ps이다. 물론 이것은 전체 명령어 개수가 많지 않기 때문이다.
명령어의 수를 증가시키면 무슨 일이 일어날까?
파이프라인 예제에 1,000,000개의 명령어를 추가한다.
파이프라인 x = 1,000,000 x 800ps + 2400ps = 800,002,400ps
파이프라인 o = 1,000,000 x 200ps + 1400ps = 200,001,400ps
실제 프로그램의 전체 실행시간의 비율은 명령어 사이의 시간 비율에 가깝다.
- > 파이프라이닝은 개별 명령어의 실행시간을 줄이지는 못하지만 대신 명령어 처리량을 증대시킴으로써 성능을 향상시킨다.
파이프라이닝을 위한 명령어 집합 설계
MIPS 명령어 집합은 원래 파이프라인 실행을 위해 설계된 것이다.
첫째, 모든 MIPS 명령어는 같은 길이를 갖는다.
이 같은 제한조건은 첫 번째 파이프라인 단계에서 명령어를 가져오고 그 명령어들을 두 번째 단계에서 해독하는 것을 훨씬 쉽게 해준다.
둘째, MIPS는 몇 가지 안 되는 명령어 형식을 가지고 있다.
모든 명령어에서 근원지 레지스터 필드는 같은 위치에 있다.
이 같은 대칭성은 두 번째 단계에서 하드웨어가 어떤 종류의 명령어가 인출되었는지를 결정하는 동안
레지스터 파일 읽기를 동시에 할 수 있다는 것을 의미한다.
셋째, MIPS에서 메모리 피연산자가 적재와 저장 명령어에서만 나타난다.
이 같은 제한은 메모리 주소를 계산하기 위해 실행 단계를 사용하고 다음 단계에서 메모리에 접근할 수 있다는 것을 의미한다.
넷째, 2장에서 설명한 바와 같이 피연산자는 메모리에 정렬(align)되어 있어야 한다.
따라서 한 데이터 전송 명령어가 두 번의 데이터 메모리 접근을 요구할까 봐 걱정할 필요가 없다.
파이프라인 단계 하나에서 프로세서와 메모리가 필요한 데이터를 주고받을 수 있다.
파이프라인 해저드
다음 명령어가 다음 클럭 사이클에 실행될 수 없는 상황이 있다.
이러한 사건을 해저드(hazard)라 부르는데 세 가지 종류가 있다.
구조적 해저드
첫 번째 해저드는 구조적 해저드(structural hazard)라 불린다.
- 구조적 해저드 : 주어진 클럭 사이클에 실행되도록 되어있는 명령어 조합을 하드웨어가 지원하지 못해서 계획된 명령어가 적절한 클럭 사이클에 실행될 수 없는 시간
MIPS 명령어 집합은 파이프라이닝하도록 설계되었지 때문에
설계자가 파이프라인을 설계할 때 구조적 해저드를 피하는 것이 비교적 용이하다.
그러나 메모리가 두 개가 아니고 하나라고 생각해 보자.
그림 4.27의 파이프라인에 네 번째 명령어가 추가된다면 같은 클럭 사이클에 첫 번째 명령어는 메모리에서 데이터에 접근하고,
네 번째 명령어는 같은 메모리에서 명령어를 가져오게 된다. 메모리가 하나라면 구조적 해저드를 피할 수 없다.
데이터 해저드
데이터 해저드(data hazard)는 어떤 단계가 다른 단계가 끝나기를 기다려야 하기 때문에 파이프라인이 지연되어야 하는 경우 일어난다.
- 데이터 해저드 : 명령어를 실행하는 데 필요한 데이터가 아직 준비되지 않아서, 계획된 명령어가 적절한 클럭 사이클에 실행될 수 없는 사건.
컴퓨터 파이프라인에서는 어떤 명령어가 아직 파이프라인에 있는 앞선 명령어에 종속성을 가질 때 데이터 헤저드가 일어난다.
예를 들어 add 명령어 바로 다음에 add의 합($s0)을 사용하는 뺄셈 명령어가 뒤따르는 경우를 가정하자
add $s0, $t0, $t1
sub $t2, $s0, $t3
별다른 조치가 없다면 데이터 헤저드가 파이프라인을 심각하게 지연시킬 수 있다.
add 명령어는 다섯 번째 단계까지는 결과 값을 쓰지 않을 텐데 이는 파이프라인이 세개의 클럭 사이클을 낭비해야 한다는 것을 의미한다.
컴파일러로 해결 못함
첫 번째 해결책은 명령어가 끝날 때까지 기다릴 필요가 없다는 관찰에 기반을 두고 있다.
위와 같은 코드인 경우 ALU가 add 명령어의 합을 만들어 내자마자 이것을 뺄셈의 입력으로 사용할 수 있다.
별도의 하드웨어를 추가하여 정상적으로 얻을 수 없는 값을
내부 자원으로부터 일찍 받아오는 것을 전방전달(forwarding) 또는 우회전달(bypassing)이라고 한다.
- 전방전달 : 프로그래머가 볼 수 있는 레지스터나 메모리에 아직 나타나지 않은 데이터를 기다리기보다는 내부 버퍼로부터 가져옴으로써 데이터 해저드를 해결하는 방법
예제 : 두 명령어 사이의 전방전달
앞의 두 명령어에서 어느 파이프라인 단계가 전방전달에 의해 연결되어야 하는 지를 보여라.
파이프라인 다섯 단계 동안의 데이터패스를 나타내기 위해 그림 4.28의 그림을 사용하라.
각 명령어에 데이터패스를 한 벌씩 할당해 정렬하라
이 장에서는 물리적 자원을 표시하는 기호의 약어로 파이프라인 단계를 표시한다.
각 단계를 표시하는 기호는 다음과 같다.
명령어 인출단계는 IF로 나타내는데, 명령어 메모리를 뜻하는 상자와 함께 표시한다.
명령어 해독/레지스터 파일 읽기 단계는 ID로 나타내는데, 읽히고 있는 레지스터 파일을 나타내는 상자와 함께 표시
실행단계는 EX로 나타내는데, ALU를 나타내는 그림과 함께 표시
메모리 접근 단계는 MEM으로 나타내는데, 데이터 메모리를 표시하는 상자와 함께 표시
쓰기 단계는 WB, 쓰기가 행해지고 있는 레지스터 파일
어둡게 그려져 있는 것은 그 구성 요소가 명령어에 의해 사용되고 있다는 것을 나타낸다.
오른쪽 반이 어두운 것 : 그 단계에서 읽히고 있다.
왼쪽 반이 어두움 : 그 단계에서 쓰기 행해지고 있다
답
그림 4.29는 add 명령어 실행 단계 후의 $s0 값을 sub 명령어의 실행 단계 입력으로 전방전달하기 위한 연결을 보여 주고 있다.
그림 표현에서 목적지 단계가 근원지 단계보다 시간상 늦을 경우에만 전방전달 통로가 유효하다.
전방전달은 매우 잘 동작하는데 자세한 것은 4.7절에서 설명한다.
그러나 전방전달이 모든 파이프라인 지연을 방지할 수는 없다.
첫 번째 명령어가 add 명령어가 아니고 $s0의 적재 명령어라고 가정하자
이 때 적재-사용 데이터 해저드(load-use data hazard)가 일어난다.
- 적재-사용 데이터 해저드 : 적재 명령어에 의해 적재된 데이터를 다른 명령어가 필요로 하는데, 필요한 시점까지 이 데이터가 도착하지 않아서 생기는 특별한 형태의 데이터 해저드
이 경우에는 전방전달을 해도 한 단계가 지연되어야 한다.
이 그림은 파이프라인 지연(pipeline stall)이라는 중요한 파이프라인 개념을 보여 주고 있다.
지연은 거품(bubble)이라는 별명으로 불리는 경우도 많다.
파이프라인의 다른 곳에서도 지연을 볼 수 있다.
4.7절은 이같이 어려운 경우의 처리 방법을 설명하는데,
하드웨어 검출과 지연을 사용하거나 적재-사용 데이터 해저드를 피할 수 있게 명령어의 순서를 바꾸는 소프트웨어를 사용한다.
예제 : 파이프라인 지연을 피하기 위한 코드의 재정렬
C로 작성된 다음 코드를 생각해 보자.
a = b + e; c = b + f;
다음은 이 코드에 대한 MIPS 코드이다.
모든 변수는 메모리에 있고 $t0를 베이스로 사용해서 접근할 수 있는 위치에 있다고 가정한다.
위 코드에서 해저드를 찾아내고 파이프라인 지연을 피할 수 있도록 명령어들을 재정렬하라.
답
두 add 명령어가 모두 해저드를 가지고 있는데
이는 바로 앞의 명령어인 lw 명령어와 각각 종속성이 있기 때문이다.
세 번째 lw 명령어를 위로 올리면 두 해저드가 모두 없어진다.
전방전달 유닛이 있는 파이프라인 프로세서에서, 재정렬된 코드는 원래 코드보다 두 사이클 먼저 완료된다.
전방전달은 언급한 네 가지 통찰(4.5절) 외에 MIPS 구조에 대한 또 다른 점을 인식하게 한다.
각각의 MIPS 명령어는 기껏해야 하나의 결과 쓰기를 할 뿐이며 그것도 파이프라인 끝에서 한다.
명령어 하나에 전방전달해야 하는 결과가 여러 개 있든가 명령어 실행의 초기에 결과 쓰기를 한다면 전방전달은 더 어려워졌을 것이다.
제어 헤저드
세 번째 헤저드는 제어 해저드(control hazart)라 불리는데 다른 명령어들이 실행 중에 한 명령어의 결과 값에 기반을 둔 결정을 할 필요가 있을 때 일어난다.
컴퓨터의 제어 해저드에 대한 두 가지 해결책이 있는데, 그중 첫 번째는 지연이다.
컴퓨터에서 이런한 결정 작업에 해당하는 것이 바로 분기 명령어이다.
바로 다음 클럭 사이클에서 분기 명령어를 이을 명령어를 가져오기 시작해야 한다.
그러나 파이프라인은 다음 명령어가 어느 것이 되어야 할지 알 수가 없다.
왜냐하면 이제 방금 메모리에서 분기 명령어를 받았을 뿐이기 때문이다.
한 가지 가능한 해결책은 분기 명령어를 가져온 직후 지연시켜서,
파이프라인이 분기의 결과를 판단하고 어느 주소에서 다음 명령어를 가져올지 알게 될 때까지 기다리게 하는 것이다.
하드웨어가 층분하기 때문에
파이프라인의 두 번째 단계에서 레지스터를 테스트하고 분기 주소를 계산하고 PC 값을 바꿀 수 있다고 가정하자
이렇게 별도의 하드웨어가 있어도 조건부 분기가 포함된 아래 프로그램을 실행하는 파이프라인은 그림 4.31처럼 보일 것이다.
예제 : 분기 시 지연(stall on branch)의 성능
분기 명령어가 나오면 지연시키는 방법이 CPI에 미치는 영향을 추정하라.
다른 모든 명령어의 CPI는 1이라고 가정한다.
답
3장의 그림 3.28에서 본 바와 같이 분기 명령어가 SPECint2006에서 실행되는 명령어의 17% 이다.
다른 명령어들은 CPI가 1이고, 분기 명령어는 지연 때문에 한 클럭 사이클이 더 필요하다.
따라서 CPI는 1.17이 되고 이상적인 경우와 비교하면 1.17배 속도 저하가 생긴다.
파이프라인이 긴 경우에는 흔히 그렇듯이 분기를 두 번째 단계에서 다 해결하지 못한다면,
분기 명령어마다 지연시키는 것은 훨씬 더 큰 속도 저하를 초래할 것이다.
따라서 두 번째 해결책이 나오게 되었다.
대부분의 컴퓨터가 분기 명령어를 다루기 위해서 예측(prediction)을 사용한다.
간단한 방법은 분기가 항상 실패한다고 예측하는 것이다.
예측이 옳으면 파이프라인은 최고 속도로 진행된다. 실제로 분기가 일어날 때만 파이프라인이 지연된다.
그림 4.32는 이러한 예를 보여준다.
분기 예측(branch prediction)에 대한 좀 더 정교한 버전은
- 분기 예측(branch prediction) : 실제 분기 결과가 확인될 때까지 기다리는 대신, 분기 결과를 가정하고 그 가정하에 파이프라인을 진행해 나가는 분기 해저드 해결 방법
어떤 경우는 분기한다(taken)고 예측하고 어떤 경우는 분기하지 않는다고(untaken) 예측하는 것이다.
프로그래밍의 경우 순환문의 끝에는 순환문의 꼭대기로 점프하라는 분기 명령어가 있다.
이 명령어들은 분기가 일어날 가능성이 높고 분기 방향이 후방이므로,
이에 착안하여 현재 위치보다 작은 주소로 점프하는 분기 명령어는 분기가 항상 일어난다고 예측할 수 있다.
이러한 분기 예측 방법들은 보편적 행동에 의존하며 특정 분기 명령어의 개별성은 고려하지 않는다.
동적 하드웨어 예측기(dynamic hardware preditor)는 이와는 정반대로
개별 분기 명령어의 행동에 의존하는 예측을 하며 프로그램이 진행되는 도중에 예측을 바꿀 수 있다.
분기의 동적 예측에 대한 보편적 방법 중 하나는 각 분기가 일어났는지 안 일어났는지 이력을 기록하고,
최근의 과거 이력을 사용하여 미래를 예측하는 것이다.
좀 뒤에 보겠지만 유지되는 이력의 양이나 정보의 종류가 많아져서 그 결과 동적 분기 예측기가 90% 이상의 정확도를 가지게 되었다.
파이프라이닝 개관에 대한 요약
파이프라이닝은 순차적인 명령어 스트림에 있는 명령어 간 병렬성을 추구하는 기술이다.
이는 멀티 프로세서 프로그래밍과는 달리 기본적으로 프로그래머에게 보이지 않는다는 상당한 이점을 가지고 있다.
이 장의 다음 절들에서
1. 4.4절의 단일 사이클 구현에서 사용한 MIPS 명령어 집합의 일부를 사용하여 파이프라이닝의 개념을 설명
2. 파이프라인의 단순화된 버전을 보인다.
3. 파이프라이닝이 갖는 문제점
4. 전형적 상황에서 얻을 수 있는 성능
에 대하여 알아본다.
요점 정리
파이프라이닝은 동시에 실행되는 명령어의 수를 증가시키며 명령어들이 시작하고 끝나는 속도를 증가시킨다.
파이프라이닝은 각각의 명령어의 실행을 끝내는 데 걸리는 시간을 단축시키지는 않는데
이 시간을 지연시간(latency)라고 부른다.
- 지연시간 : 파이프라인의 단계 수 또는 실행 중인 두 명령어 사이의 단계 수
예를 들어 다섯 단계 파이프라인은 한 명령어가 끝나는 데 다섯 클럭 사이클이 걸린다.
1장에서 사용했던 용어를 사용하면
파이프라이닝은 각각의 명령어 지연시간(execution time 또는 latency)보다는 처리율을 향상시킨다.
4.6 파이프라인 데이터패스 및 제어
그림 4.33은 4.4절의 단일 사이클 데이터패스에 파이프라인 단계를 같이 보여 주고 있다.
명령어를 다섯 단계로 나눈 것은 다섯 단계 파이프라인을 의미하며
이는 한 클럭 사이클에 최대 5개의 명령어가 실행 중일 수 있다는 것을 의미한다.
따라서 데이터패스를 5개 부분으로 나누어야 하며 각 부분은 명령어 실행 단계에 따라 다음과 같이 이름이 붙여진다.
1. IF : 명령어 인출
2. ID : 명령어 해독 및 레지스터 파일 읽기
3. EX : 실행 또는 주소 계산
4. MEM : 데이터 메모리 접근
5. WB : 쓰기 (write back)
명령어 실행의 각 단계는 데이터패스의 왼쪽부터 차례로 사상될 수 있다.
유일한 예외는 PC 갱신과 쓰기 단계인데, 쓰기 단계는 ALU의 결과나 메모리에서 읽은 데이터를 왼쪽으로 보내 레지스터 파일에 쓴다.
그러나 명령어에서는 이깉이 왼쪽에서 오른쪽으로 흐르는 것에 두 가지 예외가 있다.
- 쓰기 단계 : 이 단계에서는 결과를 데이터패스의 중앙에 있는 레지스터 파일에다 쓴다.
- PC의 다음 값 선정 : 증가된 PC 값과 MEM 단계의 분기 주소 중에서 고른다.
오른쪽에서 왼쪽으로 흐르는 데이터는 현재 명령어에 영향을 주지 않는다.
파이프라인의 뒤쪽에 있는 명령어들만이 이 같은 역방향 데이터 흐름에 영향을 받는다.
첫 번재 연결선은 데이터 헤저드
두 번째 연결선은 제어 해저드로 이어질 수 있다.
파이프라인 실행에서 일어나는 일을 보여 주는 한 가지 방법은
각 명령어가 자신의 데이터패스를 가지고 있는 것처럼 하고, 이들을 시간 축에 배치하여 그들 사이의 관계를 보여 주는 것이다.
그림 4.34는 공통 시간 축에 명령어들 자신의 데이터패스를 보여 줌으로써 그림 4.27의 명령어 실행을 보여 준다.
명령어 메모리는 명령어의 다섯 단계 중 한 단계에서만 사용된다.
그러므로 이 명령어가 다른 네 단계에 있는 동안에 명령어 메모리는 다른 명령어가 사용할 수 있다.
다른 네 단계 동안에도 각 명령어의 값을 유지하기 위해 명령어 메모리에서 읽어 들인 값을 레지스터에 저장하여야 한다.
비슷한 논지가 모든 파이프라인 단계에 적용된다.
따라서 그림 4.33에서의 단계 사이를 나누는 선이 있는 곳마다 레지스터를 두어야 한다.
그림 4.35는 파이프라인 데이터패스를 보여 주는데 파이프라인 레지스터가 강조되어 있다.
모든 명령어는 매 클럭 사이클마다 한 파이프라인 레지스터에서 다음 레지스터로 전진한다.
쓰기 (write-back) 단계 끝에는 파이프라인 레지스터가 없다는 것에 주목하라.
모든 명령어는 컴퓨터의 상태 - 레지스터 파일, 메모리, PC - 를 갱신해야 한다.
이렇게 갱신되는 상태는 별도의 파이프라인 레지스터가 필요 없다.