컴퓨터 구조 운영체제 네트워크 자료구조 알고리즘 데이터베이스 내 학습
arch · 06

메모리 계층 구조

구조캐시메모리

메모리 계층 구조(Memory Hierarchy)는 빠를수록 용량이 작고 비싼 경향이 있는 저장 장치들을 계층으로 배치한 구조입니다. CPU에 가까울수록 대체로 빠르고 비싸며 용량이 작고, 멀어질수록 대체로 느리지만 저렴하고 용량이 큽니다.

계층 1

레지스터 (Register)

CPU 내부에 위치한 가장 빠른 저장 공간입니다. 접근 시간 1ns 미만, 용량은 수십~수백 바이트 수준입니다.

계층 2

L1 / L2 / L3 캐시

CPU와 RAM 사이의 고속 버퍼입니다. 대략 L1은 코어당 수십 KB, L2는 수백 KB~수 MB, L3는 수 MB~수십 MB 규모이며, 접근 시간은 L1이 약 1ns, L3가 십수~수십 ns입니다(세대와 제품마다 다릅니다).

계층 3

메인 메모리 (RAM)

프로그램 실행 시 데이터를 올려두는 주기억장치입니다. 수 GB~수십 GB 용량, 접근 시간 대략 60~100ns(제품마다 다름)입니다.

계층 4

보조 기억 장치 (SSD/HDD)

영구 저장 장치입니다. SSD는 수십~수백 μs, HDD는 수 ms 접근 시간이며 수백 GB~수 TB 용량을 제공합니다.

핵심 원리

지역성 원리

시간 지역성(최근 사용 데이터 재사용)과 공간 지역성(인접 주소 데이터 접근)을 이용해 상위 계층 적중률을 높입니다.

핵심 원리

계층 간 데이터 이동

상위 계층에 데이터가 없으면(Miss) 하위 계층에서 블록 단위로 가져옵니다. 히트율이 높을수록 전체 성능이 향상됩니다.

속도 vs 용량 트레이드오프 — 레지스터는 RAM보다 접근 시간이 수십~수백 배 짧지만 용량은 1억 배 안팎으로 작습니다(범용 레지스터 기준). 계층 구조는 이 간극을 효율적으로 메웁니다.
평균 접근 시간 — 히트율 h, 캐시 접근 시간 Tc, 메모리 접근 시간 Tm일 때: 평균 접근 시간 = h×Tc + (1-h)×Tm 로 계산합니다. 미스일 때 캐시를 확인하는 시간은 따로 더하지 않은 단순화 모델이며, 교재의 표준형은 히트 시간 + 미스율 × 미스 페널티입니다.
캐시 일관성 (Cache Coherence) — 멀티코어 환경에서 각 코어의 L1 캐시가 같은 메모리 주소에 대해 다른 값을 가지면 안 됩니다. MESI 프로토콜 등으로 해결합니다.
가상 메모리 — 프로세스마다 독립된 주소 공간을 주고, 물리 메모리가 부족하면 일부 페이지를 디스크에 내려 두는 기법입니다. 접근한 페이지가 RAM에 없으면 페이지 폴트가 나고, 그 페이지가 디스크에 내려가 있었다면 운영체제가 디스크에서 불러옵니다(처음 쓰는 메모리는 디스크를 읽지 않고 빈 페이지를 할당하기도 합니다).
비용 구조 — SRAM(캐시)은 비트당 트랜지스터가 더 많아(보통 6개, DRAM은 트랜지스터 1개와 커패시터 1개) 같은 용량에서 면적과 비용이 훨씬 큽니다. 이 때문에 캐시는 소용량으로만 사용됩니다.

계층이 생긴 이유

