본문 바로가기
CS/컴퓨터 구조

[Chapter 4.11 컴퓨터 구조 및 설계] Instruction-Level Parallelism과 Dynamic Multiple Issue, Static Multiple Issue란?

by 베어 그릴스 2022. 7. 24.
320x100
본 정리는 CS422-컴퓨터 구조 및 설계 : 하드웨어/소프트웨어 인터페이스. David A. Patterson, 존 헤네시 책을 바탕으로 하고 있음을 미리 알립니다.

 

Instruction-Level Parallelism (ILP)


파이프라이닝은 명령어들 사이의 병렬성을 이용한다. 

이 같은 병렬성을 명령어 수준 병렬성 (Instruction-Level Parallelism) 이라고 한다.

 

이러한 명령어 수준 병렬성을 증가시키는 두가지 기본적인 방법이 있다.

 

 

1. 파이프라인의 깊이를 증가시킨다.

 

즉, 각 stage를 세분화 시키는 것이다.

파이프라인의 CPI가 1인 것은 일단 고정시키고, 대신 1 clock cycle이 걸리는 시간을 더 조금 걸리게끔 만드는 것이다.

 

기존의 5stage를 6stage로 나누고 각 단계들이 같은 길이를 갖도록 하면, clock cycle time은 5/6배 될 것이고 CPI는 그대로일 것이다.

 

 

2. 다중 내보내기(Multiple issue)

 

컴퓨터 내부의 구성 요소들을 여러 벌 갖도록 하여 매 파이프라인 단계에서 다수의 명령어를 내보낼 수 있도록 하는 것이다.

 

지금까지는 세탁기 1대 건조기 1대 등 이렇게 1대씩만 있었다면, 세탁기 건조기 3대씩 있다고 생각하고, 명령어를 동시에 3개 처리할 수 있게끔 되는 것이다.

 

즉, 이렇게 하면 clock cycle마다 처리할 수 있는 명령어가 1개 이상이 되므로, CPI(한 명령어를 처리하는데 걸리는 cycle)는 1 이하가 된다. 이렇게 CPI가 1보다 작아지면 CPI의 역수인 IPC(instructions per clock cycle)를 사용하기도 한다.

 

최근의 프로세서들은 IPC를 3~6 정도로 가지려고 노력한다.

 

ex) 4GHz 4-way multiple-issue에서는 한번에 4개의 명령어를 처리하므로 IPC가 4이다.

 

 

다중 내보내기 프로세서를 구현하는 두 가지 주요 방법은 정적 다중 내보내기(Static multiple issue)동적 다중 내보내기(Dynamic multiple issue) 두가지로 나뉜다.

 

정적 다중 내보내기 - 컴파일러가 같이 내보내질 명령어를 묶어서 내보낸다. 컴파일러가 문제를 회피하고 탐지해낸다 (컴파일러가 하므로 소프트웨어적인 해결 방법이다.)

 

동적 다중 내보내기 - CPU가 매 사이클마다 함께 보낼 명령어들을 고르고 흐름을 조정한다. 컴파일러가 명령어의 순서를 재조정하여 도와줄 수 있다.(CPU가 해결하므로 하드웨어적인 해결 방법이다.)

 

 

 

추정


더 많은 ILP를 사용할 수록 성능이 증가하기 때문에 이를 찾는 방법은 매우 중요하다.

 

이 방법 중하나가 바로 추정이다.

 

추정이란 컴파일러나 프로세서가 명령어의 특성에 대해 추측하도록 허락하여 이 명령어에 종속적일 수 있는 다른 명령어의 특성에 대해 추측하도록 허락하여 이 명령어에 종속적일 수 있는 다른 명령어들의 실행을 시작할 수 있게 하는 방법이다.

 

ex) 분기 명령어의 결과를 추정한다면 분기 명령어 뒤의 명령어들이 일찍 실행되게끔 재조정한다.

 

마찬가지로 컴파일러가 추정하는 것이 정적인 방법이고 프로세스가 추정하는 것이 동적인 방법이다.

추정 후 결과가 틀렸을 때 이를 롤백하는 방법을 갖고 있어야한다.

  • Compiler Speculation - 추정 실패 시 사용할 오류 수정 루틴을 갖고 있다.
  • Hardware Speculation - 실행될 명령어들을 미리 알 수 있다. 버퍼에 결과들을 실제로 필요할 때까지 저장해두고 만약 추정이 실패하면 버퍼의 값을 다 지워야한다.

 

추정하기 전엔 없었던 예외가 추정에 의해 바뀐 명령어에서 생길 수도 있다.

 

컴파일러 기반 즉 정적인 추정에선 이를 위해 예외가 정말 일어난다는 것이 확실해질 때 까지 무시할 수 있게 해주는 특별 추정 지원을 추가하고,

 

프로세서 기반 즉 동적인 추정에서는 예외를 버퍼링 해두었다가, 예외를 일으킨 명령어가 추정 상태에서 벗어나 실행을 완료할 준비가 되면 이때 예외를 일으키고 정상적인 예외 처리를 진행한다.

 

 

Static Multiple Issue


정적 다중 내보내기 프로세서는 컴파일러가 명령어들을 묶고 해저드를 처리하는 일을 도와주도록 사용한다.

 

