CPU는 메모리에 있는 데이터를 가져와서 연산해야 한다. 이때 메모리를 가져오는 과정에서 CPU가 쉬게되면 이는 손해다. 따라서 캐시가 도입되었다.
코어마다 L1, L2캐시가 붙어있고 L3캐시를 공유하는것을 확인할 수 있음.
아래는 2차원 배열을 두가지 방식으로 반복해서 읽어, 캐시의 공간적 지역성 효과를 비교하는 예제.
//
#include <iostream>
#include <ctime>
#include <cstddef>
#include <windows.h>
int main()
{
const std::size_t rows = 8000;
const std::size_t cols = 8000;
std::size_t total = rows * cols;
int* a = new int[total];
if (a == NULL)
{
std::cerr << "allocation failed\\n";
return 1;
}
for (std::size_t i = 0; i < total; ++i)
{
a[i] = 1;
}
volatile long long sum = 0;
std::clock_t t0, t1;
int iter = 0;
while (1)
{
++iter;
// 행 우선 접근 (row-major) — 캐시 친화적
t0 = std::clock();
for (std::size_t i = 0; i < rows; ++i)
{
std::size_t base = i * cols;
for (std::size_t j = 0; j < cols; ++j)
{
sum += a[base + j];
}
}
t1 = std::clock();
double row_ms = (double)(t1 - t0) * 1000.0 / CLOCKS_PER_SEC;
// 열 우선 접근 (col-major) — 캐시 비친화적
sum = 0;
t0 = std::clock();
for (std::size_t j = 0; j < cols; ++j)
{
for (std::size_t i = 0; i < rows; ++i)
{
sum += a[i * cols + j];
}
}
t1 = std::clock();
double col_ms = (double)(t1 - t0) * 1000.0 / CLOCKS_PER_SEC;
std::cout << "iter=" << iter
<< " row-major(ms): " << row_ms
<< " col-major(ms): " << col_ms
<< " sum=" << sum << std::endl;
Sleep(1000);
}
delete[] a;
return 0;
}