CPU 속도는 DRAM 접근 속도보다 훨씬 빠르게 좋아져 왔습니다. 1995년 Wulf와 McKee는 이 격차가 계속 벌어지면 성능을 정하는 것이 CPU가 아니라 메모리 접근 속도가 될 것이라며 '메모리 벽(memory wall)'이라는 말을 쓴 글을 발표했습니다. 빠른 저장 장치는 작고 비싸고 느린 장치는 크고 싸기 때문에 한 종류만으로는 속도, 용량, 가격을 함께 만족할 수 없습니다. 그래서 작고 빠른 저장소를 CPU 가까이에 두고, 프로그램이 같은 데이터나 가까운 데이터를 다시 쓴다는 지역성을 이용해 대부분의 접근을 위쪽 계층에서 끝내는 구조가 표준이 되었습니다.

평균 접근 시간을 숫자로 보면

위 공식에 설명용 값을 넣어 보겠습니다. 캐시 접근 시간을 1ns, 메모리 접근 시간을 100ns라고 가정합니다.

히트율 99% — 0.99 × 1 + 0.01 × 100 = 1.99ns
히트율 95% — 0.95 × 1 + 0.05 × 100 = 5.95ns
히트율 90% — 0.90 × 1 + 0.10 × 100 = 10.9ns
읽는 법 — 히트율이 99%에서 90%로 9%p 떨어졌을 뿐인데 평균 접근 시간은 약 5.5배가 됩니다. 미스 한 번의 비용이 히트보다 훨씬 크기 때문에, 히트율 1%p가 성능에 크게 영향을 줍니다.

자주 하는 오해

L1 캐시를 크게 만들수록 좋다 — 캐시가 클수록 원하는 데이터를 찾는 데 시간이 더 걸리고 차지하는 면적과 전력도 늘어납니다. 그래서 L1은 코어마다 수십 KB로 작게 유지해 빠르게 만들고, 조금 느려도 되는 L2와 L3를 더 크게 둡니다.
캐시도 프로그래머가 직접 관리한다 — 캐시는 하드웨어가 자동으로 관리하고, 레지스터에 무엇을 넣을지는 보통 컴파일러가 정합니다. 프로그래머가 영향을 줄 수 있는 것은 데이터에 접근하는 순서와 데이터를 배치하는 방식입니다.
SSD가 빨라졌으니 RAM은 필요 없다 — SSD 접근 시간은 수십~수백 μs이고 DRAM은 60~100ns 수준이라 대략 수백 배 이상 차이가 날 수 있습니다. 또 DRAM은 바이트 단위로 접근하지만 SSD는 페이지 단위로 읽고 씁니다.
메모리 계층은 하드웨어 이야기라서 코드와는 무관하다 — 같은 계산도 접근 순서에 따라 속도가 크게 달라집니다. 아래 면접 질문의 2차원 배열 순회가 대표적인 예입니다.

면접에서 이렇게 물어요

면접 질문

지역성은 캐시와 어떻게 연결되나요?

시간 지역성은 최근에 쓴 데이터를 곧 다시 쓴다는 성질이라 캐시에 남겨 두면 이득이 됩니다. 공간 지역성은 가까운 주소를 이어서 쓴다는 성질이라, 캐시는 미스가 나면 한 바이트가 아니라 캐시 라인(x86에서는 보통 64바이트) 단위로 가져옵니다.

면접 질문

2차원 배열을 행 순서와 열 순서로 순회하면 속도가 왜 다른가요?

C에서 2차원 배열은 행 단위로 메모리에 이어서 저장됩니다. Java의 2차원 배열은 배열의 배열이라 행마다 별도의 배열이고, 한 행 안의 원소는 기본형 배열이면 보통(JVM 구현상) 연속해서 놓입니다. 행 순서로 순회하면 한 캐시 라인에 든 원소를 연달아 쓰지만, 행이 충분히 길고 배열이 캐시보다 크면 열 순서로 순회할 때 원소마다 다른 라인을 가져와서 미스가 늘어납니다. 결과는 같지만 시간은 다릅니다.

면접 질문

레지스터와 캐시는 무엇이 다른가요?

