dev.log2009. 9. 7. 02:39
대략적으로, VC++에서 구조체 멤버의 배치와 관련된 개념은 2가지. #pragma pack, 혹은 컴파일러의 /Zp 옵션으로 지정하는 패킹과, __declspec(align)으로 지정하는 정렬이 있다.

원칙적으로는 SSE명령이나 캐쉬에 적합성을 높이기 위한 메모리 배치는 __declspec(align)으로 하는 것이 맞다. 그런데 문제는 __declspec(align) 속성을 가진 벡터 객체, 혹은 이를 포함하는 클래스의 객체가 STL 컨테이너에 들어갈 수 없다는 것이다. 이는 다음 두가지 사실에서 기인한다.
1. VC++컴파일러는 __declspec(align) 속성을 가진 객체를 함수의 밸류타입 인자로 넘길 수 없게 구현돼 있다.
2. STL컨테이너들의 기본생성자는 초기화 과정에서 resize메소드를 호출하는데, 표준에 의하면 resize 메소드는 밸류 타입으로 인자를 받도록 되어 있다.
그러한 고로, 주소 정렬이 포함돼 있는 클래스의 객체는 STL컨테이너에 넣을 수가 없다는 당혹스러운 결과에 직면하게 된다.

전에 회사에서 쓰던 엔진은 vector3 클래스의 선언부를 #pragma pack(16)으로 감싸놓고 할당자만 재정의해서 쓰고 있었다. 요 벡터 클래스가 심심하면 뻗을 때가 있었는데, 대개의 경우는 객체의 주소가 16바이트 정렬이 안된 채로 생성이 되어 SSE 내장 함수에서 뻗어버리는 것이었다. 이때의 경험으로 미루어 안될거 같다는 생각을 하면서도, 집에서 놀던 차에 잉여력을 활용하여 둘 간의 차이점에 대해서 삽질을 좀 해봤다.

사용한 선언은,
#pragma pack( push, 16 )
struct test_pack
{
    float x;
    float y;
    float z;
};
#pragma pack(pop)

struct __declspec(align(16)) test_align
{
    float x;
    float y;
    float z;
};
이렇게 해놓고서 배열을 선언해서 비교해 보았다.
    FILE* out = fopen( "align_pack.txt", "w");
#define LOG_OFFSET(tag,member) fprintf( out, "offset of "  #tag "::" #member " = %d\n", offsetof( tag, member ) );
#define LOG_SIZE(tag) fprintf( out, "size of "  #tag " = %d\n", sizeof( tag ) );
#define LOG_ADDR(pointer) fprintf( out, "address of "  #pointer " = 0x%x\n", pointer );
    LOG_OFFSET( test_pack, x );
    LOG_OFFSET( test_pack, y );
    LOG_OFFSET( test_pack, z );
    LOG_SIZE( test_pack );

    test_pack tp[3];
    LOG_ADDR( &tp[0] );
    LOG_ADDR( &tp[1] );
    LOG_ADDR( &tp[2] );

    LOG_OFFSET( test_align, x );
    LOG_OFFSET( test_align, y );
    LOG_OFFSET( test_align, z );
    LOG_SIZE( test_align );

    test_align ta[3];
    LOG_ADDR( &ta[0] );
    LOG_ADDR( &ta[1] );
    LOG_ADDR( &ta[2] );

    fclose( out );
결과는,
offset of test_pack::x = 0
offset of test_pack::y = 4
offset of test_pack::z = 8
size of test_pack = 12
address of &tp[0] = 0x12fecc
address of &tp[1] = 0x12fed8
address of &tp[2] = 0x12fee4
offset of test_align::x = 0
offset of test_align::y = 4
offset of test_align::z = 8
size of test_align = 16
address of &ta[0] = 0x12fe90
address of &ta[1] = 0x12fea0
address of &ta[2] = 0x12feb0


