앞에서 배운 것처럼 컴퓨터는 클록에 맞추어 매 클록 마다 한 개씩의 명령을 실행한다. 서로 다른 명령어들은 서로 다른 개수의 단계를 밟으며 실행되므로 실행시간이 다를 수 있지만 클록의 주기는 일정해야 하므로 가장 긴 실행시간을 필요로 하는 명령의 실행 시간에 맞추어 실행된다. 즉 비효율성이 발생한다.
우리 예제에서는 ldr 명령이 가장 긴 실행시간을 필요로 한다. 즉 한 명령이 실행되려면 다음과 같이 5단계를 밟는다.
이런 비효율성을 극복할 수 있는 한 가지 방법이 명령어 파이프라이닝 instruction pipelining 이다. 명령어 파이프라이닝은 명령이 실행되는데 소요되는 각 단계들을 중첩해서 여러 개의 단계들이 동시에 일어나도록 하는 방법이다.
명령어 파이프 라이닝을 적용하면 어느 정도의 성능 향상 speed up 을 얻을 수 있을까? 앞의 예제에서 본 것처럼 중첩되는 단계 stage 의 수 만큼 얻을 수 있다. 대학교의 경우 4년간을 중첩하므로 4배 빨라질 수 있고, 초등학교의 경우 6년간을 중첩하므로 6배 빨라질 수 있는 것이다.
주의해야 할 점은 명령어 파이프라이닝을 적용하더라도 한 개 명령을 실행하는데 소요되는 시간이 짧아지는 것은 아니다. 다만 단위 시간당 실행되는 명령어 개수가 증가하는 효과가 있다.
앞에서 ARM 프로세서는 프로그램 카운터 pc 의 값이 현재 실행 중인 명령의 주소보다 8바이트 더한 값이라고 말한 적이 있다. 명령어 길이가 4바이트이므로 pc 는 두 개 명령을 앞서 가고 있는 것이다. 이런 현상은 ARM 프로세서가 명령어 파이프라이닝을 적극 사용하고 있기 때문이다.
명령어 파이프라이닝을 적용하기 위해 가장 중요한 조건은 그림 13.1에 보인 것처럼 각 단계별 소요 시간이 동일해야 한다는 것이다. 대학교의 경우에도 각 학년 별 소요 기간은 1년으로 동일하기 때문에 중첩을 할 수 있다.
파이프라이닝 적용을 위해 프로세서는 다음과 같이 설계되고 있다.
그림 13.1에 보인 것처럼 명령어 파이프라이닝에서는 한 명령의 IF (Instruction Fetch) 와 다른 명령의 MEM (Memory Access) 가 중첩하여 일어난다. 즉 메모리에 있는 명령을 읽어오는 동작과 메모리의 데이터를 접근하는 일이 동시에 일어나는 것이다. 하나의 메모리에서 두 군데 내용을 동시에 읽는다는 의미다.
폰 노이만 구조 컴퓨터와 같이 단일 메모리만 있는 구조라면 이런 이중 접근은 구조적으로 불가능하다. 메모리는 한 순간에 한 내용만 접근할 수 있기 때문이다.
이런 구조적 위험성을 해결하려면 하버드 구조처럼 명령 메모리와 데이터 메모리를 분리해야 한다. 또 다른 방법은 하나의 메모리만 사용하되 뒤에서 배울 내용처럼 instruction cache memory 와 data cache memory 를 함께 사용하여 해결할 수도 있다.
명령어 파이프라이닝에서는 여러 개의 명령들이 중첩해서 동시에 실행된다. 그러나 뒤의 명령이 앞의 명령이 만들어내는 데이터를 필요로 하는 경우라면 동시 실행이 어려울 수도 있다. 예를 들어 메모리에 다음과 같은 명령이 순서대로 들어있다고 가정하자.
add r0, r1, r2 // r0 ← r1 + r2
sub r4, r0, r3 // r4 ← r0 + r3
레지스터 r0 내용은 그 위의 add 명령에서 결정되는데, 명령어 파이프라이닝을 적용하면 add 명령이 채 끝나기도 전에 sub 명령이 실행되므로 sub 명령 실행 당시 아직 r0 에는 유효한 값이 존재하지 않을 수도 있다. 즉 앞의 명령이 완전히 끝나서 유효한 데이터가 얻어진 다음에 뒤의 명령을 실행할 수 있다면 이 두 명령을 중첩해서 동시에 실행하는 것은 불가능하다.
이런 데이터 위험성을 해결하기 위해서는 파이프라이닝의 단계를 조정하거나 하드웨어를 개선하여 앞의 명령어가 완전히 끝나기 전에 결과 데이터를 뒤의 명령어 실행으로 넘길 수 있도록 해야 한다. 이것을 포워딩 forwarding 또는 바이패스 bypassing 이라고부른다. 많은 연구자들이 다양한 포워딩 방법을 개발하여 제안하고 있다.
컴퓨터 프로그램에서는 조건 분기가 자주 사용된다. 즉 조건이 맞으면 분기하고 그렇지 않으면 분기하지 않는 것이다. 그러나 명령어 파이프라이닝을 적용하면 조건 분기를 실행할 시점에 아직 조건이 맞는지 여부가 결정되지 않을 수 있다.
cmp r0,r1 // r0, r1 의 내용을 비교하여
beq L1 // 같으면 분기; 아니면 무시
명령어 파이프라이닝이 적용되면 cmp 와 beq 명령이 중첩되어 동시에 실행되므로 beq 명령 실행 당시 아직 cmp 명령이 채 끝나지 않을 수 있다. 즉 cmp 결과가 아직 나오지 않았기 때문에 beq 명령은 분기 여부를 정할 수가 없는 것이다.
극단적인 방법은 stall on branch, 즉 분기 여부가 결정될 때까지 계속 기다리는 것이다. 이렇게 하면 파이프라이닝 실행이 일시적으로 중단되어 성능 저하를 가져올 수밖에 없다.
또 다른 방법은 위의 예제에서 cmp 명령과 beq 명령 사이에 컴파일러가 여러 개의 nop (no operation) 명령을 의도적으로 넣어주는 것이다. nop 명령은 이름 뜻대로 아무 역할도 하지 않는 명령으로 단지 시간 지연만을 시킬 목적으로 사용된다. cmp 실행 후 몇 개의 nop 명령을 실행하면 그 사이에 cmp 결과가 나오게 되고 이후 beq 는 그 결과에 따라 분기 여부를 결정할 수 있다. 이 방법은 파이프라이닝 자체는 중단되지 않지만 여러 개의 nop 실행으로 인해 결국 성능 저하를 일으킬 수 있다.
세 번째 방법은 branch prediction, 즉 분기 여부를 예측하여 예측 결과에 따라 분기가 실행되거나 또는 넘어가도록 하는 것이다. 운 좋게 예측이 맞았다면 좋은 일이고, 그렇지 않다면 나중에 다시 원래 위치로 되돌아와서 (rollback) 다시 올바른 실행을 하는것이다. 예측이 정확할수록 이 방법의 성능은 좋아지는 것이고, 그렇지 않다면 성능이 오히려 떨어진다. 많은 연구자들이 다양한 분기 예측 방법을 개발하여 제안하고 있다.
Micro-programmed control 방식으로 CU 가 구현됨에 따라 CU 설계에 따른 복잡도가 낮아지게 되었고, 그래서 설계자들은 더 많은 종류의 명령어들과 더욱 다양한 어드레싱 모드를 프로세서에 넣을 수 있게 되었다. 이와 같이 micro-programmed control 방식의 CU 를 사용하고 다양한 명령어를 지원하는 구조의 컴퓨터를 CISC (ComplexInstruction Set Computer) 라고 부른다.
장점
단점
RISC (Reduced Instruction Set Computer) 는 그 이름에서 알 수 있듯이 지원하는 명령어 개수가 줄어 있다. 즉 작은 수의 명령어들만 지원한다. 명령어 개수가 작은 이유는 micro-programmed control 방식이 아니라 hardwired control 방식의 CU 를 사용하고 있기 때문이다. 이 방식은 빠른 속도의 프로세서를 만드는데 적합하지만 설계상의 복잡도 때문에 많은 명령을 지원할 수는 없다.
장점
단점
RISC 컴퓨터에서는 메모리의 변수를 오퍼랜드로 사용할 수 없으므로 대신 레지스터를 더 많이 사용한다.