dev.log2008. 6. 6. 09:11
요즘은 SSE 수학 클래스 만들기에 열을 올리고 있다. 아직 행렬 클래스는 (노느라) 바뻐서 못만들고는 있지만 벡터 클래스는 요모조모 손봐가면서 테스트중. 전에 쓴 대로, SSE는 확실히 성능향상에 도움은 된다. 그런데 그게 좀 오묘하다. 명령의 순서가 희한하게 걸리면 오히려 속도가 느려진다.

다음은 주어진 연산순서를 각각 1000만번씩 반복했을 때 걸린 시간을 10번씩 측정한 결과이다. (CPU - Core2Duo E6600, Memory - DDR2 PC2-5300 6GB, 32bit application on 64bit Windows)

연산순서 기본구현
SSE구현
cross->length->dot 0.307732
0.307663
0.307266
0.307756
0.308705
0.307553
0.307968
0.307418
0.307018
0.307521
0.173833
0.173594
0.17392
0.17435
0.173717
0.17394
0.173853
0.173623
0.173899
0.173708
cross->dot->length 0.308549
0.307164
0.307153
0.307491
0.307836
0.307346
0.307413
0.308006
0.307355
0.307362
0.366581
0.366742
0.366583
0.367308
0.366557
0.366436
0.36658
0.366613
0.36664
0.366343

첫번째 결과는 당연히 SSE가 빠르겠거니.. 하는 추정에 부합하는데, 두번째 결과를 보면 연산 순서만 바뀌었을 뿐인데 SSE구현쪽의 결과가 훨씬 더 느리다. 반면, 기본 구현은 연산 순서랑 관계없이 거의 일정한 속도를 보장해 준다. 내 생각으로는 SSE명령을 수행 후에 메모리에 연산 결과를 쓰면서 레지스터중 일부를 초기화하고 메모리에 억세스하는 비용이 크기 때문일거 같은데, 어셈코드를 봐도 사실 정확한 건 잘 모르겠다. (공부하자)

들쭉날쭉한 속도를 개선하기 위해 요모조모로 테스트해 보다가 내적의 문제점을 깨달았다. 내적은, SSE로 구현할 때 애로사항이 많다. SSE의 기본은 (사실은 SSE가 아니라 그 근간인 SIMD라고 해야 맞지만) 부동소수 4개를 각기 따로따로 별도의 데이터 패쓰를 거쳐서 처리한다는, 일종의 data parallelism 인데, 내적은 따로따로 처리되어야 하는 데이터 3개, 즉 xyz좌표를 필수적으로 한군데에서 처리하도록 해야 한다. SSE의 설계 이념과 매우 동떨어진 연산이 될 수 밖에 없다. 물론 SSE3에서는 패킹된 데이터4개를 하나로 더해주는 명령이 추가되었지만, 아직 SSE3를 지원하지 않는 CPU가 많이 사용되고 있으므로 적용하기는 약간 무리다. 따라서 지금으로썬 내적의 SSE 구현이 병목이므로 내적을 SSE를 쓰지 않은 일반 구현으로 바꾸는 것이 최선일듯.

SSE로 구현한 벡터클래스의 내적을 일반구현으로 바꿔서 테스트해봤다.다음이 그 결과다.
연산순서 기본구현 SSE구현 
cross->length->dot 0.31281
0.328362
0.326691
0.312755
0.312775
0.325598
0.318856
0.31096
0.325269
0.308923
0.163027
0.1661
0.163232
0.163836
0.163102
0.162626
0.163001
0.169913
0.163181
0.162604
cross->dot->length 0.307485
0.307058
0.306469
0.307107
0.308057
0.306862
0.308106
0.307052
0.307446
0.306855
0.161829
0.161945
0.162188
0.16885
0.163519
0.161771
0.161899
0.162052
0.16204
0.162045
결과가 아주 이쁘장하다. 흡족하다. 저정도면 어느 상황에서도 비교적 빠른 속도를 보장해주는 벡터클래스가 되지 않을까.

그래서 결과적으로 완성된 코드는 다음과 같다.
#if !defined( __VECTOR3_H__ )
#define __VECTOR3_H__

#include <cmath>


