'템플릿'에 해당되는 글 4건

  1. 2009.02.17 행렬 구현
  2. 2008.02.05 xmmintrin.h 2
  3. 2007.12.12 템플릿과 매크로
  4. 2005.01.13 오늘 나눈 대화 한조각
dev.log2009. 2. 17. 00:00
행렬을 구현하는 데에 있어서 가장 어려운 것은, 바로 메모리상의 배열 우선순위(major ordering)를 정하는 것이다. 단위 배열로 행벡터를 쓸 것인가 열벡터를 쓸 것인가를 결정하는 것인데, 행벡터는 행렬에 있어서 같은 행번호를 가진 원소가 서로 인접하도록 메모리 상에 배치하는 것이고, 열벡터는 같은 열 번호를 가진 원소가 서로 인접하도록 메모리 상에 배치하는 것. 어차피 둘 중의 하나 밖에 선택할 수 없기 때문에 심플한 문제이기는 한데, 좀 미묘한 문제가 걸려 있다.

우선, 행우선(row major) 배치는, 다음을 보자.


이와 같은 행렬이 행 우선 배열에서는 메모리 상에 선형으로 다음과 같이 배치된다.

 a00 a01 a02 a03 a10 a11 a12 a13 a20 a21 a22 a23 a30 a31 a32 a33

이는 C/C++의 2차원 배열 선언과 동일한 배치이다.

float a[4][4]

위와같은 선언은 메모리를 다음과 같이 배치한다.

a[0][0] a[0][1] a[0][2] a[0][3] a[1][0] a[1][1] a[1][2] a[1][3] a[2][0] a[2][1] a[2][2] a[2][3]
... a[3][0] a[3][1] a[3][2] a[3][3]

따라서, 배열의 첨자가 그냥 순서대로 [행번호][열번호]가 된다는 편리함이 있다. 행렬의 이름이 '행', '열' 순서이므로 이름과 잘 일치한다. 또한 C/C++의 기본 배열과 개념상 일치해 보인다는 장점이 있고, Direct3D에서 사용하는 방식이므로 별다른 변환 없이 Direct3D에서 사용이 가능하다. 유혹적이다.

반면, 열우선(column major) 배치는 행렬을 메모리에 배열하는 방법이 반대다.

 a00 a10 a20 a30 a01 a11 a21 a31 a02 a12 a22 a32 a03 a13 a23 a33

이를 2차원 배열로 표현한다면 [열번호][행번호]의 순서로 첨자를 부여해야 한다는 점이 C/C++의 표준과 불일치한다는 인상을 준다. 하지만 어차피 가로/세로의 선호는 사람에 따라 달라지는 것이므로 열이 앞이냐 행이 앞이냐 하는 문제는 표준에 정의되어 있지 않은 문제이다. 메모리를 가로 방향으로 나열해서 그렇지, 다음과 같이 나열하면 자연스러워 보인다.

 a00
 a10
 a20
 a30
 a01
 a11
 a21
 a31
 a02
 a12
 a22
 a32
 a03
 a13
 a23
 a33

이는 전적으로 메모리가 위아래로 뻗어있느냐 양옆으로 뻗어있느냐로 보는 것의 차이인데, 사실 어느 쪽을 택해도 무방하다. 물론 행렬을 표현한 2차원 배열에서 첨자의 순서가 '행','열' 순서가 아닌 점은 약간 불편할 것 같긴 하다. 하지만 흔히 쓰는 수학적인 (벡터를 세로로 적고 행렬의 뒤에 곱하는) 표현과 일치한다는 장점이 있고, OpenGL에서 사용하는 오더링이므로 OpenGL에서 별다른 변환 없이 사용이 가능하다.

