명령어 파이프라인: 고성능 프로세서의 시작 / 명령어 수준의 병렬성 1

Jin Hur·2021년 8월 20일
0
post-thumbnail

reference: "프로그래머가 몰랐던 멀티코어 CPU 이야기" / 김민장, "Computer System A Programmers'Perspective" / 랜달 E.브라이언트

명령어 파이프라인은 캐시와 더불어 현대 고성능 프로세서를 가능케 한 일등 공신. 명령어 처리율을 극대화하여 프로세서의 성능을 크게 향샹시켰다.

파이프라인 기본 개념

마이크로프로세서(microprocessor)의 성능이 비약적으로 높아지는데 가장 핵심적인 기술로 파이프라인이 있다. 파이프라인은 현대 프로세서의 기반 기술로 처리율(throughput)을 크게 높이는데 공헌하였다.

명령어 파이프라인 외에도 곱셈과 같은 시간이 오래 걸리는 산술 연산 장치와 어느 정도 시간이 걸리는 주요 작업은 대부분 파이프라인화(pipelining)되어 있다.

파이프라인은 연속으로 주어지는 어떤 작업을 처리하는 데 있어 처리율(throughput)을 높이는 일반적인 알고리즘을 가리킨다. 파이프라인 알고리즘의 핵심은 재사용성과 병렬 실행에 있다. 그리고 이를 통해 처리율, 단위 시간당 처리할 수 있는 일의 양을 크게 높일 수 있다.

파이프라인으로 얻는 이상적인 처리율 증가(speed up)는 파이프라인 단계 수 만큼 된다. 파이프라인은 각각 다른 일을 처리하는 자원들이 동시에 여러 작업을 처리함으로써 처리율을 높인다. 그러나 이 파이프라인으로 레이턴시(latency)는 개선하지 못한다.(되려 악화될 수 있음. 파이프라인이 시간 당 처리 명령어의 갯수를 높일 수 있지만, 하나의 처리에 대한 속도 향상을 가져다주지는 못한다는 뜻.)


파이프라인의 효율적 설계

파이프라인 단계가 k일때 이상적인 k배의 처리율을 얻으려면 다음 네 가지가 만족되어야함.
1. 균등한 파이프라인 단계: 파이프라인의 각 단계는 균등한 길이로 나뉜다.
2. 같은 작업: 파이프라인은 항상 같은 작업만 수행한다. (각 단계는 같은 작업을 한다.)
3. 독립적인 작업: 파이프라인에 투입되는 작업은 서로 의존 관계가 없다.
4. 파이프라인 유지 비용의 최소화: 최적의 파이프라인 길이를 찾는다.

파이프라인의 효율은 파이프라인 단계 중 가장 느린 단계가 결정. 각 단계가 동일한 시간이 걸린다면 거의 k배에 가깝게 처리율이 높아질 것. 만약 단계별 작업 시간이 다르다면 짧게 끝나는 작업 후 스톨(pipeline stall)이 발생하게되어 효율이 떨어진다. 따라서 파이프라인은 최대한 작업 단계가 비슷한 시간이 되게 설계하는 것이 가장 중요하다. 하지만 현실적으로 이런 조건은 만족하기 어렵다. 결국 긴 파이프라인 단계와 짧은 파이프라인 단계가 혼재되고, 전체 파이프라인의 효율은 가장 긴 파이프라인 단계로 결정된다.

두 번째로 이상적인 파이프라인 조건은 처리하는 작업이 모두 동등해야 하다는 것이다. 동등하지 않은 특정 작업들이 껴있다면 stall이 발생하고 만다.

세 번쨰로는 파이프라인의 처리하는 작업의 독립성이다. 작업 간의 의존성이 생기면 또한 stall이 발생하기 마련이다. 어떤 작업이 끝날 때 까지 stall이 발생하기 때문이다. 작업 간의 의존성 문제는 실제 명령어 파이프라인에서 처리해야 할 가장 중요한 문제 중 하나이다.

