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
- 0번 비트는 메모리에 store를 하는지, load를 하는지 지정한다.
- 1번 비트는 어떤 레지스터의 내용을 store 할지,
어떤 레지스터에 내용을 load 해올지 지정한다. - 나머지 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 - 전체 글 구조 보강