ice 프로젝트의 요구조건중 하나가 OpenGL과 Direct3D의 동시 지원이었기 때문에, 가능한 방법은 3가지다
1. 행 우선 배치로 구현하고, OpenGL에서는 일일이 transpose하여 로드한다.
2. 열 우선 배치로 구현하고, Direct3D에서는 일일이 transpose하여 로드한다.
3. 둘 다 구현한다.
사실, 둘 다 구현하는게 바람직하긴 한데, 문제는 그렇게 할 경우에 클래스가 2개 생기므로 이름 공간이 더럽혀지는 결과가 초래되는 점이 아름답지 못하다. row_matrix와 col_matrix가 공존하는 구조를 용납할 수는 없는 법.

그래서 전에 벡터 클래스를 만들 때 SSE 스페셜라이제이션을 템플릿 매개변수로 컨트롤했던 것을 약간 이용하여, 역시 템플릿 매개변수로 major ordering을 결정하는 방법을 취하기로 했다.
enum SPECIALIZE_POLICY
{
    SPECIALIZE_DEFAULT,
    SPECIALIZE_SSE,
};

enum MAJOR_ORDER
{
    ROW_MAJOR,
    COLUMN_MAJOR,
};

// OpenGL용 열우선 배열을 기본으로 구현
template <class scalar_t, MAJOR_ORDER order = COLUMN_MAJOR, SPECIALIZE_POLICY policy = SPECIALIZE_DEFAULT >
struct matrix4t
{
    enum { MAJOR_VECTOR = order };
    typedef scalar_t                    scalar_type;
    typedef vector4t<scalar_t, policy>  vector_type;
    typedef vector3t<scalar_t, policy>  coord_type;
    union
    {
        vector_type v[4];
        scalar_type s[16];
    } m;
public:
    matrix4t() {}
    matrix4t(   scalar_type _11, scalar_type _12, scalar_type _13, scalar_type _14,
                scalar_type _21, scalar_type _22, scalar_type _23, scalar_type _24,
                scalar_type _31, scalar_type _32, scalar_type _33, scalar_type _34,
                scalar_type _41, scalar_type _42, scalar_type _43, scalar_type _44 )
                :m.v[0](_11,_21,_31,_41),
                 m.v[1](_12,_22,_32,_42),
                 m.v[2](_13,_23,_33,_43),
                 m.v[3](_14,_24,_34,_44)
    {
    }
    template <class scalar>
    explicit matrix4t( const scalar* a )
        :m.v[0](a[0], a[4], a[8], a[12] ),
         m.v[1](a[1], a[5], a[9], a[13] ),
         m.v[2](a[2], a[6], a[10],a[14]),
         m.v[3](a[3], a[7], a[11],a[15])
    {
    }
};

// Direct3D용 행 우선 배열을 위한 부분 스페셜라이제이션
template < class scalar_t, SPECIALIZE_POLICY policy >
struct matrix4t< scalar_t, ROW_MAJOR, policy >
{
    enum { MAJOR_VECTOR = ROW_MAJOR };
    typedef scalar_t                    scalar_type;
    typedef vector4t<scalar_t, policy>  vector_type;
    typedef vector3t<scalar_t, policy>  coord_type;
    union
    {
        vector_type v[4];
        scalar_type s[16];
    } m;

   
    static const matrix4t zero;
    static const matrix4t identity;
public:
    matrix4t() {}

    matrix4t(   scalar_type _11, scalar_type _12, scalar_type _13, scalar_type _14,
                scalar_type _21, scalar_type _22, scalar_type _23, scalar_type _24,
                scalar_type _31, scalar_type _32, scalar_type _33, scalar_type _34,
                scalar_type _41, scalar_type _42, scalar_type _43, scalar_type _44 )
                :m.v[0](_11,_12,_13,_14),
                 m.v[1](_21,_22,_23,_24),
                 m.v[2](_31,_32,_33,_34),
                 m.v[3](_41,_42,_43,_44)
    {
    }

