279. 분리 캐시 (Split Cache)

핵심 인사이트 (3줄 요약)

  1. 본질: 분리 캐시 (Split Cache) 구조는 CPU에 가장 가까운 L1 캐시를 **명령어 전용(Instruction Cache, I-Cache)**과 **데이터 전용(Data Cache, D-Cache)**으로 물리적으로 쪼개어 독립적으로 운영하는 고성능 아키텍처 기법이다.
  2. 가치: 파이프라인의 명령어 인출(IF) 단계와 데이터 메모리 접근(MEM) 단계가 동일한 클럭 사이클에 동시에 캐시에 접근하려 할 때 발생하는 치명적인 **구조적 해저드 (Structural Hazard)**를 원천적으로 차단한다.
  3. 융합: 이는 메모리 공간을 분리하는 하버드 아키텍처 사상을 L1 캐시 레벨에 구현한 것이며, 코어와 멀어질수록 용량의 유연성이 중요해지므로 L2와 L3 캐시에서는 다시 하나로 합쳐진 통합 캐시 (Unified Cache) 형태로 융합되어 사용된다.

Ⅰ. 개요 및 필요성

  • 개념: Split Cache는 하나의 거대한 덩어리였던 1차 캐시(L1 Cache) 메모리를 반으로 갈라, 한쪽은 프로그램의 '코드'만 저장하고, 다른 한쪽은 프로그램이 다루는 '값'만 저장하도록 분리한 구조다. 두 캐시는 각각 독립적인 컨트롤러와 전용 버스 포트를 가진다.

  • 필요성: 폰 노이만 아키텍처 기반의 5단계 파이프라인에서는 필연적으로 메모리 충돌이 일어난다. 첫 번째 명령어가 4번째 단계인 MEM(메모리 읽기/쓰기)을 수행할 때, 뒤따라오는 네 번째 명령어는 1번째 단계인 IF(명령어 인출)를 수행해야 한다. 만약 캐시가 1개뿐이라면 두 명령어가 하나의 캐시 출입구를 두고 병목을 일으켜 파이프라인이 멈춰야 한다. 이를 막기 위해 입구를 아예 2개로 분리한 전용 캐시 구조가 필요했다.

  • 💡 비유: 도서관 출입문이 하나뿐인데, 책을 빌리러 가는 사람(명령어 인출)과 책을 반납하러 가는 사람(데이터 쓰기)이 동시에 몰리면 문에 끼어 옴짝달싹 못 하게 됩니다. 그래서 아예 '대출 전용관'과 '반납 전용관' 건물을 따로 지어 각자의 문으로 다니게 만든 것과 같습니다.

  • 등장 배경: 초기 CPU 파이프라인 설계자들은 이 문제를 해결하기 위해 캐시의 대역폭을 2배로 늘리거나 듀얼 포트 SRAM을 사용해 보려 했다. 하지만 듀얼 포트 SRAM은 단일 포트보다 면적이 훨씬 넓고 발열이 심했다. 대신, 명령어와 데이터는 접근 패턴이 완전히 다르다는 점에 착안하여, L1 캐시를 물리적으로 분리하는 것이 훨씬 더 효율적이라는 결론에 도달했다.

┌─────────────────────────────────────────────────────────────┐
│          Unified Cache vs Split Cache의 파이프라인 충돌 비교     │
├─────────────────────────────────────────────────────────────┤
...

- **📢 섹션 요약 비유**: 톨게이트를 '하이패스 전용(명령어)'과 '현금 전용(데이터)'으로 나누면 차들이 엉키지 않고 쌩쌩 달릴 수 있는 것과 같습니다. 통합 캐시는 모든 차가 섞여서 들어가야 하는 1차선 병목 톨게이트입니다.

---

## Ⅱ. 아키텍처 및 핵심 원리

### I-Cache와 D-Cache의 비대칭적 설계 (Asymmetric Design)

명령어와 데이터는 메모리를 읽고 쓰는 방식이 완전히 다르다. 따라서 분리된 두 캐시는 각자의 역할에 맞게 고도로 특화되어 설계된다.

| 설계 요소 | I-Cache (명령어 캐시) | D-Cache (데이터 캐시) | 특화 이유 |
|:---|:---|:---|:---|
| **쓰기 연산** | 거의 발생하지 않음 (Read-only) | 매우 빈번하게 발생 (Read/Write) | 명령어는 실행 중 자신을 수정하지 않음. |
| **접근 패턴** | 극단적인 순차적 패턴 | 무작위 또는 Stride 패턴 | 코드는 위에서 아래로 흐름. 데이터는 사방으로 튐. |
| **프리페칭 강도**| 매우 공격적 (Aggressive) | 방어적/선택적 (Conservative) | 명령어는 다음 줄을 예측하기 매우 쉬움. |
| **더티 비트 유무**| 필요 없음 (항상 Clean) | 필수적 (Write-Back 지원) | 명령어가 변경될 일이 없으므로 메모리에 쓸 필요도 없음. |

