본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Computer Science

현대 CPU의 마이크로아키텍처

작성 2026. 10. 14.추가
목차9개 항목
출처와 참고

팔만코딩경(42Seoul 커뮤니티 위키)의 「CPU의 대략적 구조」, 「μops와 port (port pressure,DSB,LSD,fusion,AGU)」, 「if 문 결과를 CPU가 예측한다고? (Branch Prediction)」, 「두 배 더 빠른 퀵정렬 구현 방법」, 「캐시 메모리는 프로그래머가 제어하지 않는지 궁금한다.」, 「컴퓨터 동작 과정 한눈에 보기」를 바탕으로 재구성한 노트다. 세부 수치는 Agner Fog의 마이크로아키텍처 자료와 Intel 최적화 매뉴얼을 기준으로 한다.

ISA와 마이크로아키텍처

ISA(명령어 집합 구조, x86-64·ARM64)는 프로그램이 보는 약속이고, 마이크로아키텍처는 그 약속을 실제 회로로 구현한 방식이다. 같은 x86-64 코드도 세대마다 내부 구조가 다르고 속도도 다르다. 교과서의 "인출 → 해독 → 실행 → 메모리 → 쓰기" 5단계 파이프라인(파이프라인과 병렬 처리)은 개념 모델이다. 현대 코어는 그 위에 세 가지를 더한다.

기법뜻
슈퍼스칼라한 사이클에 명령을 여러 개(4~8개) 인출·해독·실행
비순차 실행(out-of-order)프로그램 순서가 아니라 피연산자가 준비된 순서로 실행하고, 결과만 순서대로 확정
투기적 실행(speculative)분기 결과를 예측해 미리 실행하고, 틀리면 버린다

전체 흐름

flowchart LR
  dram["DRAM"] --> l3["L3"] --> l2["L2"] --> l1i["L1-I"]
  subgraph fe["프런트엔드"]
    ft["인출"] --- bp["분기 예측기"]
    ft --> dec["디코더"]
    dsb["μop 캐시(DSB)"] --> dec
    dec --> uq["μop 큐<br/>(LSD)"]
  end
  subgraph be["백엔드"]
    rob["재정렬 버퍼(ROB)<br/>이름 바꾸기(rename)"] --> rs["예약 스테이션<br/>(스케줄러)"]
    subgraph ports[" "]
      p0["포트0: ALU, 곱셈, 분기"]
      p1["포트1: ALU, 곱셈"]
      p5["포트5: ALU, 셔플"]
      p23["포트2·3: 로드 + 주소 계산"]
      p47["포트4·7: 스토어"]
    end
    rs --> p0 & p1 & p5 & p23 & p47
    cm["ROB에서 순서대로 commit"]
  end
  l1i --> ft
  uq --> rob
  ports -->|"실행 완료"| cm

프런트엔드: 명령어를 μop으로

x86 명령어는 길이가 1~15바이트로 들쭉날쭉하고, add [mem], 1처럼 메모리 읽기·연산·쓰기를 한 명령에 담는다. 이대로는 파이프라인을 고르게 채우기 어렵다. 그래서 디코더가 명령어를 고정 형식의 내부 연산 μop(micro-operation) 으로 쪼갠다.

i++   →  load  r0, [i]
         add   r0, 1
         store [i], r0

RISC 계열(ARM)은 명령어 길이가 고정이라 해독이 단순하지만, 내부에서 μop으로 바꾸는 구조는 비슷하다.

장치하는 일
마이크로코드 ROM (Intel은 MSROM)μop이 많이 필요한 복잡한 명령어(문자열 반복 명령 등)는 디코더 대신 여기서 μop 시퀀스를 꺼낸다. 교재의 마이크로프로그램 제어가 남은 부분이다
μop 캐시 (Intel은 DSB)이미 해독한 μop을 명령어 주소로 찾아 재사용. 맞으면 비싼 디코더를 건너뛴다
루프 스트림 감지기 (LSD)μop 큐 안에서 작은 루프를 감지해 같은 μop을 반복 공급. DSB보다 가깝지만 아주 작은 루프만 담는다
마이크로 퓨전로드+연산처럼 자주 붙는 μop 둘을 하나로 묶어 큐 슬롯과 디코더 대역폭을 아낀다. 실행 단계에서는 다시 나뉜다
매크로 퓨전cmp + 조건 분기 같은 인접 명령 둘을 μop 하나로 합친다

퓨전 조건은 세대마다 다르다. 예를 들어 메모리 피연산자와 즉시값을 함께 쓰는 명령은 퓨전되지 않아, 한 줄짜리 명령과 두 줄짜리 명령의 μop 수가 같을 수 있다. 명령어 줄 수가 아니라 μop 수와 포트가 속도를 정한다.

백엔드: 비순차 실행

장치역할
레지스터 이름 바꾸기논리 레지스터(rax)를 훨씬 많은 물리 레지스터에 대응시켜, 같은 레지스터를 재사용해서 생긴 가짜 의존성을 없앤다
재정렬 버퍼 (ROB)실행 중인 μop을 프로그램 순서대로 기록. 실행이 끝나도 앞선 μop이 모두 끝나야 commit(결과 확정)한다
예약 스테이션 (스케줄러)피연산자가 준비된 μop부터 비어 있는 실행 포트로 보낸다
로드·스토어 버퍼메모리 접근을 순서 규칙에 맞게 대기시키고, 쓰기를 모아서 내보낸다

