dev.log2009. 1. 28. 23:38
ice 프로젝트를 시작하려다보니, 기초 수학 클래스의 성능을 측정해야만 했다. 그래서, 옛날 R모 게임을 만들던 시절에 만져본 언리얼엔진2의 스탯 수집방식을 좇아서 비슷하게 만들어 보기로 했다. 기본 방식은 수집할 스탯의 이름과 타입을 등록하여 인덱스를 지정한 후 수집할 구역의 시작과 끝을 지정하는 것. 여기에 시간 통계를 위한 API콜은 별도의 레이어로 분리하면 대략적인 틀이 나온다.
class syscore
{
public:
    static int64 clock()
    {
        int64 begin;
        QueryPerformanceCounter( (LARGE_INTEGER*)&begin );
        return begin;
    }
    static int64 clockpersec()
    {
        int64 freq;
        QueryPerformanceCounter( (LARGE_INTEGER*)&freq );
        return freq;
    }
};

class stat_collector
{
public:
    stat_collector() { _freq = (float)syscore::clockpersec(); }
    void flush();
    uint32 enroll( const string& name, uint type );
    void start( uint index );
    void stop( uint index );

    float mean_stat( uint index );
    float inst_stat( uint index );
    float total_stat( uint index );
    int   sample_count( uint index );

    class block_stat
    {
    public:
        block_stat( stat_collector& collector, uint index )
            :_collector( &collector ), _index( index )
        {
            _collector->start( _index );
        }
        ~block_stat()
        {
            _collector->stop( _index );
        }
    private:
        stat_collector* _collector;
        uint            _index;
    };

    enum stat_type
    {
        STAT_TIME,
        STAT_COUNT,
    };

private:

    struct stat_item
    {
        stat_item( const string& name, uint type )
            : _count(0), _name(name), _type(type)
        {}

        uint   _count;
        string _name;
        uint   _type;
    };
    std::vector<sint64> _prv_stats;
    std::vector<sint64> _cur_stats;
    std::vector<stat_item> _stat_info;
    float _freq;
};

void stat_collector::flush()
{
    //_prv_stats = _cur_stats;
}

uint32 stat_collector::enroll( const string& name, uint type )
{
    assert( _prv_stats.size() == _cur_stats.size() );
    assert( _prv_stats.size() == _names.size()  );
    uint32 index = _cur_stats.size();
    _prv_stats.push_back( 0 );
    _cur_stats.push_back( 0 );
    _stat_info.push_back( stat_item( name, type ) );

    return index;
}

void stat_collector::start( uint index )
{
    _prv_stats[index] = _cur_stats[index];

    switch ( _stat_info[index]._type )
    {
    case STAT_TIME:
        _cur_stats[index] -= syscore::clock();;
        break;
    case STAT_COUNT:
        break;
    default:
        assert( "unkown stat type!!!!!!!!!" && 0 );
    };
}

void stat_collector::stop( uint index )
{
    switch ( _stat_info[index]._type )
    {
    case STAT_TIME:
        _cur_stats[index] += syscore::clock();
        break;
    case STAT_COUNT:
        _cur_stats[index]++;
        break;
    default:
        assert( "unkown stat type!!!!!!!!!" && 0 );
    }

    _stat_info[index]._count++;
}

float stat_collector::mean_stat( uint index )
{
    switch ( _stat_info[index]._type )
    {
    case STAT_TIME:
        return _cur_stats[index]/(_freq * _stat_info[index]._count);
    case STAT_COUNT:
        return (float)_cur_stats[index]/_stat_info[index]._count;
    default:
        assert( "unkown stat type!!!!!!!!!" && 0 );
        return 0;
    }
}


float stat_collector::inst_stat( uint index )
{
    switch ( _stat_info[index]._type )
    {
    case STAT_TIME:
        return (_cur_stats[index]-_prv_stats[index])/_freq;
    case STAT_COUNT:
        return (float)_cur_stats[index]-_prv_stats[index];
    default:
        assert( "unkown stat type!!!!!!!!!" && 0 );
        return 0;
    }
}

네이밍 컨벤션은.. C/C++ 표준 위원회의 스타일을 따라가 봤다.
사용법은,
#define TEST_LIST() \
    TEST( l = c.length() )                   \
    TEST( c = vector_t::cross( a, b ) ) \
    TEST( l = vector_t::dot( b, c ) )      \

stat_collector _stats;

template <class vector_t>
void test_run( const char* filename, uint stat_index )
{
    static vector_t a, b, c;

    std::ofstream log( filename, ios::app );
#define TEST( expression ) #expression "\n"
    log << "=======================================" << endl;
    log << TEST_LIST();
    log << "---------------------------------------" << endl;
#undef TEST
    for ( int count = 0; count < 10; ++count )
    {
        vector_t::scalar_type x = (float)rand();
        vector_t::scalar_type y = (float)rand();
        vector_t::scalar_type z = (float)rand();
        volatile vector_t::scalar_type l = 0;
        a = vector_t( x, y, 0 );
        b = vector_t( -y, x, 0 );
        c = vector_t( x, y, z );

        _stats.start( stat_index );
        for ( int i = 0; i < 10000000; ++i )
        {
#define TEST( expression ) expression;
            TEST_LIST();
#undef TEST
        }
        _stats.stop( stat_index );
        log << _stats.inst_stat(stat_index) << std::endl;
    }
    log << "======== " << _stats.mean_stat(stat_index) << " sec/sample ==========" << std::endl;
}

int WINAPI WinMain( HINSTANCE , HINSTANCE, char* , int )
{
    uint imp_stat, exp_stat;
    imp_stat = _stats.enroll( _t("imp"), stat_collector::STAT_TIME );
    exp_stat = _stats.enroll( _t("exp"), stat_collector::STAT_TIME );
    test_run<vector3def>( "log_imp.txt", imp_stat );
    test_run<vector3sse>( "log_exp.txt", exp_stat );

    return 0;
}

이름과 타입을 등록하고, 측정할 구간을 start/end로 지정하면 끝. TEST_LIST는 테스트 대상이 어떤 코드였는지에 대한 로그를 남기기 위한 매크로.

부족한 부분은 나중에 수정하자.

Posted by uhm