### L1 레벨에서의 하버드 아키텍처 구현

컴퓨터의 큰 틀은 메모리가 하나인 폰 노이만 구조를 따르지만, L1 캐시 내부만큼은 완벽한 **하버드 아키텍처 (Harvard Architecture)**를 모방한다. CPU 코어 내부의 로직은 메모리에 접근할 때 "지금 읽으려는 것이 실행할 코드인가, 아니면 계산할 숫자인가?"에 따라 완전히 다른 파이프를 타고 L1을 방문한다. 그러나 이 두 파이프라인은 L2 캐시 컨트롤러로 내려갈 때 다시 하나로 합쳐진다(Unified).

```text
┌─────────────────────────────────────────────────────────────┐
│          현대 마이크로아키텍처의 비대칭 메모리 계층 구조          │
├─────────────────────────────────────────────────────────────┤
...

- **📢 섹션 요약 비유**: 회사에서 '우편물 수발실(I-Cache)'과 '택배 수발실(D-Cache)'을 따로 두면, 종이 뭉치를 찾는 직원과 무거운 박스를 찾는 직원이 서로 부딪히지 않아 쾌적합니다.

---

## Ⅲ. 비교 및 연결

### Split Cache의 약점: 용량 할당의 경직성 (Rigidity)

모든 것이 완벽해 보지만, Split Cache는 용량을 동적으로 조절할 수 없다는 치명적인 경제적 단점을 가진다.
- **예시 1**: 과학 연산 프로그램은 코드는 짧지만 데이터가 거대하다. 이때 I-Cache는 텅텅 비어 노는데 D-Cache만 모자라 미스가 폭발한다.
- **예시 2**: 복잡한 제어 로직 프로그램은 데이터는 적지만 코드가 방대하다. 이때 D-Cache는 노는데 I-Cache만 부족해 명령어 미스가 폭발한다.
통합 캐시였다면 전체 용량을 유동적으로 나누어 썼겠지만, 물리적으로 분리된 Split Cache는 "내 땅이 남아도 네게 빌려줄 수 없는" 경직성 때문에 전체 적중률이 미세하게 낮아질 수 있다.

### 현대 표준: 하이브리드 캐시 아키텍처
이러한 딜레마를 극복하기 위해 현대 프로세서는 최전선(L1)만 Split Cache로 만들고, 용량의 유연성이 필요한 후방(L2, L3)은 Unified Cache로 만드는 **하이브리드 계층 구조**를 전 세계적 표준으로 확립했다.

| 캐시 계층 | 채택 구조 | 주요 목적 |
|:---:|:---:|:---|
| **L1 Cache** | **Split Cache** | 구조적 해저드 방지, 클럭당 처리량(IPC) 극대화 |
| **L2 Cache** | Unified Cache | L1 미스 방어, 명령어/데이터 간 동적 용량 할당 |
| **L3 Cache** | Unified Cache | 멀티 코어 간 자원 공유, 방대한 LLC 확보 |

- **📢 섹션 요약 비유**: 집에서 '아빠 화장실'과 '엄마 화장실'을 완전히 나누면 아침 출근 시간에 절대 안 싸워서 좋지만(속도 최고), 아빠가 출장 갔을 때 엄마가 친구들을 데려와도 아빠 화장실은 텅 빈 채로 놀려야 하는 비효율(용량 부족)이 발생합니다.

---

## Ⅳ. 실무 적용 및 기술사 판단

### 실무 시나리오

1. **I-Cache 스래싱 방어를 위한 코드 최적화**
   게임 엔진 개발자가 수많은 가상 함수를 사용해 객체 지향 구조를 짰다. CPU의 L1 I-Cache는 32KB인데, 매 프레임마다 호출되는 코드의 양이 100KB에 달한다. I-Cache가 꽉 차서 계속 코드가 쫓겨나는 '명령어 미스'가 발생한다. 이는 D-Cache 미스보다 훨씬 치명적으로, 파이프라인 전체를 멈춰 세운다. 아키텍트는 흩어진 객체 로직을 한 곳으로 모으는 '인라인' 함수 최적화나, 자주 실행되는 핫 경로의 코드를 메모리에 연속적으로 배치하도록 링커 설정을 조작하여 I-Cache 친화적 코드를 작성해야 한다.

2. **보안 취약점 공격 (Self-Modifying Code) 방어**
   악성코드가 자신이 실행 중인 메모리 영역의 값을 해킹하여 다른 명령어로 조작하려 한다. 과거 통합 캐시 시절에는 코드가 즉시 수정되어 실행되었다. 하지만 Split Cache 구조에서는, 악성코드가 D-Cache를 통해 메모리 값을 바꿔도, 이미 I-Cache에 옛날 코드가 로드되어 있으면 바뀐 코드가 실행되지 않고 CPU가 꼬이게 된다. 현대 OS는 이를 차단하기 위해 **W^X (Write XOR Execute)** 원칙을 메모리 페이지 단위로 강제한다.

### 안티패턴
- **인라인 함수의 무분별한 남용**: C++에서 함수 호출 오버헤드를 줄이겠다고 모든 함수에 `inline`을 떡칠하면, 바이너리 코드의 크기가 기하급수적으로 팽창한다. 이는 32KB밖에 안 되는 L1 I-Cache의 용량을 초과하게 만들어, I-Cache Miss를 폭발시키고 결국 인라인을 안 하느니만 못한 끔찍한 성능 저하를 초래한다.

- **📢 섹션 요약 비유**: 수발실(I-Cache)이 좁은데, 매뉴얼을 전부 펼쳐놓고 일하려 하면 책상이 좁아서 계속 문서를 서고에 넣었다 뺐다 해야 합니다. 요약본만 책상에 올려두는 센스가 중요합니다.

---

## Ⅴ. 기대효과 및 결론

### 정량적 기대효과
- **파이프라인 지연 감소**: Split Cache 적용만으로 5단계 파이프라인에서 메모리 스테이지와 인출 스테이지의 충돌이 100% 제거되어 시스템 스루풋이 이론상 20~30% 수직 상승한다.
- **캐시 대역폭 2배 확보**: 동일한 클럭 사이클 내에 I-Cache와 D-Cache에서 각각 데이터를 1개씩 동시에 인출할 수 있으므로, L1 캐시의 유효 대역폭이 획기적으로 향상된다.

### 결론
명령어 캐시와 데이터 캐시의 분리(Split Cache)는, 현대 파이프라인 프로세서가 "멈추지 않고 달리기 위해" 취할 수밖에 없었던 필연적인 진화적 선택이다. 비록 용량 할당의 경직성이라는 페널티를 안게 되었지만, 이를 하위 계층 캐시의 용량 유연성으로 보완함으로써 성능과 경제성의 황금 밸런스를 달성한 컴퓨터 아키텍처의 마스터피스다.

- **📢 섹션 요약 비유**: 결국 훌륭한 시스템은 "가장 앞쪽(L1)에서는 질서를 위해 길을 두 개로 엄격하게 가르고(Split), 뒤쪽 창고(L2)에서는 효율을 위해 유연하게 섞어 쓰는(Unified)" 유연함에서 나옵니다.

---

### 📌 관련 개념 맵

| 개념 명칭 | 관계 및 시너지 설명 |
|:---|:---|
| **하버드 아키텍처** | 명령어 버스와 데이터 버스를 물리적으로 분리하는 설계 사상으로, Split Cache의 이론적 모태. |
| **구조적 해저드** | CPU 파이프라인에서 두 개 이상의 명령어가 동시에 같은 하드웨어 자원을 요구할 때 발생하는 충돌. |
| **명령어 인출 (IF)** | L1 I-Cache가 파이프라인의 1단계에서 전적으로 담당하는 핵심 읽기 동작. |
| **통합 캐시** | Split Cache의 용량 비효율을 해결하기 위해 L2, L3 캐시 계층에서 채택하는 범용 저장소. |
| **폰 노이만 병목** | CPU와 메모리 사이의 속도 차이로 인한 지연으로, Split Cache는 이 병목을 풀기 위한 대역폭 확장 기술. |

---

### 👶 어린이를 위한 3줄 비유 설명
1. 햄버거 가게에 줄을 서는데, 옛날에는 '주문하는 줄'과 '햄버거 받는 줄'이 하나로 섞여 있어서 너무 복잡하고 싸움이 났어요.
2. 그래서 천재 사장님이 가게 문을 아예 2개로 뚫어서, 왼쪽 문은 '주문 전용', 오른쪽 문은 '받는 전용'으로 나눴답니다.
3. 문을 2개로 나눴더니 손님들이 1초도 안 멈추고 쌩쌩 들어가고 나올 수 있어서 햄버거 가게가 엄청나게 돈을 많이 벌게 되었어요!