CPU 캐시란 무엇인가?
Source: Dev.to
CPU 캐시 개요
Cache 를 한국어로 표현하면 “임시 저장소” 라고 할 수 있습니다. CPU 에는 L1, L2, L3 캐시가 존재하며
- L1 – 각 코어마다 자동으로 포함 (크기 ≈ 32 KB)
- L2 – 각 코어마다 포함 (크기 ≈ 256 KB)
- L3 – 전체 코어가 공유하는 캐시 (크기 매우 큼)
CPU 가 명령어를 읽을 때 가장 먼저 L1 캐시를 탐색합니다. L1 에 데이터가 있으면 3‑5 사이클 이내에 가져올 수 있어 매우 빠릅니다. L1 에 없으면 L2 (10‑15 사이클) 로, 또 없으면 L3 (50‑100 사이클) 로 계속 찾아갑니다.
Cache‑friendly 코드
C++ 에서 cache‑friendly 한 기본 방법은 row‑major 접근 입니다.
Cache‑unfriendly 코드
for (int col = 0; col < N; ++col) {
for (int row = 0; row < N; ++row) {
sum += matrix[row][col];
}
}
Cache‑efficient (row‑major) 코드
for (int row = 0; row < N; ++row) {
for (int col = 0; col < N; ++col) {
sum += matrix[row][col];
}
}
Row‑major 접근이 왜 cache‑friendly 한가?
2‑차원 배열 예시
int matrix[4][4] = {
{ 1, 2, 3, 4}, // row 0
{ 5, 6, 7, 8}, // row 1
{ 9, 10, 11, 12}, // row 2
{13, 14, 15, 16} // row 3
};
메모리 레이아웃 (주소)
| 요소 | 값 | 주소 |
|---|---|---|
| matrix[0][0] | 1 | 0x1000 |
| matrix[0][1] | 2 | 0x1004 |
| matrix[0][2] | 3 | 0x1008 |
| matrix[0][3] | 4 | 0x100C |
| matrix[1][0] | 5 | 0x1010 |
| matrix[1][1] | 6 | 0x1014 |
| matrix[1][2] | 7 | 0x1018 |
| matrix[1][3] | 8 | 0x101C |
| matrix[2][0] | 9 | 0x1020 |
| matrix[2][1] | 10 | 0x1024 |
| matrix[2][2] | 11 | 0x1028 |
| matrix[2][3] | 12 | 0x102C |
| matrix[3][0] | 13 | 0x1030 |
| matrix[3][1] | 14 | 0x1034 |
| matrix[3][2] | 15 | 0x1038 |
| matrix[3][3] | 16 | 0x103C |
C++ 은 row‑major 순서이기 때문에 같은 행(row) 안에서는 4 bytes (int)씩 증가하면서 연속된 메모리 주소를 사용합니다.
CPU 가 matrix[0][0] 을 처음 찾을 때 해당 주소가 없으면 64 byte 크기의 캐시 라인 하나를 메모리에서 가져옵니다. 그 캐시 라인 안에는 matrix[0][0], matrix[0][1], matrix[0][2] … 와 같이 같은 행의 모든 요소가 들어 있습니다. 따라서 루프 안에서 matrix[0][1], matrix[0][2] 등은 CPU 가 캐시를 통해 4 bytes씩 순차적으로 아주 쉽게 읽을 수 있어 실행 속도가 크게 빨라집니다.