    template <class scalar>
    explicit matrix4t( const scalar* a )
        :m.v[0](a[0], a[1], a[2], a[3] ),
         m.v[1](a[4], a[5], a[6], a[7] ),
         m.v[2](a[8], a[9], a[10],a[11]),
         m.v[3](a[12],a[13],a[14],a[15])
    {
    }
};

// SSE 템플릿 매개변수는 명시적으로 스페셜라이제이션할 경우에만 허용하도록 막아둠
template< class scalar_t, MAJOR_ORDER order >
struct matrix4t< scalar_t, order, SPECIALIZE_SSE >
{
    scalar_t error[0]; // SSE spcecialization is not allowed unless explicit
};

쓸 때는 어떤 오더링을 쓸 것인지와 SSE 스페셜라이제이션을 쓸 것인지를 명시하면 된다.

matrix4t<float, COLUMN_MAJOR, SPECIALIZE_DEFAULT >

물론, 위와 같이 쓰는 것은 불편하므로, OpenGL 모듈에서는 다음과 같이 타입으로 선언하는 것이 편리.

[render_ogl.h]
typedef matrix4t<float, COLUMN_MAJOR, SPECIALIZE_DEFAULT > matrix4;

물론, Direct3D모듈에서도 비슷하게 타입으로 선언할 수 있다.

[render_d3d.h]
typedef matrix4t<float, ROW_MAJOR, SPECIALIZE_DEFAULT > matrix4;

구현은 두개를 다 해야 하니 빡세도, 쓸때는 골라쓰는 재미가 있을듯; ice 프로젝트의 철학은 '클라이언트 측에서 엔진의 구성요소를 고를 수 있게 한다'는 것이므로.

Posted by uhm
dev.log2008. 2. 5. 10:50

난 원래 로우레벨 최적화는 별로 좋아하지 않는다 -_-

로우레벨 최적화를 하면 코드를 알아보기 힘들어지게 되기 때문이고.. 알아볼 수 있게 만든답시고

주석을 덕지덕지 달거나, 혹은 함수 안에서 최적화 레벨에 따라 '알아보기 좋은 코드'와 '최적화한 코드'를 #ifdef 따위로 갈라놓아야 하는 불상사가 생기기도 한다.

그래서 로우레벨 최적화는 컴파일러에게 맡기자;는 사상이었는데, 지난주에 회사 엔진 개발팀에서 받아온 코드를 성능측정해보고 깜짝놀랐다. SSE 내장(intrisic) 함수를 쓴 벡터 정규화 코드가 컴파일러의 SSE2 최적화 코드보다 2배나(!) 빨랐던 것이다.

그래서, SSE 내장함수를 보기 좋게.. 코드에 넣는 방법을 모색해 봤는데, 요구조건은 다음 2가지이다.

  1. 코드가 의도하고자 하는 의미에 대한 명료하고 직관적인 표현이 들어갈 것.
  2. 하드웨어 의존적인 코드를 프로그래밍 단위별로 분할 할 수 있을 것.

 1번 요구조건은 이 얘기이다. 코드에

// (1)
_mm_store_ps( &x, _mm_add_ps( _mm_load_ps(&x), _mm_load_ps(&v.x) ) )

// (2)
x += rhs.x;
y += rhs.y;
z += rhs.z;

(1)과 (2)중에서 어느 쪽이 더 우아한 지는 자명하다. 일단 (1)번은 딱 보기에도 뭔소린지 못 알아먹겠다. -_- (2)번은 뭔 소린지는 중학교만 제대로 나온 사람이라면 다 알아먹을 수 있다. (2)번 완승. 문제는, (2)번이 (1)번만큼만 속도가 나와주면 좋은데, (1)번에 비해서 조홀라 느리다는 거다.

그래서 보통은 다음과 같이 한다. SSE 함수들은 VC에서 제공하는 헤더이므로, _WINDOWS_매크로 같은 걸로 감싸서 VC에서만 컴파일 되도록 만드는 거다.

