캐시메모리
대부분 프로그램은
한 번 사용한 데이터를 다시 사용할 가능성이 높고 (시간적 지역성)
그 주변의 데이터도 곧 사용할 가능성이 높은 (공간적 지역성)
데이터 지역성을 가지고 있다.
데이터 지역성을 활용하여 메인 메모리에 있는 데이터를 캐시 메모리에 불러와 두고 CPU가 필요한 데이터를 캐시에서 먼저 찾도록 하면 시스템 성능을 향상 시킬 수 있다.
시간적 지역성 (Spatial Locality) | 공간적 지역성 (Temporal Locality) |
|
|
반복 루프 서브 루틴 공통 변수 LRU |
배열(Array) Pre-Fetch |
ex)
for (i=0; i<10; i++)
data[i+1] = data[i] + 1;
data[1] = data[0] + 1;
data[2] = data[1] + 1;
[시간적 지역성] data[1]는 다음 순환에서 read |
[공간적 지역성] data[0]부터 data[9] 까지 read |
어떤 프로그램을 동작시키기 위해서는 메모리에 적재가 되고 CPU를 할당받아야 동작이 된다.
이때 CPU와 메모리(RAM) 사이에서 활발하게 데이터를 주고받게 되는데 CPU에 비해 비교적 속도가 느린 메모리에 의해서 제대로 성능을 내지 못한다.
캐시 메모리는 CPU와 RAM 사이에서 발생하는 병목 현상을 완화하고자 사용한다.
CPU에는 이러한 캐시 메모리가 2~3개 정도 사용된다. (L1, L2, L3 캐시 메모리라고 부른다)
속도와 크기에 따라 분류한 것으로, 일반적으로 L1 캐시부터 먼저 사용된다. (CPU에서 가장 빠르게 접근하고, 여기서 데이터를 찾지 못하면 L2로 감)
캐시 메모리에 데이터가 존재하면 메인 메모리에 접근할 필요가 없다. (읽을 때)
캐시 메모리에 데이터가 교체되는 알고리즘은 다음과 같다.
캐시 메모리에서는 페이지 대신 프레임 혹은 메모리 블록
- Page : 가상 메모리를 일정 크기로 나눈 집합으로 보조기억장치부터 쓰임
- Frame : 메인 메모리의 블록 (밑에 기술)
FIFO(First In First Out)
OPT(Optimal)
LRU(Least Recently Used)
그 외에도
LFU - Least Frequently Used : 참조 횟수가 가장 작은 페이지 교체
MFU - Most Frequently used : 참조 횟수가 가장 많은 페이지 교체
NUR - Not Used Recently : 최근에 사용하지 않은 페이지 교체
등 다양하게 있다.
이 때,
CPU가 참조하고자 하는 메모리가 캐시에 존재하고 있을 경우 Cache Hit라고 한다.
CPU가 참조하고자 하는 메모리가 캐시에 존재하지 않을 때 (Page Fault) Cache Miss라고 한다.
Cache Hit Rate = Cache Hit 수 / 기억장치 접근의 총 수
캐시 히트가 일어나면, 캐시에서 데이터를 읽기만 하면된다.
하지만 데이터를 수정할 경우, 캐시 메모리에서만 끝나는게 아니라 메인 메모리에 있는 데이터 도 수정해야 한다.
캐시 히트 시, 쓰기 정책
구분 | Write Through | Write Back |
구성도 | ![]() |
![]() |
개념 | 쓰기 동작 시, 캐시와 메인 메모리 동시에 데이터 변경 | 우선 캐시에만 쓰고, 데이터 swap-out (페이지 교체) 시 메인 메모리에 복사 |
장점 | 구조 단순, 캐시 - 기억장치의 일관성 | 횟수 최소화, 시간 단축 |
단점 | 버스 트래픽 증가, 쓰기 시간 증가 | 캐시 - 기억장치의 일관성 문제 (Cache Coherency) |
이 경우 캐시 메모리 이점이 떨어진다. | Write Back 쓰기 정책을 사용하면 방금 쓴 내용을 다시 변경할 때 디스크에 두 번 쓸 필요가 없고, 변경된 내용을 하나하나 쓰는 대신 모아서 한꺼번에 쓸 수 있다는 장점이 있다. (모아서 하면 더 빠르다.) 대신 캐시에 임시로 저장된 내용을 아직 디스크에 쓰지 않았는데 컴퓨터를 끈다든가 하면 문제가 생긴다. 컴퓨터를 끄기 전에 제대로 종료시켜야 하는 이유 중 하나가 이 때문이다. |
캐시 미스가 일어나면, 메인 메모리에서 데이터를 가져와 캐시메모리에 할당 하거나 할당하지 않는다.
캐시 미스 시, 쓰기 정책
구분 | no-Allocate | Allocate |
구성도 | ![]() |
![]() |
개념 | 쓰기 동작 시, 메인 메모리에만 쓰고 캐시에 할당 하지 않음 | 메인 메모리의 블록을 캐시 메모리에 할당하고 캐시 메모리에 쓰기 |
그런데 Write-back + Write-allocate 방식이 일반적으로 사용된다. |
cache는 보통 block size가 32Byte 또는 64Byte이다. (32비트 64비트 운영체제에 따라 다르다는 얘기도 있다.)
그렇다면 Memory는 1Byte단위로 접근하는데 Cache는 왜 이렇게 큰 단위로 접근할까?
1Memory에서 data 가져오는데 비용이 많이 드니까 한번에 많이 가져오는 것이다.
예를 들면 캐시 미스가 나면 block 중에 하나의 data만 교체하는 것이 아니라 block단위로 전체를 교체해야한다.
주소를 하나주고 1개의 word를 가져오는 것을 4번 반복하는 것보다, 한번에 4개를 가져오는 것이 이득이기 때문이다.
이러한 이유때문에 공간 지역성 (Spatial locality), 캐시에 이미 저장된 같은 블록의 데이터를 접근하게 되므로 캐시의 효율성이 크게 향상된다.
클래스 / 구조체 패딩바이트
이 예시는 캐시메모리 블록 보다 메모리를
32비트 운영체제 - 4바이트씩
64비트 운영체제 - 8바이트씩
한번에 접근하는 이유에 대한 예시이다.
#include <stdio.h>
struct temp {
int a; // 4byte
int b; // 4byte
char c; // 1byte
char d; // 1byte
double e; // 8byte
short f; // 2byte
};
temp 구조체가 사용하는 데이터는 20byte일 것 같지만 실제로는 32바이트이다.
32비트 CPU를 예로 들면 위와 같이 읽게 되는데 패딩 바이트를 사용하지 않으면 위처럼 마지막 1byte가 남게 되어 연산을 두 번 처리하게 된다.
패딩 바이트를 추가하여 한 번의 연산에 하나의 값이 들어가게 되고 메모리는 더 사용하나 CPU는 한 번의 동작만 하면 되기에 연산이 보다 빠르게 처리된다.
이처럼 공간낭비일 수 있는 패딩공간을 확보하면서 메모리의 크기를 맞추는 이유는 캐시 히트율을 높이고 CPU연산을 줄이기 위함이다.
alignof - 메모리 줄 맞춤 바이트 크기 단위 반환
클래스나 구조체의 한 블록 (패킹 값) 은 해당 함수로 확인 가능
class A {
int a;
int b;
char c;
};
class B {
int* a;
int b;
int* c;
};
class C {
char a;
char b;
};
cout << alignof(A); cout << " "; cout << sizeof(A) << endl; // 4 12
cout << alignof(B); cout << " "; cout << sizeof(B) << endl; // 8 24
cout << alignof(C); cout << " "; cout << sizeof(C) << endl; // 1 2
보통은 가장 큰 바이트를 가진 타입 기준으로 정렬된다.