티스토리 뷰


편견이 깨지는 어셈블리 프로그래밍 : 최적화 강좌 2 - 2  
 
 





데이터 흐름에 의한 최적화
언 젠가 컴퓨터를 연구하는 개발자들이 컴퓨터의 동작에서 제일 높은 빈도로 사용되는 동작(operation)이 어떤 것인 지를 확인해 본적이 있다 한다. 그로 인해 컴퓨터에서 제일 많이 하는 동작은 데이터 이동이라는 것을 알게 되었고, 이것으로 인해 RISC 프로세서와 같은 것이 생겨났다. 이 뜻은 데이터 흐름을 최적화하는 것이 컴퓨터 동작의 많은 부분을 최적화시켰다는 것과 같은 말이 된다. 여기서는 데이터 흐름을 최적화하는 방법을 구체적으로 생각해보자.

주소 정렬의 필요성
486 까지는 CPU와 메모리간 데이터 라인이 32비트였기 때문에 데이터를 4바이트씩 읽어왔다. CPU의 속도가 빨라지면서 버스의 속도가 CPU의 처리 속도를 따라오지 못하는 상황이 연출되었고 결국 펜티엄부터는 CPU와 메모리간 데이터 라인이 64비트로 결정되었다. CPU의 처리 속도를 버스가 감당하지 못하므로 결국 방법은 버스의 속도를 높이든지 아니면 한 번에 가져올 수 있는 데이터의 크기를 늘리면 될 것이다. 인텔은 데이터 라인을 64비트로 늘리는 안을 채택함으로서 버스와 CPU의 원활한 동작을 꾀했다.
32비트 데이터 라인을 가진 486 버스 구조에서는 CPU가 메모리로부터 데이터를 읽어올 때 4의 배수로 시작되는 주소로부터 4바이트(32비트)씩 읽어오기 때문에 4의 배수로 주소가 정렬되어 있으면 4바이트를 읽을 경우 한 번에 데이터를 읽어올 수 있지만, 그렇지 않을 경우 4바이트를 읽기 위해서는 메모리로부터 데이터를 더 많이 읽어야 한다. 그러기 때문에 4의 배수 정렬이 안 되었을 경우 속도가 느린 것이다.

64비트 데이터 라인을 가진 펜티엄도 마찬가지이다. CPU는 메모리로부터 8의 배수로 시작되는 주소로부터 8바이트씩의 데이터를 읽어온다. 만일 읽어오려고 하는 데이터가 8의 배수로 정렬이 안 되어 있다면 역시 한 번에 읽어오지 못할 것이다. 앞으로 설명할 버스트 모드는 4나 8의 배수로 정렬된 데이터 라인 크기의 데이터를 메모리로부터 읽을 때 데이터를 더 효율적으로 전송하기 위해 지원된 것이다. 노스 브릿지의 MMU(Memory Management Unit)에서 지원한다.

구조체 정렬
프로그램 내에서 구조체를 사용할 경우 MSVC++ 컴파일러는 CPU가 구조체의 맴버 접근을 최대한 빠르게 할 수 있도록 지원하기 위해 구조체가 실제적으로 메모리(스택이나 힙)에 생성될 때 구조체 내 맴버들의 메모리 배치를 버스의 구조에 맞게 자동으로 정렬한다. 펜티엄 CPU에서부터는 CPU와 메모리간 데이터 라인이 64비트이므로 한번에 8바이트씩 데이터를 읽어올 수 있고 또한 그러한 버스 구조를 지원하기 위해 맴버들을 8바이트 주소로 정렬하여 메모리에 할당해 준다.

<리스트 1>은 구조체 정렬(structure align)을 생각하지 않고 선언한 구조체들이 실제 개발자가 의도한 것과 다른 크기를 갖게 되는 경우를 보여준다. _ST4 구조체를 보도록 하자. 1바이트 char와 4바이트 integer로 구조체가 선언되어 있다. 피상적으로 생각하기에 구조체의 크기는 5바이트(1+4)가 돼야 한다. 하지만 실제로 예제 코드의 실행 결과를 보면 메모리에 생성된 구조체의 크기가 8바이트로 잡히는 것을 알 수 있다. 이것은 앞쪽에 선언된 1바이트 char가 실질적으로는 뒤쪽에 더미 바이트(dummy byte)인 3바이트를 포함해 4바이트의 공간을 차지하기 때문이다. 왜냐하면 int는 4바이트 크기를 가졌으므로 시작 주소가 4바이트로 정렬된 경우에 가장 빨리 맴버 접근을 지원할 수 있기 때문이다. 그래서 결국 구조체의 총 사이즈가 8바이트(4+4)로 되는 것이다.

_ST8 구조체도 원리는 같다. 4바이트 integer와 8바이트 double의 구조체인 경우 8바이트의 double 변수는 8의 배수의 메모리 주소에 정렬돼야 CPU-메모리간 64비트의 데이터 라인의 최대 장점을 이용할 수 있다. 따라서 integer 4바이트 뒤쪽에 더미 4바이트를 넣어 double의 시작 주소를 8바이트의 배수로 맞추게 되고, 구조체의 총 크기가 16바이트가 된다.

◆ 구조체의 맴버 위치
맴버 변수의 크기에 따른 주소 정렬(예를 들면 integer는 4바이트이므로 4의 배수 주소에 위치해야 한다)

◆ MSVC++에서 구조체 정렬 값의 의미
구조체 맴버 크기에 따른 정렬의 한계 값을 설정한다. 예를 들면, __int64는 8바이트 크기이므로 8의 배수 주소에 위치해야 하지만 구조체 정렬을 4로 했을 때는 주소 정렬 크기가 4로 제한되므로 4의 배수 주소에 위치한다.

<리스트 1> 구조체 정렬 예제
#include

struct _ST4
{
char a ;
int b ;
};

struct _ST8
{
int a ;
double d ;
};

void main ()
{
_ST4 st4 ;
_ST8 st8 ;

printf ("sizeof _ST4 = %d, &st4 = %p ", sizeof (st4), &st4) ;
printf ("sizeof _ST8 = %d, &st8 = %p ", sizeof (st8), &st8) ;
}


<리스트 2> 구조체 정렬 예제 결과
sizeof _ST4 = 8, &st4 = 0012FF78
sizeof _ST8 = 16, &st8 = 0012FF68
Press any key to continue


출처: http://www.imaso.co.kr/?doc=bbs/gnuboard.php&bo_table=article&wr_id=249