commit이 순서대로인 이유는 정밀한 예외 때문이다. 앞선 명령이 페이지 폴트를 내면 그 뒤 명령의 결과는 없던 일이 되어야 한다. 이렇게 확정 전에 투기적으로 실행된 흔적이 캐시에 남는 것을 이용한 공격이 Spectre·Meltdown(2018)이다. 접근 시간을 재서 어떤 데이터가 캐시에 올라왔는지 알아내는 방식이다.

실행 포트와 포트 압력

예약 스테이션은 μop을 포트를 통해 실행 유닛에 보낸다. 포트마다 붙어 있는 유닛이 다르고, μop 하나는 한 사이클에 포트 하나를 쓴다. 서로 다른 포트를 쓰는 μop은 동시에 실행된다.

  • 특정 연산을 처리하는 포트가 하나뿐이면 그 연산이 몰릴 때 그 포트만 병목이 된다(포트 압력). Skylake에서 SIMD 셔플은 포트 5에서만 실행된다. Intel 최적화 매뉴얼에는 8×8 행렬 전치에서 셔플 일부를 다른 포트가 처리하는 삽입 명령으로 바꿔 크게 빨라진 예가 있다.
  • Haswell~Skylake의 포트 7 주소 계산 유닛(AGU)은 [base] 같은 단순 주소만 계산하고 [base+index]는 못 한다. 인덱스 주소 지정이 많으면 스토어 주소 계산이 포트 2·3과 경쟁한다.
  • 포트 구성은 세대마다 바뀐다(Ice Lake 이후는 로드·스토어 포트가 더 많다). 병목은 추측하지 말고 uops.info 같은 표나 perf, Intel VTune, llvm-mca로 확인한다.

분기 예측

파이프라인은 분기의 결과가 나오기 전에 다음 명령을 이미 인출해야 한다. 기다리면 매 분기마다 파이프라인이 빈다(제어 해저드). 그래서 CPU는 결과를 예측하고 그 방향으로 투기적으로 실행한다.

  • 예측기는 분기마다의 과거 이력과 주변 분기들의 패턴(전역 이력)을 함께 본다. 반복문처럼 한쪽으로 치우친 분기는 거의 다 맞힌다. 현대 예측기의 적중률은 보통 95%를 넘는다.
  • 분기 대상 주소는 BTB(Branch Target Buffer)에 기억한다. 함수 포인터·가상 함수 같은 간접 분기도 예측한다.
  • 틀리면 투기적으로 실행한 μop을 모두 버리고 다시 인출한다. 현대 코어에서 15~20사이클 정도를 잃는다.

예측할 수 없는 분기는 없앤다

정렬되지 않은 데이터를 비교하는 분기처럼 50:50이면 예측이 자주 틀린다. 이때는 분기 대신 조건부 이동(x86 cmov, setcc)이나 비트 연산을 쓴다. cmov는 예측하지 않고 비교 결과를 기다리므로 BTB도 쓰지 않는다. 늘 조금 느리지만 크게 틀릴 일이 없다.

// 분기
if (n < 0) n = -n;
// 분기 없음: 부호 비트로 마스크를 만든다 (n이 음수면 mask = -1)
int mask = n >> (sizeof(int) * CHAR_BIT - 1);
n = (n ^ mask) - mask;
분기 성격선택
한쪽으로 치우침 (오류 검사, 반복문 조건)일반 분기. 예측이 거의 다 맞는다
데이터에 따라 무작위 (정렬·필터의 비교)삼항 연산자·비트 연산으로 분기 제거

C·C++에는 cmov를 직접 쓰는 문법이 없다. 간단한 삼항 연산자는 GCC·Clang·MSVC가 보통 cmov로 컴파일하지만 보장은 아니므로 디스어셈블리로 확인한다. 분기를 없앤 퀵정렬이 무작위 데이터에서 크게 빨라진다는 연구(Edelkamp & Weiß, BlockQuicksort)가 이 원리의 대표적 예다. 반대로 정렬된 데이터에서는 일반 분기가 더 빠를 수 있다.

SMT(하이퍼스레딩)

코어 하나가 논리 스레드 두 개를 동시에 실행하는 기술이다. 한 스레드가 캐시 미스로 멈춘 동안 다른 스레드가 실행 포트를 쓴다.

자원스레드 간 관계
아키텍처 레지스터, 반환 주소 스택스레드마다 따로
로드·스토어 버퍼, ROB반씩 나눠 고정 할당
예약 스테이션, 실행 포트, L1·L2 캐시, TLB경쟁하며 공유

멀티코어 CPU는 코어마다 자기 레지스터 파일과 L1·L2를 가진다. SMT는 실행 유닛이 놀 때 이득이고, 두 스레드가 같은 포트를 두고 다투면 거의 이득이 없다.

정리

  • 현대 CPU는 명령어를 μop으로 쪼개 슈퍼스칼라·비순차·투기적으로 실행하고, 결과만 순서대로 확정한다.
  • 속도는 명령어 줄 수가 아니라 μop 수, 포트 분산, 의존성 사슬, 캐시 적중이 정한다.
  • 분기 예측 실패는 15~20사이클 손실이다. 무작위 분기는 조건부 이동이나 비트 연산으로 없앤다.
  • 마이크로 최적화는 측정 도구로 병목을 확인한 뒤에 한다. 세대마다 답이 바뀐다.

관련 노트: 파이프라인과 병렬 처리, CPU와 명령어 구조, 캐시를 고려한 코드, 컴퓨터 시스템의 기본 구성, 기본 컴퓨터의 구조와 설계

원본 기록7

댓글

GitHub 계정으로 댓글을 남길 수 있어요.