메모리 계층 구조와 컴퓨터 시스템 성능 최적화
컴퓨터 시스템의 성능은 CPU의 연산 능력뿐만 아니라 데이터를 얼마나 효율적으로 저장하고 접근할 수 있는지에 크게 의존한다. 현대 컴퓨터는 이러한 요구 사항을 충족하기 위해 속도, 용량, 비용 측면에서 다양한 특성을 갖는 메모리 장치들을 계층적으로 구성한 메모리 계층 (Memory Hierarchy) 구조를 채택하고 있다. 이 글에서는 메모리 계층의 핵심 개념, 각 계층별 특징, 그리고 메모리 계층이 효율적으로 작동하는 원리인 지역성 (Locality)과 캐싱 (Caching) 메커니즘을 심층적으로 분석하고자 한다.
메모리 계층의 필요성 및 기본 구조
컴퓨터 시스템에서 사용되는 메모리는 크게 주 기억 장치 (Main Memory)와 보조 기억 장치 (Secondary Storage)로 나눌 수 있다. 주 기억 장치는 CPU가 직접 접근하여 데이터를 읽고 쓸 수 있는 고속의 메모리이며, 주로 DRAM (Dynamic Random-Access Memory)으로 구성된다. 반면, 보조 기억 장치는 대용량 데이터를 영구적으로 저장하기 위한 느린 속도의 저장 장치로, HDD (Hard Disk Drive)나 SSD (Solid State Drive) 등이 대표적이다.
CPU의 처리 속도는 지속적으로 향상되어 왔지만, 주 기억 장치와 보조 기억 장치의 접근 속도는 CPU의 속도를 따라가지 못하는 CPU-Memory Gap 현상이 심화되고 있다. 이러한 성능 격차를 해소하기 위해 메모리 계층 구조가 도입되었으며, 이는 빠르고 작은 용량의 상위 계층부터 느리고 큰 용량의 하위 계층까지 여러 단계의 저장 장치들을 조직적으로 연결하여 전체 시스템의 메모리 접근 성능을 최적화하는 것을 목표로 한다.
Memory Hierarchy |
일반적인 메모리 계층은 다음과 같은 단계로 구성된다:
- CPU 레지스터 (CPU Registers): CPU 내부에 위치한 가장 빠르고 용량이 작은 기억 장치로, 현재 실행 중인 명령어와 데이터를 저장한다.
- L1 캐시 (Level 1 Cache): CPU 코어에 내장된 고속의 SRAM (Static RAM) 기반의 캐시로, 가장 빈번하게 사용되는 데이터를 저장하여 CPU의 메모리 접근 시간을 단축시킨다.
- L2 캐시 (Level 2 Cache): L1 캐시보다 용량이 크고 약간 느린 SRAM 기반의 캐시로, L1 캐시의 미스(miss)를 처리하고 더 많은 데이터를 저장하여 메모리 접근 효율성을 높인다. 최근에는 L3 캐시 등 다단계 캐시 구조가 일반적이다.
- 주 기억 장치 (Main Memory): 시스템의 핵심적인 휘발성 (volatile) 메모리로, DRAM으로 구성되어 프로그램 실행에 필요한 데이터와 명령어를 저장한다. 전원이 꺼지면 저장된 정보가 소실된다.
- 보조 기억 장치 (Secondary Storage): 비휘발성 (nonvolatile) 메모리로, 전원이 꺼져도 데이터를 영구적으로 보존한다. HDD와 SSD가 대표적이며, 프로그램 및 데이터를 장기적으로 저장하는 데 사용된다.
- 원격 보조 기억 장치 (Remote Secondary Storage): 네트워크를 통해 접근 가능한 저장 장치 (예: 네트워크 파일 시스템, 웹 서버)를 의미한다.
상위 계층으로 갈수록 속도는 빨라지고 비용은 비싸며 용량은 작아지는 반면, 하위 계층으로 갈수록 속도는 느려지고 비용은 저렴하며 용량은 커지는 특징을 가진다.
메모리 종류 및 작동 원리
RAM (Random-Access Memory)
RAM은 임의의 주소에 동일한 접근 시간으로 데이터를 읽고 쓸 수 있는 휘발성 메모리이다. 주로 칩 형태로 패키징되며, 여러 개의 RAM 칩들이 모여 시스템의 주 기억 장치를 구성한다. RAM은 크게 SRAM과 DRAM으로 나뉜다.
- SRAM (Static RAM): 각 비트를 저장하기 위해 4개 또는 6개의 트랜지스터 회로를 사용한다. 전원이 공급되는 동안에는 저장된 값을 영구적으로 유지하며, 전기적 노이즈나 방사선에 비교적 덜 민감하다. DRAM보다 빠르고 비싸기 때문에 주로 CPU 캐시 메모리에 사용된다.
- DRAM (Dynamic RAM): 각 비트를 저장하기 위해 하나의 트랜지스터와 하나의 캐패시터(Capacitor)로 구성된 셀을 사용한다. 캐패시터의 전하가 시간이 지남에 따라 방전되므로, 10-100ms마다 주기적으로 값을 리프레시 (refresh) 해주어야 한다. SRAM보다 느리고 전기적 외란에 더 민감하지만, 집적도가 높아 저렴한 비용으로 대용량 메모리를 구성할 수 있어 주 기억 장치에 주로 사용된다.
DRAM은 d x w
비트의 총 용량을 가지며, d
개의 슈퍼셀 (supercell)로 구성되고 각 슈퍼셀은 w
비트의 크기를 가진다. 슈퍼셀에 접근하기 위해서는 행 (row) 주소와 열 (column) 주소를 사용하며, RAS (Row Access Strobe) 신호와 CAS (Column Access Strobe) 신호를 통해 각각 행과 열을 선택한다. 데이터를 읽는 과정은 먼저 RAS를 통해 특정 행을 선택하여 행 버퍼로 복사한 후, CAS를 통해 원하는 열 (슈퍼셀)을 선택하여 데이터 선으로 전송하는 방식으로 이루어진다.
Row 2 copied from DRAM array to row buffer |
Supercell (2, 1) copied from buffer to data lines, and eventually back to the CPU |
비휘발성 메모리 (Nonvolatile Memory)
비휘발성 메모리는 전원이 꺼져도 저장된 데이터를 유지하는 메모리이다. 시스템의 펌웨어 (Firmware: BIOS, 디스크/네트워크 카드/그래픽 가속기 컨트롤러 등)나 SSD와 같은 보조 기억 장치에 주로 사용된다.
- ROM (Read-Only Memory): 제조 과정에서 프로그램이 기록되어 읽기만 가능한 메모리이다.
- PROM (Programmable ROM): 한 번만 사용자가 프로그래밍할 수 있는 ROM이다.
- EPROM (Erasable PROM): 자외선 (UV) 또는 X선을 이용하여 전체 내용을 소거하고 다시 프로그래밍할 수 있는 ROM이다.
- EEPROM (Electrically Erasable PROM): 전기적인 신호를 이용하여 특정 바이트 단위로 내용을 소거하고 프로그래밍할 수 있는 ROM이다.
- 플래시 메모리 (Flash Memory): EEPROM과 유사하지만, 섹터 단위 (부분) 소거 기능을 제공한다. 쓰기/소거 횟수에 제한 (약 10만 번)이 있으며, SSD와 같은 고성능 보조 기억 장치에 널리 사용된다.
보조 기억 장치: HDD (Hard Disk Drive)
HDD는 플래터(Platter)라는 자기 기록 매체를 회전시키고, 암 (arm)에 부착된 읽기/쓰기 헤드를 이용하여 데이터를 저장하고 읽는 장치이다.
![]() |
HDD Geometry (Multi-Platter View) |
- 디스크 구조 (Disk Geometry): 디스크는 여러 개의 플래터로 구성되며, 각 플래터의 표면은 동심원 형태의 트랙 (track)으로 나뉜다. 각 트랙은 일정한 크기의 섹터 (sector)들로 구분되며, 섹터 사이에는 갭 (gap)이 존재한다. 플래터 표면의 동일한 트랙들은 실린더 (cylinder)를 형성한다. 연속된 sector 여러 개가 모여 하나의 cluster를 이루는데, OS는 cluster 단위로 파일을 읽기/쓰기를 한다.
- 디스크 용량 (Disk Capacity): 디스크 용량은 (섹터당 바이트 수) x (트랙당 평균 섹터 수) x (표면당 트랙 수) x (플래터당 표면 수) x (디스크당 플래터 수) 로 계산된다.
- 디스크 작동 (Disk Operation): 원하는 데이터에 접근하기 위해서는 읽기/쓰기 헤드를 해당 데이터가 위치한 트랙으로 이동시키는 탐색 (seek) 과정, 플래터가 회전하여 원하는 섹터가 헤드 아래로 올 때까지 기다리는 회전 지연 (rotational latency), 그리고 데이터를 실제로 읽거나 쓰는 데이터 전송 (data transfer) 과정을 거친다. 디스크 접근 시간 (disk access time)은 이 세 가지 시간 요소의 합으로 근사화될 수 있다:
- 평균 탐색 시간 (avg seek): 헤드를 목표 실린더로 이동시키는 데 걸리는 평균 시간 (일반적으로 3-9ms).
- 평균 회전 지연 (avg rotation): 목표 섹터의 첫 번째 비트가 헤드 아래에 도달할 때까지 기다리는 평균 시간 (회전 속도에 반비례, 예: 7200 RPM에서 약 4ms).
\(Tavg rotation = 1/2 \times 1/RPMs \times 60sec/1min \) - 평균 전송 시간 (avg transfer): 목표 섹터의 데이터를 읽거나 쓰는 데 걸리는 시간 (회전 속도 및 트랙당 섹터 수에 반비례, 예: 7200 RPM, 400 섹터/트랙에서 약 0.02ms).
\(Tavg transfer = 1/RPMs \times 1/(avg num of sectors per track) \times 60sec/1min \) - 접근 시간은 주로 탐색 시간과 회전 지연에 달려있다. (물리적인 움직임)
SRAM의 접근 시간은 약 4ns, DRAM은 약 60ns임을 생각하면 디스크의 접근 시간은 상당히 느림을 알 수 있다.
- 논리 디스크 블록 (Logical Disk Blocks): 현대 디스크는 복잡한 물리적 섹터 구조를 추상화하여 일련의 고정 크기 논리 블록으로 표현하며, 디스크 컨트롤러는 논리 블록 주소를 물리적인 (표면, 트랙, 섹터) 주소로 변환한다.
- CPU의 읽기 요청: CPU가 디스크로 부터 특정 데이터를 읽기를 원할 때, 디스크 컨트롤러 포트에 명령(command), 논리 블록 번호(logical block number), 메인 메모리 주소(destination memory address)를 쓰는 것이 디스크 읽기 요청의 시작이다.
"이러한 데이터를 읽어 이 메모리 주소에 저장해달라" 지시하는 느낌. - 디스크 컨트롤러 작동: 디스크 컨트롤러는 요청된 논리 블록 번호를 물리적 디스크 주소(표면, 트랙, 섹터)로 변환하고, 디스크 드라이버에게 디스크 작동(탐색, 회전 지연, 데이터 전송)을 지시하여 저장된 데이터를 자신의 내부 버퍼로 전송하도록 한다.
- 직접 메모리 접근 (DMA) 전송: 디스크 컨트롤러는 DMA(Direct Memory Access)를 사용하여 내부 버퍼의 데이터를 메인 메모리의 지정된 주소로 전송한다. 이 과정은 CPU 개입 없이 이루어지므로 CPU는 데이터 전송 외 다른 작업을 수행할 수 있어 시스템 효율성이 높아진다.
- CPU 인터럽트: DMA 전송 완료 후, 디스크 컨트롤러는 CPU에 인터럽트(interrupt) 신호를 보낸다. 인터럽트는 CPU의 현재 작업을 일시 중단시키고, 인터럽트 처리 루틴(interrupt handler)을 실행시켜 디스크 읽기 작업이 왼료되었음을 알린다.
보조 기억 장치: SSD (Solid State Drive)
SSD는 플래시 메모리를 사용하여 데이터를 저장하는 보조 기억 장치로, HDD와 달리 기계적인 움직이는 부품이 없어 접근 속도가 빠르고 전력 소비가 적으며 소음이 없다.
- SSD 구조: SSD는 여러 개의 블록 (block)으로 구성되며, 각 블록은 여러 개의 페이지 (page)로 나뉜다 (일반적으로 페이지 크기는 512KB ~ 4KB, 블록 크기는 32 ~ 128 페이지). 데이터는 페이지 단위로 읽고 쓰며, 페이지에 데이터를 쓰기 전에 해당 페이지가 속한 블록을 먼저 소거 (erase) 해야 한다. 블록의 소거는 비교적 느린 작업이며 (약 1ms), 블록의 쓰기 수명은 제한적이다 (약 10만 번).
- SSD 작동: 논리 디스크 블록에 대한 읽기 및 쓰기 요청은 플래시 변환 계층 (Flash Translation Layer, FTL)을 통해 관리되며, 이는 웨어 레벨링 (wear leveling)과 같은 기술을 통해 쓰기 수명을 연장하고 성능을 최적화한다.
- SSD 성능 특성: 순차 읽기 및 쓰기 속도는 HDD보다 훨씬 빠르지만, 랜덤 쓰기 속도는 블록 소거 작업으로 인해 상대적으로 느리다. 읽기 및 쓰기 접근 시간은 HDD의 회전 및 탐색 시간 없이 전자적으로 이루어지므로 매우 짧다 (수십 마이크로초).
CPU와 메모리 간의 데이터 전송
CPU와 주 기억 장치 간의 데이터 전송은 버스 (bus)라는 병렬 전선들의 집합을 통해 이루어지며, 주소 (address), 데이터 (data), 제어 (control) 신호들을 전달한다. 버스는 일반적으로 여러 장치(graphic adapters, disk controllers, USB controllers, etc.)들이 공유한다. CPU가 메모리에서 데이터를 읽는 메모리 읽기 트랜잭션 (memory read transaction)은 다음과 같은 단계를 거친다:
- CPU가 읽고자 하는 데이터의 주소 A를 메모리 버스에 놓는다.
Memory Read Transaction (1) - 주 기억 장치는 버스에서 주소 A를 읽고, 해당 주소에 저장된 데이터 x를 검색하여 다시 버스에 놓는다.
Memory Read Transaction (2) - CPU는 버스에서 데이터 x를 읽어 자신의 레지스터로 복사한다.
Memory Read Transaction (3)
지역성 (Locality)
현대 하드웨어 상의 발전이 점점 어려워지는 상황에서 소프트웨어의 중요성이 대두되었다. 여기서 메모리 계층 구조가 효과적으로 작동하는 핵심 원리는 프로그램의 지역성 (locality)이라는 속성에 기반한다. 지역성은 프로그램이 최근에 접근한 데이터나 명령어, 또는 그 주변의 데이터나 명령어를 다시 접근할 가능성이 높다는 원리이다. 지역성은 크게 두 가지 형태로 나눌 수 있다:
- 시간적 지역성 (Temporal Locality): 최근에 참조된 항목은 가까운 미래에 다시 참조될 가능성이 높다는 것이다. 예를 들어, 루프 내에서 반복적으로 사용되는 변수나 명령어는 시간적 지역성이 높다고 할 수 있다.
- 공간적 지역성 (Spatial Locality): 주소가 가까운 항목들은 시간적으로 가까운 시점에 함께 참조되는 경향이 있다는 것이다. 예를 들어, 배열의 원소들을 순차적으로 접근하는 경우 인접한 메모리 위치들이 함께 사용되므로 공간적 지역성이 높다.
다음 C 코드 예시는 지역성을 보여준다:
int sum_array_rows(int a[M][N]) {
int i, j, sum = 0;
for (i = 0; i < M; i++)
for (j = 0; j < N; j++)
sum += a[i][j]; // 배열 a의 원소에 대한 공간적 지역성, 변수 sum에 대한 시간적 지역성
return sum;
}
- 배열
a
의 원소들은 (C언어 기준) 행 우선 순서로 메모리에 저장되어 있으므로,a[i][j]
와 같이 순차적으로 접근하면 공간적 지역성이 높다. - 변수
sum
은 각 반복마다 반복적으로 갱신되므로 시간적 지역성이 높다. for
루프의 명령어들은 순차적으로 실행되고 반복되므로 명령어 참조에 대한 시간적 및 공간적 지역성이 모두 높다.
프로그래머는 코드 작성 시 지역성을 고려하여 메모리 접근 패턴을 최적화하는 것이 중요하다. 예를 들어, 2차원 배열을 처리할 때 행 우선 (row-major) 순서로 접근하는 것이 열 우선 (column-major) 순서로 접근하는 것보다 공간적 지역성을 활용하여 성능 향상에 유리할 수 있다 (C언어 기준).
캐싱 (Caching)
캐시 (cache)는 하위 계층 (느리고 큰 용량의 저장 장치)의 데이터 일부를 복사해 놓는 더 빠르고 작은 용량의 저장 장치이다. 메모리 계층의 각 단계에서 캐시는 바로 아래 계층의 데이터를 저장하는 역할을 수행하며, 지역성을 활용하여 자주 사용되는 데이터에 대한 접근 시간을 줄이는 데 핵심적인 역할을 한다.
CPU가 특정 데이터에 접근하려고 할 때, 먼저 가장 가까운 캐시를 확인한다. 만약 원하는 데이터가 캐시에 존재하면 이를 캐시 적중 (cache hit)이라고 하며, 데이터를 매우 빠르게 얻을 수 있다. 반대로, 원하는 데이터가 캐시에 없으면 이를 캐시 실패 (cache miss)라고 하며, 캐시는 하위 계층에서 해당 데이터를 가져와 자신에게 저장한 후 CPU에게 전달한다. 캐시의 용량이 제한적이므로, 새로운 데이터를 캐시에 저장해야 할 경우 기존의 데이터 중 일부를 교체 (eviction) 해야 하며, 어떤 데이터를 교체할지를 결정하는 정책을 교체 정책 (replacement policy)이라고 한다 (예: LRU - Least Recently Used).
Cache Hit |
Cache Miss |
캐시 실패는 다음과 같은 세 가지 유형으로 분류될 수 있다:
- Cold Miss (Compulsory Miss): 캐시가 비어있는 상태에서 처음으로 데이터에 접근할 때 발생하는 필연적인 실패이다.
- Conflict Miss: 캐시의 용량은 충분하지만, 여러 개의 데이터 블록이 캐시의 특정 위치(세트)로만 매핑되어 충돌이 발생하여 발생하는 실패이다. 예를 들어, 특정 캐시 세트에 메모리의 블록 0과 블록 8이 모두 매핑될 수 있을 때, 이 둘을 번갈아 접근하면 계속 실패가 발생할 수 있다.
- Capacity Miss: 필요한 데이터의 Working Set (현재 활발하게 사용되는 데이터 집합) 크기가 캐시 용량보다 커서 발생하는 실패이다. 즉, 캐시가 너무 작아서 모든 필요한 데이터를 담을 수 없을 때 발생한다.
다시 오늘의 메인 주제인 메모리 계층 (Memory hierarchy)로 돌아가면, 메모리 계층의 각 단계는 캐싱 메커니즘을 활용한다. 예를 들어, L1 캐시는 L2 캐시의 데이터를 캐싱하고, L2 캐시는 주 기억 장치의 데이터를 캐싱하며, 운영 체제의 버퍼 캐시는 디스크의 데이터를 캐싱하는 방식으로 작동한다. 웹 브라우저 캐시나 웹 프록시 서버 캐시와 같이 네트워크를 통한 데이터 접근을 최적화하기 위한 캐싱 메커니즘도 존재한다.
Memory Hierarchy |
결론
메모리 계층 구조는 속도, 용량, 비용 간의 절충을 통해 컴퓨터 시스템의 전반적인 성능을 향상시키는 핵심적인 기술이다. CPU 레지스터부터 원격 저장 장치까지 여러 단계로 구성된 메모리 계층은 빠르고 작은 상위 계층과 느리고 큰 하위 계층 간의 성능 차이를 완화하며, 프로그램의 지역성(Locality) 특성을 이용하여 캐싱(Caching)이라는 효율적인 데이터 관리 메커니즘을 제공한다. 메모리 계층의 작동 원리와 각 저장 장치의 특징을 이해하는 것은 고성능 컴퓨터 시스템을 설계하고 소프트웨어 성능을 최적화하는 데 필수적인 지식이다. 지속적으로 증가하는 CPU와 메모리 간의 속도 격차를 극복하기 위해 더욱 정교하고 효율적인 메모리 계층 구조 및 캐싱 기술의 발전이 요구된다.