#pragma intrinsic( sqrt )
// Internal Combustion Engine
namespace ice
{
template <class scalar_t, bool use_implicit = true > struct vector3_t
{
    typedef scalar_t scalar_type;
    scalar_type x, y, z;

public:
    vector3_t() {}
    vector3_t( scalar_type a, scalar_type b, scalar_type c ) : x(a), y(b), z(c) {}
    vector3_t( const scalar_type* xyz ) : x(xyz[0]), y(xyz[1]), z(xyz[2]) {}

    scalar_type operator[] ( int i ) const { return *(&x + i); }
    scalar_type& operator[] ( int i ) { return *(&x +i); }

    vector3_t& operator+= ( const vector3_t& v )
    {
        x += v.x; y += v.y; z += v.z;
        return *this;
    }
    vector3_t operator+ ( const vector3_t& v ) const
    {
        return vector3( x+v.x, y+v.y, z+v.z );
    }
    vector3_t& operator-= ( const vector3_t& v )
    {
        x -= v.x; y -= v.y; z -= v.z;
        return *this;
    }
    vector3_t operator- ( const vector3_t& v ) const
    {
        return vector3( x-v.x, y-v.y, z-v.z );
    }
    vector3_t& operator*= ( scalar_type s )
    {
        x *= s; y *= s; z *= s;
        return *this;
    }
    vector3_t operator* ( scalar_type s ) const
    {
        return vector3( x*s, y*s, z*s );
    }
    vector3_t& operator/= ( scalar_type s )
    {
        x /= s; y /= s; z /= s;
        return *this;
    }
    vector3_t operator/ ( scalar_type s ) const
    {
        return vector3( x/s, y/s, z/s );
    }

    static scalar_type dot( const vector3_t& v1, const vector3_t& v2 )
    {
        return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
    }
    static vector3_t cross( const vector3_t& v1, const vector3_t& v2 )
    {
        return vector3_t(  v1.y * v2.z - v1.z * v2.y,
                                v1.z * v2.x - v1.x * v2.z,
                                v1.x * v2.y - v1.y * v2.x );
    }

    scalar_type squared() const { return x*x + y*y + z*z; }
    scalar_type length() const { return std::sqrt(squared()); }
    void normalize()
    {
        *this /= length();
    }
    vector3_t unit() const
    {
        return *this/length();
    }
};
}

#if defined( _USE_SSE_ )
#pragma pack( push, 16 )

#include <intrin.h>
#define ALIGN_MMX __declspec(align(16))

namespace ice
{

template<> struct ALIGN_MMX vector3_t<float, false>
{
    typedef float scalar_type;
    scalar_type x, y, z;

    vector3_t() {}
    vector3_t( scalar_type _a, scalar_type _b, scalar_type _c )
    {
        _mm_store_ps( &x, _mm_set_ps( 0, _c, _b, _a ) );
    }
    vector3_t( const scalar_type* xyz )
    {
        _mm_store_ps( &x, _mm_load_ps( xyz ) );
    }
    vector3_t( const vector3_t& v )
    {
        _mm_store_ps( &x, _mm_set_ps( 0, v.z, v.y, v.x ) );
    }

    scalar_type operator[] ( int i ) const { return *(&x + i); }
    scalar_type& operator[] ( int i ) { return *(&x +i); }

    vector3_t& operator+= ( const vector3_t& v )
    {
        _mm_store_ps( &x, _mm_add_ps( _mm_load_ps(&x), _mm_load_ps(&v.x) ) );
        return *this;
    }
    vector3_t operator+ ( const vector3_t& v ) const
    {
        return vector3_t( _mm_add_ps( _mm_load_ps(&x), _mm_load_ps(&v.x) ) );
    }
    vector3_t& operator-= ( const vector3_t& v )
    {
        _mm_store_ps( &x, _mm_sub_ps( _mm_load_ps(&x), _mm_load_ps(&v.x) ) );
        return *this;
    }
    vector3_t operator- ( const vector3_t& v ) const
    {
        return vector3_t( _mm_sub_ps( _mm_load_ps(&x), _mm_load_ps(&v.x) ) );
    }
    vector3_t& operator*= ( scalar_type s )
    {
        _mm_store_ps( &x, _mm_mul_ps( _mm_load_ps(&x), _mm_set1_ps( s ) ) );
        return *this;
    }
    vector3_t operator* ( scalar_type s ) const
    {
        return vector3_t( _mm_mul_ps( _mm_load_ps(&x), _mm_set1_ps( s ) ) );
    }
    vector3_t& operator/= ( scalar_type s )
    {
        _mm_store_ps( &x, _mm_div_ps( _mm_load_ps(&x), _mm_set1_ps( s ) ) );
        return *this;
    }
    vector3_t operator/ ( scalar_type s ) const
    {
        return vector3_t( _mm_div_ps( _mm_load_ps(&x), _mm_set1_ps( s ) ) );
    }

    static scalar_type dot( const vector3_t& v1, const vector3_t& v2 )
    {
        return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
    }
    static vector3_t cross( const vector3_t& v1, const vector3_t& v2 )
    {
        //v1.y * v2.z - v1.z * v2.y
        //v1.z * v2.x - v1.x * v2.z
        //v1.x * v2.y - v1.y * v2.x
        __m128 a = _mm_shuffle_ps( *(__m128*)&v1, *(__m128*)&v1,
_MM_SHUFFLE( 3, 1, 2, 0 ) );
        __m128 c = _mm_shuffle_ps( *(__m128*)&v2, *(__m128*)&v2,
_MM_SHUFFLE( 3, 2, 0, 1 ) );
        __m128 b = _mm_shuffle_ps( *(__m128*)&v1, *(__m128*)&v1,
_MM_SHUFFLE( 3, 2, 0, 1 ) );
        __m128 d = _mm_shuffle_ps( *(__m128*)&v2, *(__m128*)&v2,
_MM_SHUFFLE( 3, 1, 2, 0 ) );
        return vector3_t( _mm_sub_ps( _mm_mul_ps( a, c ), _mm_mul_ps(b,d) ) );
    }

    scalar_type squared() const { return dot(*this,*this); }
    scalar_type length()  const
    {
        return _mm_sqrt_ss( _mm_set_ss( squared() ) ).m128_f32[0];
    }
    void normalize()
    {
        _mm_store_ps( &x, _mm_div_ps( _mm_load_ps(&x),
_mm_sqrt_ps( _mm_set1_ps( squared() ) ) ) );
    }
    vector3_t unit() const
    {
        return vector3_t( _mm_div_ps( _mm_load_ps(&x),
_mm_sqrt_ps( _mm_set1_ps( squared() ) ) ) );
    }

private:
    vector3_t( __m128 v )
    {
        _mm_store_ps( &x, v );
    }
};

typedef vector3_t<float, false>  vector3;
}
#pragma pack ( pop )
#else

