나노미터 단위로 배관공사하기 [CSAPP Chap 4]

제목이 난해하게 느꼈다면 그럴 수 있다. 하지만 파이프라인을 생각하고 왔다면 당신도 난해한 것이다. 축하한다. 난해의 Depth가 있다는 것은 사람을 이해 할 수 있는 폭이 넓은 것이다. 아무래도 나는 타로 집을 차려서 여러분들이 고른 카드에 따라 프로젝트를 추천해줘야겠다.


파이프라이닝 : CPU의 효율 극대화하기

예시로 언급되는 SEQ 프로세서는 한 명령어의 1~6단계를 모두 끝내야만 다음 명령어를 진행한다. 이것은 비효율적이다.

요즘은 파이프라이닝을 사용한다. 아래와 같은 예시를 보자.

1번 사이클: 1번 명령어 인출(Fetch)
2번 사이클: 2번 명령어 인출 & 1번 명령어 해독(Decode)
3번 사이클: 3번 명령어 인출 & 2번 명령어 해독 & 1번 명령어 실행(Execute)

이렇게 함으로써 이상적으로는 매 클럭 사이클 마다 하나의 명령어가 처리 된다.
SEQ보다 훨씬 높은 처리량을 낼 수 있다. 하지만 이 방식에는 몇 가지 위험이 존재한다.

 

  • Data Hazard : 앞선 명령어가 아직 계산을 끝내지 않았는데, 뒤따라오던 명령어가 그 결과값을 필요로 하는 상황을 말한다.
  • Control Hazard : if문이나 loop같은 분기를 만나, 다음에 어떤 명령어를 파이프라인에 넣어야 할지 바로 결정 할 수 없는 상황을 말한다.

Data Hazard

addq %rax, %rbx   # 1번 명령어: rax와 rbx를 더해서 rbx에 저장
subq %rbx, %rcx   # 2번 명령어: rcx에서 rbx를 뺀다

2번 명령어는 앞의 내용이 필요한데, 파이프라인 구조 상 1번 명령어의 저장 단계가 끝나기 전에는 2번 명령어가 정상 작동하지 못한다.
즉, 1번 명령어의 결과가 반영되지 않은 채로 %rbx를 사용 할 것이다.

  • 해결법 (포워딩이나 바이패싱이나 원리는 같다)
    • 결과가 레지스터에 저장될 때까지 기다리지 말고, 계산이 끝나는 즉시 그 결과를 다음 명령어에 직접 전달한다.
  • 실행(Execute) 단계: addq 명령어가 ALU에서 덧셈을 막 끝냈다. 결과값(새로운 %rbx 값)은 ALU 내부에 잠시 머물러 있다.
  • 포워딩 발생: CPU는 이 값을 ALU 출력에서 바로 빼내어 다음 명령어의 ALU 입력으로 가는 지름길로 즉시 보낸다.
  • 문제 해결: subq 명령어는 기다릴 필요 없이, 이 지름길을 통해 전달받은 값을 사용해서 바로 뺄셈을 시작할 수 있다.

 

Control Hazard

jmp 명령어로 인해 분기점이 생기면, CPU는 점프 여부 결정전까지는 다음 명령어를 뭘 써야할지 결정하지 못한다.

  • 하지만 성능 손실을 최소화 하기위해 분기 예측을 사용한다. 다음 내용을 추측해서 파이프라인을 미리 채우는 것.
  • 실패했다면 미리 넣어둔 작업을 모두 폐기한다 (Pipeline Flush), 그리고 다시 시작한다.

이 앞의 분기 예측이 실패하지 않게끔, 즉 Pipeline Flush가 일어나지 않아야 파이프라인 프로세서의 성능 손실이 줄어드는 것이다.

 

After All + ret

사용 패턴까지 종합하여 분기 예측을 하는 시도도 생각 해 볼 수 있는데,
현대의 CPU에는 동적 분기 예측(Dynamic Branch Prediction) 이라는 이름으로 구현되어있다.

jmp, jle는 목적지 주소가 명령어 자체에 포함되어 있거나, 비교 결과에 따라 둘 중 하나를 예측 할 수 있었다.
ret은 스택 맨 위에서 복귀 주소를 pop하고 그 주소로 점프한다. 이건 좀 어렵다.

문제는 스택은 메모리에 있어서 파이프라인 구조 상 꼬이게 된다.
물론 ret이 제대로 될 때까지 멈추는 방안도 있지만, 그럼 성능 저하를 반드시 발생시킬 것이다.

  • 해결책
    • 반환 주소 예측기(Return Address Predictor)라는 특별한 하드웨어를 사용하기로 했다. 일종의 작은 메모지로써 활약한다.
      • CPU는 내부에 작은 하드웨어 스택을 따로 가지고 있다.
      • call 명령어를 실행 할 때, 복귀 주소를 메인 메모리의 스택에도, 그리고 이 하드웨어 스택에도 함께 저장한다.
      • ret 명령어를 실행하면, 메인 메모리까지 가지 않고, 하드웨어 스택의 맨 위에 있는 주소가 복귀 주소일 것이라고 예측하고 그 곳의 명령어를 즉시 가져온다.

거의 정확한 이 예측은 ret으로 인한 파이프라인의 성능 저하를 획기적으로 줄인다.