메모리 정렬이란?
왜 같은 구조체인데 메모리 크기가 다를까?
본 블로그 작성 글들은, 강의를 목표로 하고 제작되었습니다. 또한 작성된 글의 정보도 틀렸을 수 있으니, 주의해주세요.
코딩하다 보면 구조체 크기가 내 생각과 다르게 잡히는 경우가 있다. 이 현상을 정식 명칭으로 Memory Alignment(메모리 정렬) 및 Padding(패딩) 이라고 부른다.
CPU는 메모리에서 변수들을 읽어올 때, 1 Byte 단위로 쪼개서 읽어오지 않는다. 이는 워드 단위로 (보통 4 Byte 또는 8 Byte) 뭉텅이로 읽어오기 때문에, 변수들이 메모리에 어떻게 배치되어 있는지가 성능에 엄청나게 중요하다.
구조체의 멤버들이 메모리에 어떻게 배치되어 있는지에 따라 구조체의 전체 크기가 달라질 수 있는데, 이는 패딩(Padding)이라는 개념 때문이다. 패딩은 구조체의 멤버들이 CPU의 워드 경계에 맞춰 정렬되도록 돕기 위해 컴파일러가 자동으로 쑤셔 넣는 빈 공간이다. 중간에 빈 공간이 들어가니 당연히 구조체의 크기도 증가하게 된다.
코드로 직접 확인해 보자.
#include <iostream>
#include <cstddef> // offsetof 매크로 사용
// 1. 비효율적인 메모리 배치 (총 24 Byte)
struct BadOrder {
int A; // 4 Byte, 다음 변수는 8 Byte 정렬이 필요하므로 4 Byte 패딩 발생 (총 8 Byte 사용).
long long B; // 8 Byte, 혼자 8 Byte 사용. (총 16 Byte 사용)
bool C; // 1 Byte, 1 Byte 사용 후 7 Byte 패딩 발생 (총 24 Byte 사용).
};
// 2. 컴파일러 최적화를 고려한 효율적인 메모리 배치 (총 16 Byte)
struct GoodOrder {
long long A; // 8 Byte, 혼자 8 Byte 사용. (총 8 Byte 사용)
int B; // 4 Byte, 남은 4 Byte에서 다음 변수가 사용될 수 있으므로, 패딩 발생하지 않음. (총 12 Byte 사용)
bool C; // 1 Byte, 1 Byte 사용 후 3 Byte 패딩 발생. (총 16 Byte 사용)
};
// 3. 패딩을 강제로 없앤 구조체 (총 13 Byte)
#pragma pack(push, 1)
struct PackedStruct {
int A; // 4 Byte
long long B; // 8 Byte
bool C; // 1 Byte
};
#pragma pack(pop)
여기서 궁금한 점이 생긴다. BadOrder 끝에는 7 Byte, GoodOrder 끝에는 3 Byte의 패딩이 굳이 왜 더 붙을까? 이건 구조체가 배열 등으로 묶일 때를 대비한 정렬 요구사항 때문이다. 구조체의 전체 크기는 내부 멤버 중 가장 큰 데이터 타입(위에서는 long long의 8 Byte)의 배수가 되어야 한다. 그래야 배열의 다음 요소로 넘어갈 때도 똑같이 8 Byte 정렬을 보장받을 수 있기 때문이다.
3번은 #pragma pack을 써서 패딩을 강제로 없애는 방법이다. 이렇게 하면 메모리는 딱 13 Byte로 아낄 수 있지만, 치명적인 성능 저하가 발생할 수 있다. CPU가 워드 단위로 읽어오는데 데이터의 시작점이 어긋나 있으면, CPU는 메모리를 두 번 읽어서 억지로 합치는 쓸데없는 추가 연산을 해야 한다. 심지어 특정 하드웨어 환경에서는 아예 프로그램이 뻗어버릴 수도 있다.
또한 캐시 라인(Cache Line) 효율성도 무시할 수 없다. 현대 CPU는 메모리를 보통 64 Byte 단위로 통째로 가져오는데, 1번처럼 24 Byte면 64 Byte 안에 2개밖에 못 넣지만, 2번처럼 16 Byte로 최적화하면 4개가 깔끔하게 다 들어간다. 배열을 반복문으로 돌릴 때 처리 속도에서 엄청난 차이가 나게 된다.
네트워크 통신 규격이나 파일 입출력처럼 1 Byte의 오차도 없이 메모리 레이아웃이 정확해야 하는 특수한 경우가 아니라면, 일반적인 상황에서는 패딩을 허용하는 것이 성능 측면에서 훨씬 이득이다. 따라서 2번과 같이 크기가 큰 멤버부터(또는 역순) 묶어 배치하여 불필요한 패딩을 최소화하는 습관을 들이는 것이 좋다.