마지막으론 파이프라인 자체의 비용을 생각해야한다. 파이프라인의 단계가 많아지면 이상적인 처리율 상슥폭이 증가하지만 과도한 비용으로 이런 이득이 상쇄될 수 있다. 이 중 대표적인 것이 바로 레이턴시 측면에서 손해를 보는 것이다. 하나의 단계로만 구성된 작업을 쭉 수행하면 중간에 하나의 단계에서 다른 하나의 단계로 넘어갈 때의 레이턴시가 발생하지 않는다. 반면 극단적으로 100단계로 나눈다치면 레이턴시 측면에선 매우 비효율적일 것이다. 처리율 측면에서의 이득을 상쇄시키지 않게 이러한 단계 수도 잘 책정해야할 것이다.

보통 파이프라인이 k단계로 나뉘어 있을 떄 이 값을 파이프라인 깊이(pipeline depth)로 표현한다. 파이프라인이 어느 정도 깊으면 처리율이 높아지지만 너무 깊은 파이프라인은 피해야 한다. 파이프라인은 채우는데 또 비우는데 시간이 걸리기 떄문. 파이프라이 최대 효율을 내려면 모든 단계가 바삐 돌아가야만 한다.


파이프라인 프로세서의 구현

캐시는 대표적인 명령어 완료 레이턴시 개선 방법이다. 명령어 처리율을 높일 수 있는 근본 원리는 최대한 많은 일을 병렬로 처리함에 있다.

명령어 수행 병렬성을 높이려면 하드웨어 제공이 제공되어야 한다. 최근의 멀티코어가 좋은 예이다. 여러 스레드에 있는 명령어는 서로 독립적이므로 병렬 실행 가능하다. 멀티코어는 일부 캐시를 제외하고 모든 프로세서 자원을 복제하므로 병렬 처리에 이상적이다.
=> 쓰레드 수준 병렬성
그러나 과거에는 반도체 기술 수준이 지금과 같지 않으므로 멀티코어 제작이 불가능했다. 이런 관점에서 파이프라인은 하드웨어 복제를 조금하면서 명령어 처리율을 높이는 데 사용된 최초의 병렬 기술이다(단일 코어에서의).
=> 명령어 수준 병렬성

최상일 때 파이프라인 단계만큼의 작업되어 비록 다른 세부 단계에 있지만 병렬로 처리된다. 프로세서는 여러 장치에 파이프라인을 적용하여 처리율을 극대화한다. 명령어 파이프라인이 대표적이다. (특별한 말이 없으면 파이프라인은 명령어 파이프라인을 가리킴.)