vector3& operator+= ( const vector3& rhs )
{
#if !defined( _WINDOWS_ )
    x += rhs.x;
    y += rhs.y;
    z += rhs.z;
#else
    _mm_store_ps( &x, _mm_add_ps( _mm_load_ps(&x), _mm_load_ps(&v.x) ) )
#endif
    return *this;
}

 근데 한 함수 안에서 저렇게 코드를 갈라 놓는 건 심히 보기 좋지 못하다. 이건 2번 요구조건과 걸리는데, 함수야말로 가장 기본적인 프로그래밍 단위인데, 그걸 저렇게 내부에서 갈라 놓는건 아름답다고는 할 수 없는 코드이다 -_-

 

그래서 궁여지책으로 생각해 낸 게 템플릿의 명시적 특수화를 이용한 구현이다. 나는 보통 벡터는 템플릿으로 구현해 놓기 때문에, SSE가 빠르게 처리해 줄 수 있는 float 타입의 벡터만 SSE 내장 함수로 특수화하면 되는 문제.

#pragma pack( push, 16 )
template <typename scalar_t> struct vector3
{
    typedef scalar_t scalar_type;
    scalar_type x, y, z;

public:
    vector3() {}
    vector3( scalar_type a, scalar_type b, scalar_type c ) : x(a), y(b), z(c) {}

};

#if defined( _WINDOWS_ )|
#include <xmmintrin.h>

template<> struct vector3<float>
{
    typedef float scalar_type;
    scalar_type x, y, z;
private: scalar_type _; // 128비트 정렬을 위한 숨은 멤버.
                                // 사실 #pragma pack이 있으므로 없어도 된다.
public:
    vector3() {}
    vector3( scalar_type a, scalar_type b, scalar_type c )
    {
        _mm_store_ps( &x, _mm_set_ps( 0, c, b, a ) );
    }
};

#endif
#pragma pack( pop )

뭐 이런 식. 윈도우일때만 SSE 내장 함수를 사용하고, 원래의 직관적 의미도 유지할 수 있는 방안인 거 같다.

Posted by uhm
dev.log2007. 12. 12. 21:37

오늘 받은 코드는 패킷을 다음과 같이 정의해서 쓰고 있다.

struct Packet

{

    unsigned type;

    unsigned size;

};

#define PACKET_DECLARE(_packet,_type)  _packet() \

    { type = _type; size = sizeof(_packet); }

 

struct PacketXXX : public Packet

{

    int BlahBlah;

    PACKET_DECLARE(PacketXXX, IDPKT_XXX)

};

 그런데 난 다음과 같은 방식을 더 선호한다.

template < class P, unsigned T >

struct Packet

{

    unsigned type;

    unsigned size;

    Packet() { type = T; size = sizeof(P) }

};

 

struct PacketXXX

    : public Packet < PacketXXX, IDPKT_XXX >

{

    int BlahBlah;

};

여러모로 템플릿은 매크로의 유용한 대체 수단이 된다. 그런데 뭐가 더 좋은지는 각자의 선호도에 달린 문제니까, 뭐라 평가는 못하겠지만, 대안이 있음을 알고 있다는 것은 더 좋은 일임에 분명하다.

 

Posted by uhm
dev.log2005. 1. 13. 18:46

[먕] //네트웍 패킷 파싱.
  int header=packet.header;
  Message * pMsg;
  switch(header)
  {
  case HELLO_MSG:
    pMsg=new HelloMsg();
    break;
  case CHAT_MSG: 
    pMsg=new ChatMsg();
    break;
  }