이렇게 묶여져서 같은 cycle에 처리되는 명령어의 묶음을 내보내기 패킷(issue packets)이라 한다.

 

이러한 내보내기 패킷을 여러개의 연산자를 갖는 큰 명령어 하나로 생각하기도 한다.

 

이러한 관점에서 내보내기 패킷의 원래의 명칭은 VLIW(Very Long Instruction Word)이었다.

 

 

정적 다중 내보내기 프로세서에서는 컴파일러가 내보내기 패킷의 순서를 재조정하고, 패킷 내부에 서로 의존성이 없게끔 한다.

하나의 사이클에  ALU/branch instruction와 load/store instruction이 동시에 실행되는 예시이다.

원래는 32bit 씩 정렬되었지만 이제는 64bit씩 정렬되고 있다.

 

여기에 우리가 봐왔던 문제(harzard)나 따로 스케줄링할 필요가 있을때 nop 명령어를 삽입하기도 한다.

 

 

 

add $t0, $s0, $s1
load $s2, 0($t0)

위 두 명령어를 하나의 패킷으로 묶는다고 가정해보자.

 

원래 같으면 Forwarding으로 문제를 해결했겠지만 하나의 패킷에서 동시에 실행되기 때문에 불가능하다.

 

즉, 이렇게 패킷이 묶을 수 없고 그냥 순차적으로 스케줄링 한다면, nop명령어가 대신 들어와 성능에 하락이 있을 것이다.

 

다른 예시도 살펴보자.

Loop: lw $t0, 0($s1) # $t0=array element
	addu $t0, $t0, $s2 # add scalar in $s2
	sw $t0, 0($s1) # store result
	addi $s1, $s1,–4 # decrement pointer
	bne $s1, $zero, Loop # goto Loop if $s1!=0

이를 내보내면,

다음과 같이 병렬성을 사용하지 않을 때 보다 그닥 성능의 향상이 없는 것을 알 수 있다.

 

 

이러한 순환문에서 더 좋은 성능을 얻기 위한 한 가지 중요한 컴파일러 기법은 순환문 펼치기(Loop Unrolling)이다.

Loop Unrolling

순환문을 nop 등의 지연 없이 스케줄링하기 위해서는 순환문 본체를 네 벌 만들 필요가 있다.

 

순환문 펼치기를 하고 불필요한 순환문 오버헤드 명령어를 없애면 순환문은 4개의 lw,add,sw와 한개의 addi(인덱스 증가),bne(한번의 분기)를 갖고 있다.

 

위에 한번의 순환문에서는 필요 없었던, t1 t2 t3 레지스터들이 보일 것이다.

 

이런 과정을 레지스터 재명명이라 하는데 진정한 데이터 종속성은 아니지만 잠재적 해저드의 원인이 되거나 컴파일러가 코드를 유연하게 스케줄링 하는 것을 방해하는 종속성을 없애기 위하여 사용한다.

 

t0 레지스터만 사용하여도 실제론 lw 다음에 바로 addu 하면 되고 그 후에 sw가 나오면 되기ㅐ때문에 문제가 있진 않지만 순전히 하나의 이름을 계속 사용하기 때문에 레지스터 재명명이 강요된다.

 

결국 이렇게 순환문 몸체를 여러 벌 만들어 펼쳐 서로 다른 반복에 속하는 명령어들을 중첩시켜 가용한 ILP가 많아지게 한다.

 

Dynamic Multiple Issue


동적 다중 내보내기 프로세서는 수퍼스칼라 프로세서라고도 한다. 

 

CPU가 (성능을 위해 컴파일러의 명령어 스케줄링을 받든 안받든) 명령어를 순차대로 내보내고 주어진 클럭 사이클에 몇개의 명령어를 내보낼지를 결정한다.

 

정적 다중 내보내기 프로세서와 가장 큰 차이점은 명령어가 어떻게 스케줄링 되었든 하드웨어가 오류 및 문제가 안나도록 보장한다는 것이다.

 

대부분의 수퍼스칼라 프로세서는 동적 내보내기를 결정하는 기본 틀을 확장하여 동적 파이프라인 스케줄링을 포함하도록 한다.

 

동적 파이프라인 스케줄링은 해저드와 지연은 피하면서 주어진 클럭 사이클에 어떤 명령어를 실행할 것인지 선택한다.

 

lw $t0, 20($s2)
addu $t1, $t0, $t2
sub $s4, $s4, $t3
slti $t5, $s4, 20

 

여기서 sub 명령어는 실행될 준비가 되어 있지만 이 명령어들은 lw와 addu가 먼저 끝날 때까지 기다려야한다.

 

동적 파이프라인 스케줄링에서는 이러한 해저드를 완전히 혹은 부분적으로 피할 수 있다.

 

첫 번째 유닛은 명령어를 순차적으로 가져오고 해독하고 각각의 명령어를 실행단계의 해당 기능 유닛에 보낸다.

 

각각 해독된 명령어는 대기영역에 들어가서 내 피연산자가 모두 준비될 때까지 기다리고, 실행할 기능 유닛이 준비가 되면 결과가 계산되는데 이 계산은 비동기적으로 순서 상관 없이 진행된다.

 

이후 이 계산 결과들이 모여 commit unit에 모이는데 이때는 다시 순차적으로 결과가 작성된다.

728x90