데이터 구조 정렬 Data Structure Alignment

Processor

대부분의 컴퓨터는 32비트 또는 64비트의 프로세서를 갖고 있습니다. 이는 프로세서가 데이터를 처리할 때 사용하는 레지스터1)의 크기, 데이터들의 전송 통로인 버스의 폭에 따라 결정됩니다.

💡 Tips

BitByteCountRange
81\(2^{8} = 256\)-128 ~ 127
324\(2^{32} = 4,294,967,296\)−2,147,483,648 ~ 2,147,483,647
648\(2^{64} = 18,446,744,073,709,551,616\)–9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807

맨 왼쪽 비트는 부호를 나타내는 비트이고, unsigned를 붙이면 부호 비트 자리도 데이터가 올 수 있음

Memory access

프로세서가 메모리에 읽기 또는 쓰기를 할 때도 32비트 또는 64비트씩 접근하여 처리합니다. 따라서 프로세서의 아키텍쳐에 맞추어 4의 배수 또는 8의 배수의 사이즈가 되게 코딩을 해주면 메모리 접근을 줄일 수 있고 효율과 성능을 높일 수 있습니다!

Unaligned access

현재 64비트 프로세서이고, 위 사진에서 하나의 칸이 1 byte라고 생각한다면, b0부터 b7까지 접근하기 위해 몇 번의 엑세스가 필요할까요?

위 사진과 같이 정렬이 되지 않은 상태라면 b0부터 b3까지 접근하기 위해 0부터 7까지 8 바이트를 읽어야 하고 b4부터 b7까지 접근하기 위해 8부터 15까지의 8 바이트를 읽총 두 번의 엑세스가 필요합니다.

b0부터 b70부터 7까지 모두 붙어 있었더라면 한 번의 접근으로 데이터를 모두 읽을 수 있었을 텐데 말입니다.

Aligned access

💡 Data structure alignment is the way data is arranged and accessed in computer memory. It consists of three separate but related issues: data alignment, data structure padding📝, and packing. wikipedia

위와 같이 프로세서가 데이터를 효율적으로 가져갈 수 있도록 하는 것Data structure alignment 또는 Memory alignment라고 합니다. 이를 위해 컴파일러는 알아서 패딩을 넣어 정렬합니다.

Padding 📝

패딩은 솜 따위를 넣어 속을 메워 넣는 행위를 뜻합니다. 웹 사이트의 CSS 요소인 패딩도 안을 넣어 부풀립니다. 우리가 코딩할 때 변수를 선언하면 메모리에 할당 되는데 패딩의 개념을 이용하여 4의 배수 또는 8의 배수를 맞추어 효율성과 성능을 잡을 수 있습니다.

padding

⚠️ int 자료형은 플랫폼마다 사이즈가 다를 수 있습니다! 위 사진에서 8 바이트로 소개 되었지만 보통의 경우 4 바이트를 사용한다고 합니다.

Data structure padding

이미 정의된 자료형은 특별하게 해줄 것이 없습니다. 하지만 구조체의 경우는 조금 다릅니다.

구조체다양한 자료형의 모음이며 설계에 따라 컴파일러가 패딩을 적게 또는 많게 넣을 수 있습니다. 우선 아래의 예를 보겠습니다.

1
2
3
4
5
6
7
typedef struct Some_t
{
    char    a; // 1 byte
    short   b; // 2 byte
    int     c; // 4 byte
    char    d; // 1 byte
} Some;

단순히 자료형의 사이즈만큼 모두 더했을 때 8 바이트가 나오고, 64 비트 프로세서에서는 한 번에 접근하여 처리할 수 있는 사이즈 인 것 같습니다.

하지만 컴파일러는 다음과 같은 규칙을 갖고 패딩을 만들고 있어 8 바이트의 구조체가 만들어지지 않습니다.

💡 컴파일러가 구조체에 패딩을 넣는 규칙

  1. 구조체의 크기는 가장 큰 사이즈를 갖는 자료형의 사이즈의 배수로 설정
  2. 가장 큰 사이즈를 갖는 자료형으로 데이터 정렬
  1. 가장 큰 사이즈를 갖는 자료형은 int
  2. 첫 번째 charshort 총 3 바이트를 int 에 정렬 = 1 바이트 패딩
  3. 세 번째 int 4바이트를 int 에 정렬 = 패딩 없음
  4. 네 번째 char 1바이트를 int 에 정렬 = 3 바이트 패딩

따라서 위 구조체는 12 바이트(int 3개)가 되며 두 번 접근해야 처리할 수 있는 사이즈로 만들어집니다.

8의 배수로 맞추기 위해 아래와 같이 코딩할 수 있습니다.

1
2
3
4
5
6
7
typedef struct Some_t
{
    char    a; // 1 byte
    char    d; // 1 byte
    short   b; // 2 byte
    int     c; // 4 byte
} Some;
  1. 가장 큰 사이즈를 갖는 자료형은 int
  2. 첫 번째, 두 번째의 charshort 총 4 바이트를 int 에 정렬 = 패딩 없음
  3. 두 번째 int 4바이트를 int 에 정렬 = 패딩 없음

위 구조체는 8 바이트(int 2개)가 되며 한 번에 처리할 수 있는 사이즈로 만들어집니다.

정리

프로세서는 32비트 또는 64비트라는 특정 사이즈만큼의 레지스터와 버스 폭을 갖고 있고 데이터 처리를 효율적으로 시키기 위해 이를 맞추어 주면 됩니다. 구조체의 경우 단순하게 큰 사이즈를 갖는 자료형을 먼저 쓰는 것이 프로세서의 작업이나 메모리의 공간 측면에서 효율적으로 시키거나 할당하게 될 것 같습니다.

각주

  1. 프로세서 레지스터(영어: processor register, 순화 용어: 기록기) 또는 단순히 레지스터는 컴퓨터의 프로세서 내에서 자료를 보관하는 아주 빠른 기억 장소이다. 일반적으로 현재 계산을 수행중인 값을 저장하는 데 사용된다. wikipedia