Programmer

Memory Alignment의 필요성

Memory Alignment(메모리 정렬)는 왜 필요할까?

1. 초기적 접근

메모리 정렬은 현대에 갑자기 생겨난 개념이 아니다.
초기에 어떻게 생겨났을지를 생각해보라.

자, 32비트 CPU에서 4-byte 데이터가 4의 배수가 아닌 주소에 존재한다고 생각해보자.
아래와 같이 데이터가 4-byte의 경계에 걸쳐있다.
이 데이터를 읽으려면 어떻게 해야할까?

0  1  2  3  4  5  6  7  8 = address
|-----------|-----------|
|  |  |00|F4|14|1F|  |  | = data
|-----------|-----------|

CPU는 워드 사이즈로 워드 사이즈의 배수인 주소만 읽기가 가능하다.
그래서 이렇게 되면 CPU는 0-3 범위의 데이터를 한번 읽고,
4-7 범위를 한번 더 읽은 뒤 합쳐야 한다.
즉 한번에 읽을 수 없어서 추가적인 과정이 필요하다.
캐시라인이나 페이지경계에 걸쳐 있다면 문제가 생기기도 한다.

x86 같은 아키텍쳐는 이런 정렬되진 않은 데이터도 읽은 뒤 합쳐주지만,
GPU나 RISC계열 CPU는 대부분 이를 지원하지 않아 bus-error를 낸다.

여기까지가 일반적인 설명인데,
“그래서 왜 CPU는 꼭 워드 사이즈 경계로만 데이터를 읽는가?”

32비트 CPU가 하나 있다고 생각해보자.
CPU가 메모리에서 load하기 위해선 IR를 다음과 같이 세팅한다.

1000 0000 0000 0000 0000 0000 0000 0000
||
|└-> 0 : R0
|    1 : R1
|
└--> 0 : STORE
     1 : LOAD
  1. 0번 비트는 메모리에 store를 하는지, load를 하는지 지정한다.
  2. 1번 비트는 어떤 레지스터의 내용을 store 할지,
    어떤 레지스터에 내용을 load 해올지 지정한다.
  3. 나머지 30개의 비트는 가져올 메모리의 주소를 표현한다.

그런데 어라, 주소를 표현할 수 있는 비트가 30개 밖에 안된다.
30비트로 32비트를 표현해야 하는데 어떻게 하면 될까.
30비트가 나타내는 주소의 단위가 4바이트가 되도록 하면 된다.
그러면 표현할 수 있는 주소가 4배 늘어나므로 30비트로 32비트를 표현할 수 있다.

┌-------------30 bit----------------┐
???? ???? ???? ???? ???? ???? ???? ??00

최하위 비트 2개가 무조건 0이라고 지정하면
4의 배수만 표현이 가능하지만
위에서 얘기한대로 30비트로 32비트의 범위를 표현가능해진다.

(오해할 수 있는데, CPU에서 포인터 주소연산 할 때에는 여전히 1바이트 단위이다.
CPU에서 메모리에 요청할 때 4바이트 단위로 요청해 가져와서 따로 후처리를 한다는 것이다.)

2. 현대에서는

현대의 메모리 페이징, Resizable BAR 등도 1과 마찬가지로 적은 비트수를 이용해 더 넓은 주소공간을 매핑하는 기술이 계속 사용되고 있으며,
CPU/GPU 캐시라인, BUS 버스트 모드 등 한 번의 io에 단일 바이트 메모리가 아니라 주변 메모리 까지 합쳐서 한 섹션을 들고오는 방법이 계속 사용되고 있기 때문에
페이지 바운더리(4kb), 캐시 바운더리(x64-64byte), DRAM 채널 바운더리(256bit) 등등등등… 이러한 ‘경계’가 더 많이 존재할 수 밖에 없게 되었다.

이렇게 경계에 걸쳐있는 데이터는 raw한 레벨에서는 곧바로 읽을 수 없을 것이다.
물론 arm계열이나 raw api등은 이런 접근이 아예 실패하고,
cisc계열(x64)이나 고수준 api는 내부적으로 두 번에 걸쳐 읽는 방식으로 어찌저찌 처리해주긴 한다.

하지만 내부적으로 어찌저찌 처리해준다고 해도 그 과정에서 레이턴시가 확연히 길어지거나 내부 설계가 극히 복잡해지게 되고.
MESI protocol 같은 CacheLine Coherence 시스템은 모든 버스에 락을 잡지 않으면 그 일관성이 깨질 수 있게 된다.


Update

2026-07-16 - 전체 글 구조 보강