본문 바로가기
프로그래밍

Windows CPU 효율 향상을 위한 프로그래밍시 데이터 정렬

by 안플루언서 2011. 1. 21.




아래의 포스팅 내용은 100%정확한 내용이라고 할수 없으며 틀린점이 있으면 지적해주시면 감사하겠습니다.



Windows CPU 효율 향상을 위한 프로그래밍시 데이터 정렬
BYTE ALIGNMENT , DATA ALIGNMENT
CPU에서 메모리의 데이터를 읽어올때
2바이트 메모리의 경우 (WORD 형 데이터 라던지.) 메모리의 주소가 2로 나누어 떨어지는 위치.
4바이트 메모리의 경우 4로 나누어 떨어지는 위치가 효율이 좋다고 한다.

void func( void* buff )
{
	// 첫 주소의 1바이트 읽기
	char c = *((char*)buff);

	// 첫 바이트 제외한 주소 2~5까지의 4바이트 데이터 읽기
	int buff2 = *(int*)((char*)(buff)+1);
}

위와 같은 방식으로 데이터를 사용할때
4바이트 데이터를 가져오려면 CPU는
데이터 비정렬 예외를 유발한다..
보통의 CPU는 예외 이런상황에서 예외를 알아서 처리하고
4바이트의 데이터를 읽기 위해 앞부분 3바이트와 뒷부분 1바이트로 나누어서 두번 읽어오게된다.
이렇기 때문에 CPU의 효율을 높이기 위해서는 데이터를 각 CPU의 비트에 맞게 정렬해서 사용하는게 좋다.


class S1 
{
public:
	char m_1;             // 1-byte element
	// 3-bytes of padding are placed here
	int m_2;           // 4-byte element
	double m_3, m_4;      // 8-byte elements
};

#pragma pack(1)
class S2
{
public:
	char m_1;             // 1-byte element
	// 3-bytes of padding are placed here
	int m_2;           // 4-byte element
	double m_3, m_4;      // 8-byte elements
};
#pragma pack()

class S3
{
public:
	char m_1;             // 1-byte element

	// 1-bytes of padding are placed here

	short m_5;             // 2-byte element

	int m_2;           // 4-byte element
	int m_6;           // 4-byte element
	double m_3, m_4;      // 8-byte elements
};

void main()
{
	short a = 0;
	char b = 0;
	char c = 0;
	S1 x;
	long y = 0;
	S1 z[5];
	printf("S1 size   = %d\n", sizeof(S1));
	printf("a         = %p\n", &a);
	printf("b         = %p\n", &b);
	printf("c         = %p\n", &c);
	printf("x         = %p\n", &x);
	printf("x.m_1     = %p\n", &x.m_1);
	printf("x.m_2     = %p\n", &x.m_2);
	printf("x.m_3     = %p\n", &x.m_3);
	printf("x.m_4     = %p\n", &x.m_4);
	printf("y         = %p\n", &y);
	printf("z[0]      = %p\n", &z[0]);
	printf("z[1]      = %p\n\n", &z[1]);

	S2 s2;
	printf("S2 size   = %d\n", sizeof(S2));
	printf("s2         = %p\n", &s2);
	printf("s2.m_1     = %p\n", &s2.m_1);
	printf("s2.m_2     = %p / +%d\n", &s2.m_2, ((char*)&s2.m_2 - (char*)&s2.m_1));
	printf("s2.m_3     = %p / +%d\n", &s2.m_3, ((char*)&s2.m_3 - (char*)&s2.m_2));
	printf("s2.m_4     = %p / +%d\n\n", &s2.m_4, ((char*)&s2.m_4 - (char*)&s2.m_3));


	S3 s3;
	printf("s3 size   = %d\n", sizeof(S3));
	printf("s3.m_1    = %p\n", &s3.m_1);
	printf("s3.m_5    = %p / +%d\n", &s3.m_5, ((char*)&s3.m_5 - (char*)&s3.m_1));
	printf("s3.m_2    = %p / +%d\n", &s3.m_2, ((char*)&s3.m_2 - (char*)&s3.m_5));
	printf("s3.m_6    = %p / +%d\n", &s3.m_6, ((char*)&s3.m_6 - (char*)&s3.m_2));
	printf("s3.m_3    = %p / +%d\n", &s3.m_3, ((char*)&s3.m_3 - (char*)&s3.m_6));
	printf("s3.m_4    = %p / +%d\n", &s3.m_4, ((char*)&s3.m_4 - (char*)&s3.m_3));
}







참고 : data alignment 에 대한 msdn 문서 http://msdn.microsoft.com/en-us/library/aa290049(v=vs.71).aspx

위의 예제는 msdn 에서 가져온걸 약간 수정한 것이다.

Visual Studio 2008 C++ 에서 기본 세팅으로 실험했을때.

* 구조체 S1
구조체 S1에서 m_1의 주소와 m_2의 주소가 4바이트 차이가 난다.
m_1과 m_2의 사이의 3바이트는 버려지게 되는것이다.
아래의 그림 참조..



m_2 가 int형이라  메모리 주소상 4로 나누어 떨어지는 주소에 공간이 할당 되게 된다.
이것은 Struct의 기본 데이터 정렬이 각 자료형의 사이즈로 메모리 주소를 나누었을때
딱 떨어지는 메모리 주소가 CPU에서 사용하기에 좋기 때문일것이다.
m_3의 위에 선언된 맴버 변수 m_2가 8로 나누어 떨어지는 주소에서 공같이 끝났기때문에
m_3 는 바로 뒤에 붙을수 있었다.

* 구조체 S2
S2 는 #pragma pack(1) 을 이용해 구조체의 데이터 정렬을 1바이트 단위로 하도록 설정하였다.
아래의 그림 참조..


1바이트 정렬을 적용한 뒤의 구조체는 21바이트가 나왔다..
메모리 상의 효율은 좋아졌다..
하지만 CPU의 연산은 32비트CPU에서 4바이트씩 읽기때문에
S1의 각 데이터를 가져오기 위해서는 6번이면 되지만 
S2의 각 데이터를 가져오기 위해서는 9번의 연산을 해야되므로 늦어지게 되는것이다.

* 구조체 S3
padding 되는 공간을 더 확인해 보기 위해 구조체를 살짝 수정함..
아래의 그림을 참조..


구조체S1 과의 차이점은  short m_5 와 int m_6 를 맴버 변수 사이에 추가 하였다.
m_5는 m_1의 끝난메모리주소 에서부터 2로 나누어 떨어지는 공간에 할당 되고..
m_6의 추가 선언으로인해 m_3이 8바이트가 밀려서 16바이트의 위치에 할당이되었다..

위와 같은 상황을 개선하기 위해서는 아래와 같이 변수 선언을 조절하는게 좋다.
class S3
{
public:
	double m_3, m_4;      // 8-byte elements
	int m_2;           // 4-byte element
	int m_6;           // 4-byte element
	short m_5;             // 2-byte element
	char m_1;             // 1-byte element
};


이런식으로 조절하게 된다면 메모리 효율과 CPU 효율을 둘다 향상시킬수 있을것이다.


* 참고
64 비트와 32 비트 양쪽에 돌아가게하는 프로젝트에서는 
포인터의 사이즈가 4바이트에서 8바이트로 변경되므로 이것도 주의해야한다.


댓글