캐시의 등장 배경과 동작 원리

date
Jul 16, 2022
thumbnail
slug
cs-cache
author
status
Published
tags
OS
summary
캐시가 무엇인지, 왜 나왔는지, 동작은 어떻게 이루어지는지에 대하여…
type
Post
updatedAt
Jan 23, 2023 01:31 PM
최근 LRU 캐시를 직접 소프트웨어적으로 구현해보면서, LRU 캐시에 대한 동작은 많이 찾아봤지만, 정작 캐시가 왜 만들어졌는지에 대한 컴퓨터 구조적인 배경, 보통 어떻게 구현되는지 등에 대한 근본적인 물음은 제대로 해결하지 않았다. 따라서 캐시 자체에 대한 학습을 해보려한다.

캐시의 등장 배경

처리 속도가 차이나는 장치간의 상호작용 시, 처리 속도의 차이로 인한 성능 저하(병목 현상)을 완화시켜서 성능을 향상시키기 위함이다.
폰노이만 구조가 등장하기 이전의 컴퓨터들(ex. 애니악)은 스위치를 설치하고 전선을 연결하여 데이터를 전송하고 신호를 처리하는 식으로 프로그래밍을 하였다.
영화 이미테이션 게임, 영국의 수학자 앨런 튜링과 그가 만든 봄브
영화 이미테이션 게임, 영국의 수학자 앨런 튜링과 그가 만든 봄브
매번 계산을 하기 위해선 스위치를 조정하고 전선을 빼고 다른 곳에 연결하고 … 이러한 방식에 비효율성은 느끼게된 폰 노이만 은 메모리에 프로그램과 데이터가 저장되는 프로그램 내장 방식의 컴퓨터 제안하게 된다.

폰노이만 구조

폰노이만 아키텍처
폰노이만 아키텍처
크게 CPU메모리 , I/O장치로 구성되어 있다.
CPU 내부는 ALU , Control Unit , Register가 존재한다. 폰 노이만은 컴퓨터가 스스로 덧셈, 뺄셈, 나눗셈, 곱셈을 포함한 기본적인 수학 연산을 계산할 수 있게 보장해주는 장치가 필요하다고 생각했고, 그 장치가 ALU 이다.
Register는 연산 시 필요한 데이터를 임시로 저장하는 소형 메모리이다.
하지만 CPU에게 1+1을 명령으로 보낸다고 알아듣는 것은 아니다. 이 명령은 해석하여 ALU에게 그에 맞는 신호를 주는 것이 Control unit 이다.CPU 내부의 요소들끼리는 CPU에서만 사용되는 내부 Bus 를 통해 데이터를 교환한다.

Bus

notion image
notion image
Bus는 컴퓨터의 각 컴포넌트를 서로 연결하여 데이터를 전달하기 위한 경로이다. Bus는 위에서 확인한 것 처럼 CPU 내부에서만 사용되는 CPU 내부 버스 , 컴퓨터 시스템을 구성하는 주요 구성요소들을(CPU, Memory, I/O장치)를 연결하는 시스템 버스 로 나뉜다. 또한 시스템 버스는 아래와 같이 구분된다.
  • Data bus : 시스템 구성요소들 간의 실제 데이터를 전달하기 위한 선들의 집합이다. 요소끼리 서로 데이터를 주고 받아야하므로 양방향 통신이다.
  • Address bus: 데이터 버스상에서 전달되는 데이터의 송신자 혹은 수신자를 나타내기 위해 사용된다. CPU에서만 다른 유닛들의 주소를 지정하므로 이는 단방향 통신이다.
  • Control bus : 모든 장치들에 의해 공유될 수 있는 데이터의 주소선에 제어 신호 (read or write). 양방향 통신이다.
    • 왜 Control Bus는 양방향일까? 메모리에서 다른 유닛들에게 제어 신호를 보낸다거나 하는 일은 없을텐데 말이다. 이는 I/O장치에서 발생하여 CPU에 작업을 요청하는 인터럽트 때문이다. 이 Bus를 통해 CPU의 인터럽트 라인으로 신호가 가게되는 것이다.
       