namespace ice
{
typedef vector3_t<float>  vector3;
}

#endif // #if defined( _USE_SSE_ )

#endif // #if defined( _VECTOR3_H_ )

Posted by uhm
geek.log2008. 3. 24. 02:57
프로그래머라면 당근 diff 유틸리티 한두개쯤은 손에 익어야함. 그래서..
모기불님 블로그에서 보고삘받아서 해봤습;;


Posted by uhm
geek.log2008. 2. 25. 23:19
엄   : 아주 멋진 아이디어가 떠올랐어
에고 : 무슨?
엄   : 음.. 지하철.. 적자운영에 사람도 많고 비효율적이니까
엄   : 철로를 뜯어내고
엄   : 지하터널에 물을 채워서
엄   : 지하 운하로 쓰는 거지!
에고 : 물을 흘려?

징군 : ㅋㅋ
징군 : 갑문이 꽤 많이 필요하겠군 ㅋㅋ
에고 : 근데..
에고 : 지하철통로에 물채우면..
에고 : 무너진다던데
엄   : 지하 운하 운영에 드는 경비는
엄   : 철로를
엄   : 고철로 팔아서 충당.
엄   : ㅇㅋ?
징군 : ㅋㅋㅋ
에고 : 굿인데
엄   : ㅋㅋㅋ
에고 : 관광용으로도 대박이겠다
징군 : 지하 수로에 대한 이야기는
징군 : 갖가지
징군 : 소설에 등장하는거 같은데
엄   : ㅋㅋㅋ
에고 : 좋은점도 졸라 많구만..
에고 : 부산에서 여객선을 타고 올라오다가..

에고 : 청계천 즈음에서 7호선 수로를 타고 우리집으로 올수도 있고
에고 : 잠실즘에서는 2호선 수로를 타고 댕길수도 있겠군
엄   : 그렇지; ㅋㅋ
엄   : 하수를 지하수로에 흘려보내도
엄   : 스크류로 휘저으면
엄   : 자연정화되므로
엄   : 별도의 하수처리 시설도 필요 없어
에고 : 파리 날리고 있는..
에고 : 한강 수상 택시도
에고 : 이거되면 대박이겠는데
엄   : 음.. 물론 한강에 5개의 철교가 있지만..
엄   : 이정도는 리프트 기술이 충분히 발달되어 있으므로
엄   : 커버가능.
에고 : ㅋㅋ
에고 : 머..
에고 : 대략 철교쪽은..
에고 : 철통하나 밑에 넣고..
에고 : 수로교로 만들면되지
에고 : 공구리 쳐도 되고
엄   : ㅋㅋㅋ

징군 : 블로그에 적어라
징군 : 트랙백 달아주마
에고 : 블로그에 올려

에고 : 대박이얌
Posted by uhm