일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- nodejs
- MongoDB
- dfs
- Express
- Bull
- JavaScript
- cookie
- typeORM
- 게임
- AWS
- game
- Dinosaur
- class
- MySQL
- Nest.js
- 정렬
- flask
- 자료구조
- nestjs
- Sequelize
- react
- 공룡게임
- mongoose
- jest
- OCR
- GIT
- Queue
- TypeScript
- Python
- Today
- Total
포시코딩
캐시(Cache) 본문
캐시(Cache)
먼저 CPU의 작업에 대해 간단히 설명하자면
CPU는 클록마다 작업을 수행하기 위해 메모리에서 연산자와 피연산자의 정보를 갖고 온다.
캐시는 CPU의 이런 과정에 대해 성능 향상을 위해 고안된 일종의 지름길이다.
성능 향상 == 처리 속도 향상
캐시는 CPU 안에 있고 메모리는 물리적 위치가 아예 따로 멀리 떨어져 있다. ex) 램
당연히 CPU가 메모리에서 정보를 가져오는 것보다 캐시에서 가져오는게 훨씬 빠르다.
캐시에 찾는게 있다면 cache hit
없다면 cache miss라고 한다.
캐시의 역할을 한마디로 정의하자면
CPU <-> 메모리 사이의 데이터 전송 레이턴시 개선을 위해 사용되는 전략이다.
지역성(Localitry)
메모리에서 자주 가져와야 하는 데이터가 캐싱되어 있다면 CPU의 효율이 좋아지는건 알았다.
그럼 캐시에 어떤 기준으로 저장해야 하는걸까?
많이 찾았던 걸 저장할 수도 있고 비슷한 걸 저장할 수도 있을 것이다.
이러한 캐싱 알고리즘인 지역성에 대해 알아보자
지역성은 두가지로 나뉜다.
- 시간적 지역성: 지금 어떤 데이터를 사용했다면 가까운 미래에 재사용할 가능성이 있다고 믿는 것
- 공간적 지역성: 지금 어떤 데이터를 사용했다면 그와 인접한 데이터도 사용할 가능성이 있다고 믿는 것
코드로 예를들어 보자
const arr = [];
// ... arr에 데이터 잔뜩 넣고
const result = [];
const loopCount = 10; // arr.length는 loopCount보다 크다고 가정한다.
for (let i = 0; i < loopCount; i += 1) {
result.push(arr[i] + arr[i + 1]);
}
이 코드에서 시간적 지역성은 다음과 같다.
arr[0], arr[10]을 제외한 나머지 arr의 원소들은 첫 번째 루프에서는 오른쪽 피연산자로 사용.
두 번째 루프에서는 왼쪽 피연산자로 사용.
result.push(arr[i] + arr[i + 1]); // 에 대해
// i === 0 -> arr[0] + arr[1]
// i === 1 -> arr[1] + arr[2]
// i === 2 -> arr[2] + arr[3]
// ...
// i === 9 -> arr[9] + arr[10]
또한, 공간적 지역성은 다음과 같다.
arr이라는 배열 내의 메모리들이 순차적으로 사용
캐시의 구성
CPU는 다수의 코어를 보유하고 있고 각 코어는 자체 캐시를 보유하고 있다.
하지만 그림상에서 L1, L2, L3 캐시가 전부 크기가 다른 것을 볼 수 있고
L3 캐시는 공유 캐시로 되어있기 까지 하다.
그림에서 각 캐시들이 코어랑 멀면 멀수록 실제로도 멀어진다고 생각하자
그럼 L1이 가장 가깝고, L3가 가장 멀 것이다.
당연하게도 거리가 가까울수록 CPU가 데이터를 가져오는 속도가 빠르고, 멀수록 느려진다.
그래서 L1 캐시는 속도를 챙기는 대신 저장 곤강을 포기하여 빠르게 가지고 오는 것에만 집중했다.
하지만, 가진게 별로 없어서 그만큼 cache miss가 될 확률은 높아질 수 밖에 없다.
반면 L2, L3 캐시는 데이터를 갖고 오는 속도는 L1 캐시에 비해 느리지만 cache miss가 될 확률도 낮아지게 된다.
이렇게 캐시들이 계층으로 나뉜 것을 알게 되었다.
캐시는 존재하는 데이터들의 일부를 CPU가 빠르게 접근하기 위해 필요하다.
라는걸 명심하자.
캐시 코히런스
캐시가 가진 데이터의 원천은 메모리인데, 메모리의 데이터는 불변이 아니다.
언제든지 특정 메모리 주소에 쓰인 값들은 변경이 될 수 있다.
프로그래밍에서 변수의 값이 계속 변하듯
그렇다면 캐싱된 메모리 주소에 있는 데이터가 변경되었을 경우
해당 데이터를 여러 개의 캐시가 동시에 보관하고 있었다면 이 데이터의 일관성 및 무결성은 어떻게 보장될까?
어떤 캐시는 업데이트를 했는데 어떤 캐시는 업데이트가 안 되는 경우와 같은 문제를
캐시 코히런스(일관성) 문제라고 한다.
캐시가 2개 이상이면 무조건 코히런스를 고려해야 하는데,
캐시가 캐시 코히런스 문제에 대해 어떻게 해결하는지 알아보자.
MSI 프로토콜
MSI 프로토콜이란 각각의 캐시 라인들에 상태를 부여하여
각 캐시 라인의 상태에 따라 최신값으로의 동기화 여부를 결정하는 프로토콜이다.
- S(Shared) 상태
- 가장 기본 상태.
동기화가 완료된 직후거나 값에 변함이 없으면 캐시 라인은 해당 상태를 유지한다.
- 가장 기본 상태.
- M(Modified) 상태
- 코어가 캐시 및 메모리의 값을 새롭게 바꾸려고 할 때 캐시 라인의 상태를 S -> M 상태로 바꾼다.
- '수정중'이라는 사실을 다른 코어나 메모리에 천명한다고 생각하면 된다.
- 이런 상태의 캐시 라인이 있으면 메모리는 해당 데이터가 새롭게 본인에게 쓰이기 전까지 다른 코어의 요구를 거부한다.
- I(Invalid) 상태
- 캐시 라인의 상태가 유효하지 않음을 뜻한다.
- 자신을 M 상태를 만들고 새롭게 데이터를 쓰려는 코어가
본인의 캐시 라인을 제외한 다른 캐시 라인들의 상태를 전부 I 상태로 바꾸어 놓는다. - 해석하자면 '이 데이터를 수정중이고 다른 캐시 데이터는 더이상 유효하지 않다.
이 데이터를 읽으려면 캐시에 물어보지 말라'를
M 상태로 만든 코어가 모든 코어에 경고하는 것이라고 볼 수 있다.
MESI 프로토콜
MESI 프로토콜은 MSI 프로토콜보다 버스(메인보드의 bus)의 트래픽 낭비를 줄이기 위해 고안된 프로토콜이다.
캐시 메모리의 일관성을 유지하기 위해 별도의 플래그(flag)를 할당한 후 플래그의 상태를 통해
데이터의 유효성 여부를 판단한다.
Modified(수정) 상태: 데이터가 수정된 상태
Exclusive(배타) 상태: 유일한 복사본이며, 주기억장치의 내용과 동일한 상태
Shared(공유) 상태: 데이터가 두 개 이상의 프로세서 캐시에 적재되어 있는 상태
Invalid(무효) 상태: 데이터가 다른 프로세스에 의해 수정되어 무효화된 상태
캐시가 중요한 이유
캐시는 CS, CPU에서의 중요한 개념인 것 뿐만 아니라
고성능 프로그램을 만들고 싶다면 반드시 고려해야할 개념이다.
백엔드 개발자라면 서버의 메모리 혹은 인-메모리 데이터베이스,
SQL_CACHE와 같은 MySQL 키워드까지 알고 상황에 따라 데이터를 가져오는 레이턴시를
줄이려고 노력하는 디테일이 필요하다는 것을 잊지 말자
'CS (Computer Science)' 카테고리의 다른 글
[JavaScript] 이벤트 루프(Event Loop) (1) | 2023.04.13 |
---|---|
함수 호출 스택(call stack) (0) | 2023.04.08 |
CPU (0) | 2023.01.07 |
Database, MongoDB (0) | 2022.12.13 |
API, RESTful API (0) | 2022.12.01 |