2009년 9월 1일 화요일

윈도우 환경에서 시간측정하는법 - 초정밀 카운터

글은 마이크로소프트웨어 99 2월의 "이보다 더 정확할 순 없다! 윈도우 환경에서 시간측정하는법" 이란 기사에서 참고한 것입니다. ( 저작권 걸리나요? )

 

윈도우 상에서 시간을 측정하기 위한 방법에는 몇가지가 있는데, clock() 함수를 사용하거나 WM_TIMER 메시지를 사용한다거나, 일반 타이머보다 더 높은 정확도를 위해 멀티미디어 타이머를 사용할 수 있다.

WM_TIMER의 경우는 초당 클럭수가 18.3 이므로 1/18.3 , 55 ms 정도의 정확도를 가진다. 그리고 멀티미디어 타이머의 경우는 최소시간 간격이 1ms 이고, 10ms 이내의 이벤트 지연시간을 가지는 타이머 이벤트는 CPU 자원을 많이 소모하기 때문에 주의해야 한다.

앞서 설명한 멀티미디어 타이머는 최소 시간간격이 1ms, 그 이하의 시간을 측정하는데는 적합하지 않다. 더구나 CPU의 성능이 높아지면서 1ms는 무척 긴 시간이 돼 버렸다. 1ms 동안 많은 명령을 수행할 수 있기 때문에 네트웍 패킷의 전송시간이나 특정 루틴의 시간을 측정하기 위해서는 멀티미디어 타이머는 도움이 되지 않는다. 이러한 경우에 Win32 API에서 제공하는 QueryPerformanceFrequency QueryPerformanceCounter , 이 두 개의 함수가 유용하게 쓰인다.

 QueryPerformanceFrequency 1초 동안 카운터가 증가하는 값을 얻어내는데, 시스템이 이 두 함수들을 지원하지 않으면 QueryPerformanceFrequency 의 값이 0 이 되고 결과값도 0 이 돌아온다.

다음 코드는 VC++에서 카운터를 사용한 예이다.

//-----------------------------------------------------------

#include "stdafx.h"  // pre-compiled header file for VC++

#include "windows.h"

#include "stdio.h"

 

void main()

{

        LARGE_INTEGER freq, start, end;

        LARGE_INTEGER tmp;

 

        if(QueryPerformanceFrequency(&freq)) {

                QueryPerformanceCounter(&start);

                printf("Performance Frequency is %I64d\n", freq);

                QueryPerformanceCounter(&end);

                printf("Performance Resolution is %4.3f micro sec.\n",

                        1.0/freq.QuadPart * 1000000);

                printf("printf elapsed time is %4.3f micro sec.\n",

                        (double)(end.QuadPart-start.QuadPart)/freq.QuadPart*1000000);

 

                QueryPerformanceCounter(&start);

                        QueryPerformanceCounter(&tmp); // 1

                        QueryPerformanceCounter(&tmp); // 2

                        QueryPerformanceCounter(&tmp); // 3

                        QueryPerformanceCounter(&tmp); // 4

                        QueryPerformanceCounter(&tmp); // 5

                        QueryPerformanceCounter(&tmp); // 6

                        QueryPerformanceCounter(&tmp); // 7

                        QueryPerformanceCounter(&tmp); // 8

                        QueryPerformanceCounter(&tmp); // 9

                        QueryPerformanceCounter(&tmp); // 10

                QueryPerformanceCounter(&end);

 

                printf("QueryPerformance delay time is %4.3f micro sec.\n",

                        (double)(end.QuadPart-start.QuadPart)/freq.QuadPart*100000); //  /10

        } else

                printf("This computer don't support QueryPerformance function sets !!\n");

}

//--------------------------------------------------------

 Pentium II-266 , Windows 2000 professional 에서의 결과는 다음과 같다.

Performance Frequency is 3579545

Performance Resolution is 0.279 micro sec.

printf elapsed time is 801.499 micro sec.

QueryPerformance delay time is 3.771 micro sec.

 

위 코드에서 LARGE_INTEGER 에 대한 MSDN의 설명은 다음과 같다.

The LARGE_INTEGER structure is used to represent a 64-bit signed integer value.

typedef union _LARGE_INTEGER {

    struct {

        DWORD LowPart;

        LONG  HighPart;

    };

    LONGLONG QuadPart;

} LARGE_INTEGER;

 