패킹으로 선언된 test_pack의 경우는 주소 자체가 16바이트 정렬이 안되기 때문에, 패딩 바이트를 주더라도 SSE나 캐쉬라인에 맞추기 위한 목적으로는 쓰일 수 없다는 것이 분명해졌다. 구조체 멤버를 다른 타입으로 (char, short 등) 몇가지 더 추가하면서 알아낸 바로는, #pragma pack으로 지정하는 패킹은 멤버들의 구조체 내에서의 옵셋에만 영향을 주고, 구조체 자체의 배치에는 영향을 끼치지 않는다는 것.

vector3 클래스를 생성 영역에 따라 분리하는 것도 방법. 힙에서 생성할 객체들은 할당자에 의해 정렬이 되는 클래스로 분리하고, 스택이나 정적 데이터 영역에 생성할 객체들은 __declspec(align)으로 분리한다는 것인데... 이럴 경우에도 STL의 메소드가 내부적으로 사용하는 임시 객체가 정렬이 안이루어지면 뻗을 수밖에 없다.

이제 vector3 클래스를 SSE를 활용하면서 컨테이너에 넣기 위한 방법은 1가지밖에 없다는 결론이 도출된다. 바로 사용하고자 하는 모든 컨테이너 클래스의 메소드를 밸류 타입으로 인자를 받지 않도록 상수 참조의 형태로 바꿔서 재정의한다는 것. 대부분의 STL컨테이너를 커스터마이징해야 한다는 얘기. 이건 삽질도 보통 삽질이 아닌데.....


Posted by uhm
misc.log2009. 8. 22. 14:50
모두가 가는구나.

Posted by uhm
dev.log2009. 7. 3. 17:17
행렬 클래스를 다음과 같이 구현해보려고 했었다.
struct matrix
{
    vector4 m[4];
    matrix( scalar m11, scalar m21, scalar m31, scalar m41,
               scalar m12, scalar m22, scalar m32, scalar m42,
               scalar m13, scalar m23, scalar m33, scalar m43,
               scalar m14, scalar m24, scalar m34, scalar m44 )
        : m[0]( m11, m21, m31, m41 ),
          m[1]( m12, m22, m32, m42 ),
          m[2]( m13, m23, m33, m43 ),
          m[3]( m14, m24, m34, m44 )
    {
    }
};

결론적으로 말하자면, 위와 같은 구문은 허용되지 않는다. 저런 식으로 접근해 보려고 1시간쯤 삽질했는데, 결과적으로는 안되는 거였다. 잠깐 생각해 보니 알 수 있었는데, 배열의 초기화는 {}로 묶인 배열의 초기화 목록으로만 가능하기 때문이다. 저런 구문이 작동 가능하려면 전역 배열도 다음과 같이 초기화할 수 있어야 한다.
[header]
extern int array[4];

[source]
int array[0](0);
int array[1](1);
int array[2](2);
int array[3](3);
배열의 이름/크기 선언과 별도로 배열의 내용물을 각기 따로 초기화할 수 있는 방법이 있다면 위의 구문이나 아래의 구문이나 내용면에서는 차이가 없는데, 아래와 같은 선언이 당연히 안될 거라는 것에 생각이 미치자 위의 것도 당연히 안된다는 걸 깨달았다. 기본 생성자에서 아무것도 안하게 하면 생성자 바디에서 초기화해도 오버헤드는 없으므로 상관은 없지만, 그래도 초기화는 초기화 리스트에서 해주는 것을 선호하는 지라, 약간 불만.

그리고 또 하나의 삽질은, 행렬의 SSE구현에서 다음과 같은 생성자를 만들려고 한 것이었다.
struct matrix
{
    matrix( __m128 v1, __m128 v2, __m128 v3, __m128 v4 )
    {
        //............
    }
};
__m128 구조체는 VC에서 선언한 SSE 내장함수용 16바이트 정렬 구조체인데, 위 생성자를 컴파일하면 2719 에러가 난다.
http://msdn.microsoft.com/ko-kr/library/373ak2y1.aspx
그냥 안되는 거였슴. 줵.

Posted by uhm