C++/데이터 정렬에 대해


데이터 정렬(Data Alignment)
프로그램이 실행되는 동안 그 성능에 가장 큰 영향을 미치는 것은 CPU의 캐시일 것이다. CPU 제조사마다 캐시를 관리하는 것에 대한 설계가 조금씩 다르지만 크게 보았을 때 구조와 동작원리는 비슷한데, 많은 CPU 제조사들은 캐시에 데이터를 적재할 때 데이터를 가져오기 위해서 캐싱과 Prefetching을 동시에 수행하고, 이 과정을 특정 데이터 블록 단위로 수행하는 경우가 많다. 이 데이터 블록을 캐시 라인(Cache line)이라고 한다.

그리고 자주 사용되는 데이터를 프로세서의 캐시 라인 크기에 맞추면 효율적으로 캐시된다. 이런 이유에서 컴파일러는 가능하면 캐시 라인 크기에 맞도록 메모리 상에 데이터를 적재한다. 이것이 프로그래밍에서 말하는 데이터 정렬이다.

C++의 데이터 정렬
C++ 컴파일러는 기본적으로 데이터 블록의 크기에 기반해 데이터를 정렬한다. 데이터 정렬 크기는 1부터 8192(바이트) 까지의 2의 승수, 즉 2, 4, 8, 16, 32, 64 등을 가질 수 있다. 데이터 정렬 크기가 정해지는 방법은 가능하면 가장 작은 크기로 정해진다. 즉, 4 바이트 int는 가능한 가장 작은 정렬 크기인 4 바이트 정렬을 한다(뒤에 보다 자세히 설명).

pack
컴파일러의 데이터 정렬을 변경하려면 다음 지시어를 사용한다.

#pragma pack(push, #) 
구조체
#pragma pack(pop)

#에는 1부터 2의 승수가 들어갈 수 있고, 2의 승수 외 다른 수가 들어가면 경고가 발생하고 지시어는 적용되지 않는다.
#pragma pack(pop)을 하지 않으면 #pragma pack(push, #)이 사용된 이후 코드는 전부 적용이 되게 된다. 주의해야 한다.

적용 예.
struct A
{
   char a;
   short b;
};

sizeof A = 4 Bytes

구조체 멤버 변수 크기를 합치면 char (2 바이트) + short (1 바이트) = 3 바이트지만 2 바이트로 정렬되어 struct A는 4 바이트가 되었다.

#pragma pack(push, 1) 
struct A
{
    char a;
    short b;
};
#pragma pack(pop)

sizeof A = 3 Bytes

1 바이트 정렬 지시가 적용되어 3바이트가 되었다.


데이터 정렬 크기가 결정되는 방법
다음 예를 통해 알아본다.
#pragma pack(push, 8) 
struct A
{
    char a;
    short b;
    int c;
    int d;
};
#pragma pack(pop)

sizeof A = 12 Bytes

이 경우 struct A 멤버들의 크기는 2 + 1 + 4 + 4 = 11 이고 4 바이트 데이터 정렬이 적용되어 크기는 12가 된다. 컴파일러는 모든 타입의 크기를 알고 있기 때문에 컴파일 중에 구조체 안의 가장 큰 타입의 크기에 의해(항상 보장하지는 않는다. 하지만 일반적으로 그렇다.) 데이터 정렬 크기가 정해진다. 이 경우 int의 4 바이트로 정렬되었다.

이 때 #pragma pack(push, 8)은 무시된다. 8 바이트로 정렬되어 struct A의 크기가 16이 되어야 한다고 생각할 수도 있지만 #pragma pack은 정렬 크기가 설정 값 이상으로 pack 되었을 때만 적용된다. struct A는 4 바이트로 정렬되었기 때문에 8 바이트로 정렬하라는 지시는 무시된다. 즉, pack 지시어가 적용되는 예는 아래와 같다.

struct A
{
    char a;
    short b;
    double c;
};

sizeof A = 16 Bytes

char (2) + short (1) + double (8) = 11 바이트가 8 바이트로 정렬되어 16 바이트가 되었다.

#pragma pack(push, 4) 
struct A
{
    char a;
    short b;
    double c;
};
#pragma pack(pop)

sizeof A = 12 Bytes

char (2) + short (1) + double (8) = 11 바이트가 4 바이트로 정렬되어 12 바이트가 되었다.


컴파일러 별 차이점

데이터 정렬 크기를 변경할 때 MS VC++과 gcc 등 컴파일러마다 데이터 정렬 방식이 다르는 점을 알아둬야 한다. 그 차이는 다음과 같다.

#pragma pack(push,1)
struct A {
    int a:8;
};
#pragma pack(pop)

멤버 변수에 비트필드를 사용했을 때 A는 VC++는 4 바이트가 되고 gcc는 1 바이트가 된다.

VC++는 비트필드 사용에 상관 없이 멤버들의 자료형 내에서 데이터 정렬 크기가 결정되고 gcc는 비트필드의 합이 8비트 이하면 1 바이트가 된다.


참고



이 글에는 0 개의 댓글이 있습니다.