Members

LowPart

Specifies the low-order 32 bits.

HighPart

Specifies the high-order 32 bits.

QuadPart

        Specifies a 64-bit signed integer.

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

앞에서 설명한 API 보다 좀 더 정확한 방법으로 수행 시간을 측정하기 바란다면 한 가지 방법이 있다. 이 방법은 Win32 API를 이용한 것이 아니라 어셈블리를 이용한 방법으로 인텔 펜티엄 이상에서만 수행시킬 수 있는 방법이다. 펜티엄에서 추가된 명령중에 RDTSC(Read from Time Stamp Counter) 라는 것이 있다. 펜티엄은 내부적으로 TSC(Time Stamp Counter)라는 64비트 카운터를 가지고 있는데, 이 카운터의 값은 클럭 사이클마다 증가한다. RDTSC 명령은 내부 TSC 카운터의 값을 EDX EAX 레지스터에 복사하는 명령이다. 이 명령은 6에서 11 클럭을 소요한다.

다음의 rdtscEx 명령은 36 클럭을 소요하며 측정구간을 클럭단위로 측정할 수 있는 강력한 시간 측정 방법이다. 하지만 이 방법은 클럭 수만 측정할 뿐 시간을 알 수는 없다. 정확한 시간을 알려면 시스템의 CPU 클럭을 알아야 하며 측정한 클럭값을 CPU 클럭으로 나누어야 시간이 나온다.

 RDTSC 명령을 수행할 때 CPU가 수행속도 향상을 위해서 CPU 명령순서가 바뀔 수 있기 때문에 CPUID 명령을 전에 수행해 명령순서를 맞춰야 하는 경우도 있다.

//------------------------------------------------------

#include "stdafx.h"  // pre-compiled header file for VC++

#include <windows.h>

#include <stdio.h>

 

#define rdtsc(x) \

{ __asm __emit 0fh __asm __emit 031h __asm mov x, eax}

#define rdtscEx(low, high) \

{ __asm __emit 0fh __asm __emit 031h __asm mov low, eax _asm mov high, edx}

#define cpuid {__asm __emit 0fh __asm __emit 0a2h}

 

int main(void)

{

    LARGE_INTEGER start, end, tmp;

    __int64 freq, diff;

 

    if (QueryPerformanceFrequency((LARGE_INTEGER*)&freq)) {

        rdtscEx(start.LowPart, start.HighPart);

        printf("Performance Frequency is %I64d\n", freq);

        rdtscEx(end.LowPart, end.HighPart);

        printf("Preformance Resolution is %4.3f micro sec.\n",

                        1.0/freq * 1000000);

        diff = *(__int64*)&end - *(__int64*)&start;

        printf("printf elapsed clock cycle is %I64d\n", diff);

        printf("printf elapsed time is %4.3f micro sec.\n",

                        (double)diff/266);  // 클럭에 따라 이 부분이 바뀌어야한다.

 

        rdtscEx(start.LowPart, start.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart); // 1

                rdtscEx(tmp.LowPart, tmp.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart); // 10

        rdtscEx(end.LowPart, end.HighPart);

        printf("rdtscEx elapsed clock clycle is %I64d\n",

                        (*(__int64*)&end - *(__int64*)&start)/10);

    } else

        printf("This computer don't support QueryPerformance function sets!!\n");

}

//----------------------------------------------------

결과는 다음과 같다.

Performance Frequency is 3579545

Preformance Resolution is 0.279 micro sec.

printf elapsed clock cycle is 182771

printf elapsed time is 687.109 micro sec.

rdtscEx elapsed clock clycle is 36

 

지금까지 측정한 시간들은 절대적인 값이라 볼 수는 없다. 실제로 위에서 소개한 두 코드를 가지고 여러번 실행해 보면 그때마다 값이 조금씩 다르게 나오는 것을 볼 수 있는데 그건 윈도우가 멀티 태스킹 환경이기 때문에 다른 영향을 받을 수 있기 때문이다. 그리고 windows 98 windows 2000 에서도 값이 다르게 나올 거라 생각한다. (직접 해보진 못했지만..)

그냥 상대적인 비교만으로 족할 듯 하다..

- the end of this article -

댓글 없음:

댓글 쓰기