다시 큰 주제로 돌아와서 병목현상은 왜 발생하는가? 결론적으로 말하면 폰노이만 구조에서 각기 다른 유닛들이 공통의 버스로 연결되었기 때문이다.
CPU는 명령어나 데이터를 메모리로부터 가져와 처리한 후, 결과를 다시 메모리에 내보내게 된다. CPU 내부에서 처리를 하는 부분은 CPU 내부 버스를 이용 하여 CPU의 클럭과 동일한 속도로 데이터를 교환하며 처리가 가능하다.
하지만 CPU 외부의 메모리나 다른 장치와 처리한 데이터를 교환할 때는 시스템 버스를 이용 한다. 즉 CPU의 연산이 아무리 빨라져도 데이터를 가져오는데 시간이 걸리면 전체 시스템 속도는 느려지게 된다. 이런 문제를 해결하기 위해 하버드 구조, 메모리 계층 구조 등의 해결책이 등장하게 되었다.

하버드 구조

notion image
이전에는 데이터를 처리하고 내보냄 → 다음에 명령어를 가져옴 → 해석하고 데이터를 가져오라는 명령을 함→ … 이렇게 시스템 버스가 1개이므로 불필요한 대기시간이 생겼다. 하버드 구조는 CPU가 명령어와 데이터를 동시에 가져올 수 있도록 명령용 버스와 데이터 버스 를 물리적으로 분리하여 구현하여 명령을 메모리로부터 읽는 것과 데이터를 메모리로부터 읽는 것이 동시에 가능해진다.

메모리 계층 구조

notion image
캐시는 CPU 칩 안에 들어가는 작고 빠른 메모리다. CPU가 매번 메인 메모리에 접근해 데이터를 받아오면 시간이 오래 걸리기 때문에 캐시에 자주 사용하는 데이터를 담아두고, 해당 데이터가 필요할 때 프로세서가 메인 메모리 대신 캐시에 접근하도록해 처리 속도를 높인다.
모든 데이터를 캐시같은 빠른 메모리에 저장하면 좋겠지만 캐시는 일반 메모리, 하드 디스크에 비해 매우 비싸다. 따라서 컴퓨터는 메모리 계층 구조를 갖는다.
빠른 저장 장치는 용량에 비해 가격이 비싸고, 용량이 넉넉한 저장 장치는 처리 속도가 느리다. 또한 빠른 속도가 필요한 상황도 있지만, 단순히 많은 내용을 천천히 읽고 쓰는 작업도 그만큼 많다. 결론적으로 싸고 성능 좋은 컴퓨터를 구현하는 설계가 메모리 계층 구조이다.

캐시의 종류

notion image
시스템에 장착된 캐시의 용량과 성능이 점점 증가하면서 캐시의 캐시로 사용되는 메모리가 추가되었다.
  • L1 Cache - 내부적으로 데이터 캐시와 명령어 캐시로 나뉜다. (위에서 설명한 하버드 아키텍처를 따른다.) 보통 명령어는 보통 공간 지역성이 높고 데이터는 보통 시간 지역성이 높기 때문에 이 둘을 나누어 더 빠른 접근 시간을 갖게 한다.
  • L2 Cache - L1 캐시가 miss일 경우 다음에 참조하는 찾게되는 캐시이다. L2는 L1보다 용량이 더 큰 대신 더 느린 속도를 갖는다.
  • L3 Cache - 멀티 코어 시스템에서 여러 코어가 공유하는 캐시

캐시의 지역성

자주 사용하는 데이터에 관한 판단은 지역성의 원리를 따르며, 지역성 원리는 시간 지역성(Temporal locality)과 공간 지역성(Spatial locality)으로 구분해서 볼 수 있다.
for(let i=0; i<arr.length; i++){
	console.log(arr[i]);
}
시간 지역성은 최근 접근한 데이터에 다시 접근하는 경향을 말한다. 예를 들면, 인덱스 역할을 하는 변수 i에는 짧은 시간안에 여러 번 접근이 이뤄진다.
공간 지역성은 최근 접근한 데이터의 주변 공간에 다시 접근하는 경향을 말한다. 위 루프의 경우 배열 arr의 각 요소를 참조하면서 가까운 메모리 공간에 연속적으로 접근하고 있다. 배열의 요소들이 메모리 공간에 연속적으로 할당되기 때문이다.
 