[먕] 잠시
[먕] 위의 예를 보자고
[먕] HelloMsg,ChatMsg 는 모두 Msg 를 상속 받았다고 하고
[먕] 네트웍 패킷을 위처럼 파싱하고 있는데
(엄) Message 겠지;
(엄) 여튼.
[먕] 저런 switch 문을 쓰고 싶지 않고.
[먕] 해당 키 값에 따라 동적으로 클래스 인스턴스를 생성하고 싶으면
[먕] 어케 해야 되나?
(엄) 어차피 어딘가에는 스위치나. 테이블이 들어 가야 해 -_-
(엄) 뭐 그럴때 쓰는게
(엄) 프로토타입 패턴이긴 한데
(엄) 어차피 스위치나 테이블 둘중의 하나가
(엄) 코드에 포함되는건 피할 수 없지 ㅡ_-
(엄) 나도 저 문제로 열라 궁리 많이 했거덩 -_-
[먕] 음...
[먕] c로는 불가능 한거군.
[먕] java 에서는 저런 switch 문 없이
[먕] 동적으로 생성자 맵핑이 되거덩
(엄) 어뜨케?
[먕] 해쉬테이블이 구조체가 하나있고
[먕] hash.registerDecoder(HELLO_MSG,HelloMsg.class);
[먕] hash.registerDecoder(HCHAT_MSG,ChatMsg.class);
(엄) 그게
(엄) 프로토 타입이자너 -_-
[먕] 패턴이름은 모르겠고.
(엄) 뭐. 그렇게 하려면.
(엄) Message 클래스에
(엄) Message* Message::clone()
(엄) 등의 메소드를 선언하고.
(엄) Message* ChatMsg::clone() { return new ChatMsg( *this ); }
(엄) Message* HelloMsg::clone() { return new HelloMsg( *this ); }
(엄) 그리고 각 객체의 프로토 타입을 해쉬테이블에 등록하면 되지
[먕] 흑... 그건 아냐..
[먕] 잠시.
(엄) 어
[먕] 그럼 위에 처럼 할려면
[먕] 메시지 디코더 인스턴스가 하나씩 생성되어 버리는거 아냐?
(엄) 어차피 니가 쓴 자바 코드도
(엄) 해쉬테이블에
(엄) 타입태그와 타입클래스를
(엄) 등록해 놓는거 아냐
[먕] 자바에서는
[먕]
  NetMessageHandlerManager manager=getMessageHandlerManager();
  manager.registerHandler(DefaultChatMessage.makeIdentifier(ChatProtocol.LOGIN_SUCCESS),new UnSupportedMessageHandler());
  manager.registerHandler(DefaultChatMessage.makeIdentifier(ChatProtocol.PLAIN_MESSAGE),new ChatMessageHandler());
  manager.registerHandler(DefaultChatMessage.makeIdentifier(ChatProtocol.CHAT_MESSAGE_RECEIVE),new C
[먕] 이렇게 해놓으면 타입태그+클래스생성자 들이 등록되는거고
[먕] 위의 프로토타입 패턴을 쓰면.
[먕] 타입태그+클래스 인스턴스가 등록되고
(엄) 그르치
[먕] 그나마 깔끔하다.
(엄) 뭐 정 시르면
(엄) 그르니까
(엄) 해당 메시지의 인스턴스가 생기는게 정 시르면
[먕] 시로
(엄) 해당 클래스의
(엄) 오브젝트 팩토리를 등록해 놓든가 -_-
[먕] 잠시 고로케 한번 해볼께
[먕] 근데.
(엄) 어
[먕] 그렇게 하면 마찬가지로
[먕] 타입태그+팩토리 오브젝트가 맵핑되는거 아닌가 -_-
(엄) 그러치
(엄) 어차피.
(엄) 자바 코드도
(엄) Class 클래스의 객체가 등록되는 거자너
(엄) 별로 다를건 없는거 같은데? -_-
[먕] 잠시 비교 들어갔음.;
(엄) 웅
[먕] 근데
[먕] c 에서 팩토리 구현은 어떤 식으로 되는거야 ?
(엄) C에서?
[먕] c++ 이나
(엄) C에서는 좀 많이 돌아가야 되고 -_-
(엄) C++은.. 대개는
class abstract_factory
{
    object* produce() = 0;
};
class chat_factory : public abstract_factory
{
    object* produce() { return new chat_msg(); }
};
(엄) 뭐 이런 식으로 하지
[먕] 아 -_-
(엄) 아
(엄) virtual 빼먹;
class abstract_factory
{
    virtual object* produce() = 0;
};
[먕] 비지니스 오브젝트 생성 없이
[먕] 팩토리 오브젝트 생성으로 대신하는군
(엄) 어
(엄) 내가 그랬자너 -_-
(엄) 어차피 어딘가에서는
(엄) 테이블에 등록되던가 스위치가 들어가던가
(엄) 둘중의 하나가 불가결하다고 -_-
[먕] 절케 하려면
[먕] 또다시 N 개의 팩토리 클래스가 필요하네 -_-
(엄) 어
[먕] 아햏햏
(엄) 뭐, 물론 비즈니스 오브젝트의 불필요한 생성은 막을 수 있고....
(엄) 팩토리 패턴이 가지는 모든 장점도....
(엄) 차후에 적용할 수 있거찌 ㅡ_-
[먕] 아아...자바가 좋아~ =.=
(엄) 자바도 다를바가 없는거 같은데? -_-
(엄) 자바도
(엄) 어차피 클래스 로더가
(엄) 클래스 로딩하면
(엄) Class 클래스의 객체를 만들어 놓자너 -_-
[먕] 오브젝트의 동적타입을 실시간에 알 수 있으므로
[먕] C 보다는 져아~ =.=
(엄) 어. C++도 가능해
(엄) 컴파일할 때 RTTI 켜놓고 컴파일하면
(엄) typeid()던가
(엄) 하는 연산자를 쓸 수 있지;
[먕] RTTI 가 뭐야 ?
[먕] 첨들어 =.=
(엄) runtime type information
[먕] 오오....
[먕] 마이 컸네 C
[먕] =3
(엄) C++98 부터 있던 표준이야 ㅡ_-
[먕] 한번도 본적 없는걸 ㅡ.ㅜ
(엄) http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclang/html/_pluslang_typeid_operator.asp
[먕] 저걸로 컴팔이 하면
[먕] 클래스 필드에 뭔가가 추가 되는거냐
(엄) 당연히 뭔가가 추가 되거찌?
[먕] 그럼 예상치 못한 문제도 생길 수 있겠군 -_-
[먕] 여튼..
[먕] 별게 다있군 오..
(엄) 뭐 그래서 로우-레벨한 걸 좋아하는. 대다수의 C++유저는..
(엄) RTTI잘 안쓰지 ㅡ_-
[먕] 특히 네트웍 플그램 에서도 =.=
(엄) 어
[먕] 니가 말한 팩토리 이용한 방법이
[먕] 가장.. 적합한것 같다
(엄) 내 생각도 -_-
[먕] OKI DOKI
(엄) 수고~
[먕] 어 수고~


 ==================================================================

결국, 가능한 최선의 해결책은 결국엔 하나로 귀결된다고나 할까.

저기서 약간의 첨언을 하자면, 각 팩토리 클래스는 템플릿으로 구현하면 약간의 손품을 덜 수 있다..

template <class T>
class concrete_factory : public abstract_factory
{
public:
    object* produce() { return new T(); }
};

위에서 T가 object에서 상속받기만 한다면, 다음과 같이 팩토리 패턴을 그대로 쓸 수 있다.

hash_table.register( ID_TYPE1, new concrete_factory<type1>() );
hash_table.register( ID_TYPE2, new concrete_factory<type2>() );
hash_table.register( ID_TYPE3, new concrete_factory<type3>() );
hash_table.register( ID_TYPE4, new concrete_factory<type4>() );

N개의 팩토리 클래스 대신, 1개의 팩토리 클래스 템플릿으로 대체가 되는 것이다!
템플릿과 함께하는 C++에 축복을! >_<


Posted by uhm