폰 노이만 병목과 CPU 캐시
빠른 CPU는 왜 메모리를 기다릴까?
본 블로그 작성 글들은, 강의를 목표로 하고 제작되었습니다. 또한 작성된 글의 정보도 틀렸을 수 있으니, 주의해주세요.
CPU가 아무리 빠르게 계산할 수 있어도, 계산할 명령어와 데이터가 도착하지 않으면 아무것도 할 수 없다. CPU와 메모리 사이의 전송 속도가 전체 성능을 제한하는 현상을 Von Neumann Bottleneck(폰 노이만 병목)이라고 부른다.
CPU는 메모리를 기다린다
폰 노이만 구조에서는 명령어와 데이터가 같은 메모리와 버스를 사용한다.
CPU가 명령어를 요청함
↓
메모리에서 명령어를 가져옴
↓
CPU가 필요한 데이터를 요청함
↓
메모리에서 데이터를 가져옴
↓
CPU가 계산함
CPU의 계산 속도와 비교하면 메인 메모리 접근은 느리다. 게다가 하나의 통로로 명령어와 데이터가 함께 움직이면 서로 차례를 기다리는 상황도 생긴다.
캐시는 이 기다림을 줄인다
현대 CPU는 자주 사용할 명령어와 데이터를 CPU 가까운 Cache Memory(캐시 메모리)에 미리 가져온다. L1 캐시는 작지만 매우 빠르고, L2와 L3로 갈수록 커지는 대신 조금씩 느려진다.
CPU → L1 Cache → L2 Cache → L3 Cache → Main Memory
빠르고 작음 느리고 큼
캐시는 프로그램이 가까운 데이터를 반복해서 사용한다는 Locality(지역성)를 이용한다.
- 시간 지역성: 방금 사용한 값은 곧 다시 사용할 가능성이 높다.
- 공간 지역성: 어떤 주소를 사용하면 그 주변 주소도 사용할 가능성이 높다.
배열을 앞에서부터 차례대로 읽는 코드가 여기저기 뛰어다니며 읽는 코드보다 캐시에 유리한 이유도 공간 지역성 때문이다.
// 연속된 메모리를 순서대로 읽으므로 캐시가 활용되기 쉽다.
for (int i = 0; i < count; ++i)
{
sum += values[i];
}
명령어 캐시와 데이터 캐시
현대 CPU는 메인 메모리에서는 명령어와 데이터를 함께 두면서, L1 캐시에서는 Instruction Cache와 Data Cache를 나누기도 한다. 덕분에 다음 명령어를 가져오는 일과 현재 데이터에 접근하는 일을 어느 정도 동시에 수행할 수 있다.
이것이 수정된 하버드 구조라고 불리는 이유다. 하버드 구조와 폰 노이만 구조의 장점을 필요한 계층에서 섞어 사용한 것이다.
결국 컴퓨터 성능은 CPU의 연산 횟수만으로 결정되지 않는다. 데이터를 얼마나 연속적으로 배치했는지, 캐시 적중률이 어떤지, CPU가 메모리를 얼마나 오래 기다리는지도 같이 봐야 한다.