레지스터는 명령어가 이름으로 직접 지정하는 CPU 내부 저장소입니다. 캐시는 메모리 주소를 기준으로 하드웨어가 투명하게 관리하는 메모리의 사본이라, 프로그램이 캐시를 이름으로 지정하지 않습니다.

면접 질문

평균 메모리 접근 시간을 줄이려면 어떻게 하나요?

히트율을 높이거나(지역성 좋은 코드, 캐시 크기와 구성), 미스 때의 비용을 줄이거나(다단계 캐시, 프리페치), 히트 시간을 줄입니다(작고 단순한 L1). 세 요소가 서로 맞물려 있어서 하나를 개선하면 다른 하나가 나빠지기도 합니다.

실제 시스템에서는

lscpu — L1d(데이터), L1i(명령어), L2, L3 캐시 크기를 보여줍니다. 기본 출력은 전체 코어를 합산한 값이고, 캐시 하나의 크기는 lscpu -C의 ONE-SIZE 열에서 볼 수 있습니다.
리눅스 sysfs — /sys/devices/system/cpu/cpu0/cache/index0/, index1/ 같은 하위 디렉터리 아래의 level, type, size, coherency_line_size 파일로 캐시 계층과 캐시 라인 크기를 읽을 수 있습니다.
L3 공유 — 많은 CPU는 L1과 L2를 코어마다 두고 L3는 여러 코어가 함께 씁니다. 같은 L3를 함께 쓰는 코어는 sysfs의 shared_cpu_list로 확인할 수 있습니다.
RAM은 디스크의 캐시 — 리눅스는 디스크에서 읽은 파일 내용을 메모리에 보관(페이지 캐시)해 두고 캐시에 남아 있는 동안은 같은 파일을 다시 읽어도 디스크에 가지 않습니다. free 명령의 buff/cache 항목에는 이 페이지 캐시가 포함됩니다.

함께 보면 좋은 토픽

캐시 메모리 — 캐시 라인, 매핑 방식, 교체 정책이 어떻게 동작하는지 봅니다.
가상 메모리 — RAM과 디스크 사이에서 주소를 변환하고 페이지를 옮기는 과정을 봅니다.
폰 노이만 구조 — CPU와 메모리 사이의 통로가 병목이 되는 이유를 봅니다.
메모리 관리 — 운영체제가 물리 메모리를 프로세스에 나눠 주는 방식을 봅니다.
NUMA 아키텍처 — 소켓마다 메모리가 붙은 서버에서 로컬 메모리와 원격 메모리의 접근 시간이 다른 이유를 봅니다.

참고 자료

메모리 벽 — Wulf, McKee, "Hitting the Memory Wall: Implications of the Obvious", ACM SIGARCH Computer Architecture News 23(1), 1995
교재 — Hennessy, Patterson, "Computer Architecture: A Quantitative Approach" 6판 부록 B "Review of Memory Hierarchy"

C — 메모리 계층별 접근 시간 시뮬레이션

// 메모리 계층별 접근 시간 (나노초 단위)
#define T_REGISTER  1      // 레지스터:  ~1 ns
#define T_L1        4      // L1 캐시:    4 ns
#define T_L2        12     // L2 캐시:   12 ns
#define T_L3        40     // L3 캐시:   40 ns
#define T_RAM       100    // RAM:      100 ns
#define T_SSD       100000 // SSD:  100,000 ns (100 μs)

// 히트율 기반 평균 접근 시간 계산 (표준 계층적 공식)
// AMAT = T_L1 + MissRate_L1 × (T_L2 + MissRate_L2 × (T_L3 + MissRate_L3 × T_RAM))
// L2 히트 시에도 L1 조회(미스) 비용을 함께 지불해야 하므로 누적 합산한다.
double calc_amat(double h_l1, double h_l2, double h_l3) {
    // L3 미스 시 페널티: L3 접근 + RAM 접근
    double miss_l3_penalty = T_L3 + (1.0 - h_l3) * T_RAM;

    // L2 미스 시 페널티: L2 접근 + L3 탐색 비용
    double miss_l2_penalty = T_L2 + (1.0 - h_l2) * miss_l3_penalty;

    // AMAT: L1 접근 + L1 미스 시 추가 페널티
    double amat = T_L1 + (1.0 - h_l1) * miss_l2_penalty;

    return amat;
}