이 원리는 프로세스에서도 동일하게 적용된다.
notion image
가상메모리 페이징 기법에서도 한 프로세스 안에도 자주 사용하는 페이지와 그렇지 않은 페이지가 있다. 위 그림은 페이지를 참조한 기록이다. 가로 축은 실행 시간이고, 세로 축은 메모리 주소이다.
가상 메모리 페이징기법에선, TLB 라는 MMU 내에서 사용하는 캐시가 존재한다. 가상 메모리에선 가상 주소 → 실제 물리 주소 로 매핑하는 과정이 필수적인데, 이때 사용하는 페이지 테이블이 메모리에 존재하므로 이에 대한 접근 시간을 줄이기 위해 사용하는게 TLB이다. TLB도 위와같은 캐시의 지역성 원리를 따르게 된다.

캐시의 동작 원리

notion image
캐시는 주소가 키(Key)로 주어지면 해당 공간에 즉시 접근할 수 있다. 즉 해시 테이블과 동일하다.
캐시는 Block단위로 구성된다. 메인 메모리는 Byte단위로 주소를 구분하지만, 캐시는 32Byte 또는 64Byte크기의 블록을 사용한다. Byte단위를 사용하지 않는 이유는 매번 Memory를 access해서 data를 가져오는 것은 비용이 크기때문에 한번 memory에 접근할 때마다 Block단위로 가져오는 것이다. (이는 공간 지역성이랑도 연관이 있다.)
찾고 있는 데이터의 메모리 주소는 먼저 캐시 메모리에서 이용된다. 메모리 주소를 이용하여 찾고 있는 데이터가 캐시 블록에 있는지 여부를 식별하는데 사용한다.
그렇다면 접근하려는 Memory Address가 캐시에 블록 중 어느 블록에 있는지 어떻게 찾는 것인가?
notion image
32비트 주소 체계를 가정하고 설명한다. 0~4bits는 block offset, 5~14bits는 Index bit, 15~31bits는 Tag bit 이다.
  • block offset - 결국 필요한 데이터에 접근하기 위해선 block이 아닌 Byte단위로 접근해야 한다. 이때 필요한 정보가 block offset으로 한 블록 내에서 원하는 Byte에 접근하기 위한 정보이다. (그림들에서는 32Byte를 가정하므로 5비트로 32개의 바이트 위치를 표현이 가능하다.)
  • Index bit - 어느 블록인지 나타내기 위한 필드다. 5~14까지의 비트는 총 10비트이므로 2^10=1024개의 블록을 구분 가능하다.
  • Tag bit - 인덱스의 충돌(0~14bits가 같은데 15~31bits가 다른 0x12345678 과 0x52345678 같은 경우)을 줄이기 위해 주소값의 나머지 비트를 태그로 사용한다. 이 때, valid bit 1비트를 추가한다. 이 비트는 해당 캐시 블록에 올바른 데이터가 저장되어 있다는 것을 의미한다.
notion image
  1. Memory address 의 index bit를 보고 Cache의 어떤 block인지 찾아간다.
  1. Cache의 Tag를 가져와서 Valid bit가 1인지 확인한다.
  1. Valid bit 가 0이면, Tag matching을 하지 않고, Memory에서 data를 가져온다.
  1. Valid bit 가 1이면, Tag matching을 진행한다. 만약 address의 tag bit 와 Cache에 있는 tag bit가 같다면 hit 이다.

캐시 쓰기 정책

데이터를 읽는 동작이 아니라 입력하는 동작이 발생할 때, 데이터를 변경할 주소가 캐싱된 상태라면 메모리의 데이터가 업데이트되는 대신 캐시 블록의 데이터가 업데이트된다.
이때 캐시에서 업데이트된 데이터를 언제 메모리에 쓸 것인가?에 관한 문제가 생긴다. 여기 두 가지 쓰기 정책(Write policies)이 있다.
  • 하나는 Write-through 방식이다. 캐시에 데이터가 작성될 때마다 메모리의 데이터를 업데이트하는 것이다. 이렇게 하면 많은 트래픽이 발생하지만, 메모리와 캐시의 데이터를 항상 동일하게 유지할 수 있다. 하지만 오히려 hit 일 때, cache와 main memory 모두 update해야해서 시간이 더 걸리게 된다
  • 또 다른 방식은 Write-back 방식이다. 이 방식은 블록이 교체될 때만 메모리의 데이터를 업데이트한다. 데이터가 변경됐는지 확인하기 위해 캐시 블록마다 dirty 비트를 추가해야 하며, 데이터가 변경되었다면 1로 바꿔준다. 이후 해당 블록이 교체될 때 dirty 비트가 1이라면 메모리의 데이터를 변경하는 것이다. cache의 크기는 늘어나지만, memory access가 줄어드는 방식이다.

참고