최초의 파이프라인 프로세서는 1960년대 초반 IBM의 7030 컴퓨터였다. 그리고 1980년대에 RISC 프로세서의 주요 구현 방식으로 자리잡았다. x86 CISC 구조에서는 인텔 i486에 최초로 1989년에 구현되었다.
(80486 이전의 인텔 프로세서, 80286, 80386도 어느 정도 파이프라인화가 되어 있었다. 어느 부분은 서로 겹쳐서 동시에 처리할 수 있었지만, 완벽한 의미의 파이프라인은 80486에서 비로소 구현되었다.

명령어 파이프라인은 IF, ID, OF, EXE, OS 처럼 나워진 명령어 과정 단계를 파이프라인 개념에 적용하는 것이다.
하나 기억해야 할 제약 조건은 파이프라인의 단계 하나는 한 사이클 내에 완료되어야 한다는 것이다. 사이클마다 파이프라인 단계가 진행된다. 따라서 가장 긴 파이프라인의 단계가 프로세서의 '클록 속도'를 결정 짓는다.

다섯 단계의 명령어 파이프라인이라면 명령어 하나가 완료되는데 걸리는 레이턴시는 다섯 사이클이다. 그리고 명령어 처리율이 이상적인 경우, 사이클마다 명령어가 완료되어 IPC(Instruction Per Cycle), 클록 당 완료 명령어 개수는 1이 된다. 그런데 실제 프로세서의 파이프라인 구성은 사뭇 다르다.

파이프라인의 단편화를 줄이자

이상적인 파이프라인의 첫 번째 조건은 균등한 파이프라인이었고, 두 번째는 같은 작업이 투입된다는 가정이었다. 이 조건은 실제로 만족되기 어렵다. 명령어는 크게 ALU 명령어, 로드, 스토어, 분기문으로 나뉜다. 각 명령어가 요구하는 작업이 다른다. 구현하는 ISA의 특이성과 또 실제 프로세서 구현 제약도 고려해야 한다. 이런 이유로 이 책에서 정의한 다섯 단계, IF/ID/OF/EXE/OS는 실제 구현에서 그대로 지켜지기 어렵다. 실제로는 어떤 파이프라인 단계들을 합치기도, 반대로 나누어 최대한 파이프라인 단계 사이에 균형을 맞춘다. 서로 다른 작업들도 최대한 같은 부분을 찾아 통합함으로써 불균형을 최소화한다.


파이프라인의 내부 단편화: 파이프라인 단계의 길이 차이가 심하므로 나누거나 합쳐셔 균형있게함.

소프트웨어 메모리 할당자가 겪는 내부 단편화(internal fragmentation) 문제와 같다. 위 그림에선 단계 A와 B를 각각 두 단계, 세 단계로 나누고, 반대로 C와 D 단계는 하나로 합쳐 각 단계가 같은 길이가 되게 바꾸었다. 파이프라인도 훨씬 균형있게 되었고 단계가 더 많아짐으로써 기대할 수 있는 이상적인 처리율도 크게 상승한다.

예를들어 RISC 구조에서 명령어 인출(IF)와 명령어 해독(ID)은 간단하므로 합쳐질 수 있다. 실제 MIPS 프로세서 중 R2000/R3000 모델은 다섯 단계의 파이프라인이지만 IF와 ID 단계가 합쳐 있다. 반면 x86 프로세서는 가변 길이의 명령어를 읽어야 하고, 복잡한 명령어 포멧과 마이크로 명령어로 변환까지 해야 하므로 IF와 ID가 해야할 일은 많다. 따라서 이 과정을 여러 단계로 나누어 파이프라인의 균형을 맞추기 쉽게 한다. 따라서 이 과정을 여러 단계로 나누어 파이프라인의 균형을 맞추기 쉽게 한다. 최근의 x86 프로세서는 모두 IF와 ID가 여러 단계로 더 세밀하게 파이프라인화되어 있다. 한편, IF와 ID는 명령어 종류에 상관없이 무조건 해야하는 일이다. 그러나 피연산자 피연산자 인출(OF), 실행(EXE), 결과 저장(OS)은 명령어 종류에 따라 해야 하는 일이 달랐다. 여기서 또 불균형이 야기된다.


파이프라인의 외부 단편화: 명령 2는 총 6단계가 필요하고 명령 4는 3단계가 필요없다. X는 파이프라인 stall

이전에 정의한 피연산자 인출(OF) 단계는 레지스터를 읽거나 메모리를 읽는 작업이 모두 포함되었다. 그러나 이 두 작업은 사실 상당히 다르다. 레지스터는 빠르게 읽을 수 있지만 메모리 읽는 작업은 캐시가 있지만 레지스터 작업에 비하면 많이 느리다. 실제 구현에서는 논리적인 피연산자 인출 단계를 레지스터 파일을 읽는 단계메모리에 접근하는 단계로 구분하는 것이 더 합리적이다. 실제 많은 프로세서는 이를 분리하고 있다. 실행(EXE) 단계에서 간단한 정수 연산이 부동소수점 나눗셈보다 더 빨리 완료됨도 자명하다. 다른 연산을 모두 하나의 EXE 단계로 배치하는 것은 합리적이지 못하다. 긴 시간이 걸리는 부동소수점 처리는 별도의 파이프라인 단계를 가지는 것이 더 효율적이다. 이렇게 서로 다른 명령어로 말미암아 각기 다른 파이프라인 단계가 필요하므로 파이프라인에는 스톨이 발생할 수 있다.(그림 7-4 이상적이지 못한 파이프라인 상태 (좌) 참고)

내부 단편화에 비유했듯이 이 문제는 파일 시스템에서 볼 수 있는 외부 단편화(external fragmentation) 문제에 비유할 수 있다. 위 그림 명령 1, 3은 다섯 단계가 필요하다. 그러나 명령 2는 4번째 단계가 4a와 4b로 하나 더 필요하다. 이때, 명령 3은 파이프라인 stall을 겪을 수 있다. 명령 3이 5번째 단계를 실행하고 싶어도 아직 명령 2가 이에 해당하는 자원을 놓지 않았기 때문이다. 명령 4는 3번째 파이프라인 단계가 필요치 않지만 같은 이유로 파이프라인 stall이 생기고 만다. 이런 자원 부족으로 겪는 현상을 구조 해저드(structural hazards)라고 한다.
정리하자면, 실제 파이프라인은 처리하는 명령어의 종류가 다르고, 구현하는 ISA의 특성과 프로세서 제약 조건으로 이전에 정의한 이상적인 파이프라인 단계의 적용이 어렵다. 내/외부 단편화를 최소화할 수 있게 파이프라인 단계를 결정해야 한다.

최적 파이프라인 깊이 (파이프라인 단계 수 == 파이프라인 깊이)

파이프라인 단계를 정의할 때, 최적 파이프라인의 단계 수, 즉 깊이도 같이 고려해야 한다. 파이프라인 설계에 있어 파이프라인 깊이는 성능에 큰 영향을 미치는 중요한 설계 변수이다. 왜냐하면 파이프라인 깊이가 곧 프로세서의 클록 속도를 결정하기 때문이다. 본 글에서는 이상적으로 다섯 단계 파이프라인 깊이를 예로 들었다. MIPS R2000/3000 프로세서는 실제 다섯 단계의 파이프라인으로 되어 있다. 초기 팬티엄 프로세서도 역시 다섯 단계였다. 그런데 파이프라인의 이상적인 처리율 상승은 파이프라인 깊이에 비례하므로 더 깊은 파이프라인 단계는 매력적인 선택이다. 이와 더불어 깊은 파이프라인은 클록 속도를 높일 수 있다. 파이프라인의 한 단계는 한 사이클 내에 완료되어야 한다. 그런데 파이프라인이 깊어지면, 다시 말해 파이프라인 단계를 더 잘게 나누어 단계 수를 늘리면, 각 단계에서 해야 할 일의 양이 줄어들게 되므로 보다 빠른 클록을 얻을 수 있다.

cf) 파이프라인 단계가 k라고 할 때 이것은 어떤 명령어를 기준으로 말할까? 간단한 정수 연산과 부동 소수점 연산은 필요한 파이프라인 단계가 다르므로 기준이 필요하다. 일반적으로 파이프라인의 단계는 분기문의 결과를 알 수 있는 단계까지로 정의된다. 왜냐하면 분기문의 결과가 밝혀진다는 것은 투기적 실행(speculation)을 구현하는 대부분 프로세서에서 상당히 중요한 일이므로 이것을 기준으로 한다.

현대 프로세서는 보통 10단계 이상의 깊은 파이프라인을 채용한다. IBM의 POWER 5/6 프로세서는 모두 14단계 파이프라인이며 인텔의 펜티엄 프로와 Core 2 Duo 이후의 프로세서도 14단계 정도된다. 예외적으로 펜티엄 4는 30단계까지 파이프라인이 깊어지기도 했다. 그런데 깊은 파이프라인은 문제를 많이 일으킨다. 클록이 올라가는 만큼 성능이 올라가지 않기 때문이다.
연구 결과에 다르면 펜티엄 4 같은 구조에서 깊은 파이프라인을 채용해 클록 속도를 두 배로 올려도 성능 향상은 35~90% 정도 된다. 높아진 클록의 절반만이 실제 성능 향상으로 바뀐 셈이다. 더욱이 파이프라인이 약 50단계가 되면 아무리 클록 속도가 빨라져도 시스템 성능은 하락하게 된다(레이턴시 증가 악영향 때문..?).

0개의 댓글