// 예시: L1=90%, L2=80%, L3=90% 히트율
// AMAT = 4 + 0.1 × (12 + 0.2 × (40 + 0.1 × 100))
//      = 4 + 0.1 × (12 + 0.2 × 50)
//      = 4 + 0.1 × 22 = 6.2 ns

AMAT(Average Memory Access Time)은 계층별 히트율과 접근 시간으로 계산합니다. L1 히트율이 90%일 때 10%의 미스가 하위 계층으로 전파되므로 전체 계층의 히트율을 높이는 것이 성능 향상의 핵심입니다.

Java — 메모리 계층 접근 패턴 시뮬레이터

// 메모리 계층 시뮬레이터 — 지역성 원리 확인
public class MemoryHierarchySim {

    private static final int L1_SIZE  = 128;   // 128 KB
    private static final int L2_SIZE  = 1024;  // 1 MB
    private static final int L3_SIZE  = 8192;  // 8 MB

    private long l1Hits, l2Hits, l3Hits, ramHits;
    private long totalAccess;

    /** 주소 접근 시뮬레이션 */
    public long access(int address) {
        totalAccess++;

        // 간단한 직접 매핑 시뮬레이션
        if (inCache(address, L1_SIZE)) {
            l1Hits++;
            return 4;   // L1 접근: 4ns
        }
        if (inCache(address, L2_SIZE)) {
            l2Hits++;
            return 12;  // L2 접근: 12ns
        }
        if (inCache(address, L3_SIZE)) {
            l3Hits++;
            return 40;  // L3 접근: 40ns
        }

        ramHits++;
        return 100;   // RAM 접근: 100ns
    }

    private boolean inCache(int addr, int size) {
        return (addr % (size * 1024)) < (size * 512);
    }

    /** 히트율 및 평균 접근 시간 출력 */
    public void printStats() {
        double amat = (l1Hits*4.0 + l2Hits*12.0 +
                       l3Hits*40.0 + ramHits*100.0) / totalAccess;
        System.out.printf("L1 히트율: %.1f%%%n", l1Hits*100.0/totalAccess);
        System.out.printf("AMAT: %.2f ns%n", amat);
    }
}

시간 지역성이 높은 워크로드(같은 주소 반복 접근)는 L1 히트율이 높아 AMAT가 4ns에 근접합니다. 반면 순차 대용량 배열 접근(공간 지역성만 존재)은 캐시 미스가 많아 AMAT가 RAM 접근 시간인 100ns에 가까워집니다.

Q1

메모리 계층 구조에서 레지스터에 가까울수록 일반적으로 어떤 특징을 가지나요?

레지스터→캐시→메모리→SSD로 갈수록 반대 경향이 나타납니다.
메모리 계층은 레지스터에 가까울수록 빠르지만 용량이 작고 비싸며, SSD/HDD에 가까울수록 느리지만 용량이 크고 저렴합니다.

Q2

메모리 계층 구조를 두는 근본적인 이유는?

빠른 저장장치는 비싸고 용량이 작습니다.
빠르지만 비싸고 작은 저장장치(레지스터, 캐시)와 느리지만 싸고 큰 저장장치(SSD, HDD)를 계층적으로 배치해 성능과 비용의 균형을 맞춥니다.

Q3

지역성(locality)의 원리가 메모리 계층에서 중요한 이유는?

시간적 지역성, 공간적 지역성을 떠올려보세요.
최근에 접근한 데이터(시간적 지역성)나 그 근처 데이터(공간적 지역성)는 다시 쓰일 확률이 높아, 이를 캐시에 올려두면 적중률이 높아집니다.