프로그램의 성능을 측정하는 방법. 프로그램이나 알고리즘의 성능을 알아보는 방법중 하나가 수행시간 측정입니다.
HighResolutionTimer 사용하기 ¶
Windows 에서의 수행시간측정 방법.
~cpp BOOL QueryPerformanceFrequency(LARGE_INTEGER* param) BOOL QueryPerformanceCounter(LARGE_INTEGER* param)상기 두 Windows API함수를 사용해서 수행 시간을 측정 할 수 있습니다.
예제 ¶
수행시간 측정용 C++ Class. 수행시간 단위는 Sec 입니다. 단 HighResolutionTimer를 지원하는 프로세서가 필요합니다.
다음은 Binary Search 의 퍼포먼스 측정관련 예제. CTimeEstimate 클래스를 만들어 씁니다.
다음은 Binary Search 의 퍼포먼스 측정관련 예제. CTimeEstimate 클래스를 만들어 씁니다.
~cpp #include <windows.h> #include <time.h> #include <stdio.h> class CTimeEstimate { protected: __int64 m_nStart, m_nEnd, m_nFreq; public: CTimeEstimate () { m_nStart = m_nEnd = m_nFreq = 0; QueryPerformanceFrequency((LARGE_INTEGER*)&m_nFreq); } ~CTimeEstimate () { } void Start () { QueryPerformanceCounter((LARGE_INTEGER*)&m_nStart); } void End () { QueryPerformanceCounter((LARGE_INTEGER*)&m_nEnd); } double Result () { return (double)(m_nEnd - m_nStart)/m_nFreq; } }; void Init (int S[]); int getRandNum (int nBoundary); int BinarySearch (int nBoundary, int S[], int nKey); int main (void) { CTimeEstimate est; int S[30001]; int i, nRandNum, nLocation; int nBoundary = 2000; Init (S); nRandNum = getRandNum (nBoundary); est.Start (); for (i=0;i<1000;i++) { nLocation = BinarySearch (nBoundary, S, nRandNum); } est.End (); // result printf ("random number : %d \n", nRandNum); printf ("location : %d\n", nLocation); printf ("estimated time : %f \n", est.Result ()); return 0; } void Init (int S[]) { for (int i=1;i<30001;i++) { S[i] = i; } } int getRandNum (int nBoundary) { time_t t; t = time (NULL); srand (t); return rand () % nBoundary; } int BinarySearch (int nBoundary, int S[], int nKey) { int nBoundaryLow, nBoundaryHigh, nMiddleKey; nBoundaryLow = 1; nBoundaryHigh = nBoundary; while ((nBoundaryLow <= nBoundaryHigh) && nMiddleKey) { nMiddleKey = (nBoundaryLow + nBoundaryHigh) / 2; if (nKey == S[nMiddleKey]) return nMiddleKey; else if (nKey < S[nMiddleKey]) nBoundaryHigh = nMiddleKey - 1; else nBoundaryLow = nMiddleKey + 1; } return FALSE; }
ftime함수, timeb 구조체의 사용 ¶
비교적 CPU와 OS에 의존적이지 않은 방법으로는 ftime 함수와 timeb 구조체를 사용하는 방법이 있습니다. 밀리세컨드 단위까지 밖에 제공되지 않습니다. sys/timeb.h 헤더에 정의된 내용이 ANSI C 는 아니라고 알고있습니다.
timeb 구조체 ¶
~cpp struct timeb { long time ; /* seconds since 00:00:00, 1/1/70, GMT */ short millitm ; /* fraction of second (in milliseconds) */ short timezone ; /* difference between local time and GMT */ short dstflag ; /* 0 if daylight savings time is not in effect */ };
ftime 함수와 timeb 구조체 사용 예 ¶
~cpp #include <sys/timeb.h> #include <stdio.h> void show_est(struct timeb start, struct timeb end); int main(void) { struct timeb start, end; ftime(&start); sleep(1); ftime(&end); show_est(start,end); } void show_est(struct timeb start, struct timeb end) { int time, millitm; time = (int)(end.time - start.time); millitm = (int)(end.millitm - start.millitm); if (millitm<0) { time --; millitm += 1000; } printf (" %d ms 걸렸습니다.\n",time*1000+millitm); }
RDTSC 의 사용 ¶
마이크로 소프트웨어 1999년 2월호 테크니컬 컬럼에 나온 방법입니다.
펜티엄 이상의 CPU에서 RDTSC(Read from Time Stamp Counter)를 이용하는 방법이 있다. 펜티엄은 내부적으로 TSC(Time Stamp Counter)라는 64비트 카운터를 가지고 있는데 이 카운터의 값은 클럭 사이클마다 증가한다. RDTSC는 내부 TSC카운터의 값을 EDX와 EAX 레지스터에 복사하는 명령이다. 이 명령은 6에서 11클럭을 소요한다. Win32 API의 QueryPerformanceCounter도 이 명령을 이용해 구현한 것으로 추측된다. 인라인 어셈블러를 사용하여 다음과 같이 사용할 수 있다.
펜티엄 이상의 CPU에서 RDTSC(Read from Time Stamp Counter)를 이용하는 방법이 있다. 펜티엄은 내부적으로 TSC(Time Stamp Counter)라는 64비트 카운터를 가지고 있는데 이 카운터의 값은 클럭 사이클마다 증가한다. RDTSC는 내부 TSC카운터의 값을 EDX와 EAX 레지스터에 복사하는 명령이다. 이 명령은 6에서 11클럭을 소요한다. Win32 API의 QueryPerformanceCounter도 이 명령을 이용해 구현한 것으로 추측된다. 인라인 어셈블러를 사용하여 다음과 같이 사용할 수 있다.
~cpp #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}간단하게 32비트 정수로 사용하고자 한다면 RDTSC명령이 카운터에서 가져오는 값 중에서 EAX에 담긴 값만을 가져오는 방법이 있다. 짧은 시간동안 측정한다면 EAX에 담긴 값만 가지고도 클럭을 측정할 수 있다. 64비트를 모두 이용할려면 LARGE_INTEGER 구조체를 이용한다.
~cpp LARGE_INTEGER start, end rdtscEx(start.LowPart, start.EndPart); ..... // 측정 구간 rdtscEx(end.LowPart, start.EndPart); elasped_time = *(__int64*)&end - *(__int64*)&start;rdtscEx명령은 36클럭을 소요하며 측정 구간을 클럭 단위로 측정할 수 있는 강력한 시간 측정 방법이다. 하지만 이 방법은 클럭 수만 측정할 뿐 시간을 알 수는 없다. 정확한 시간을 알려면 시스템의 CPU클럭을 알아야 하며 측정한 클럭값을 CPU클럭으로 나누어야 시간이 나온다. RDTSC명령을 수행할 때 CPU가 수행 속도 향상을 위해서 CPU 명령 순서가 바뀔 수 있기 때문에 CPUID명령을 전에 수행해 명령 순서를 맞춰야 하는 경우도 있다. 자세한 설명은 인텔에서 제공하는 성능 모니터링을 위한 RDTSC 명령 사용법을 참조하기 바란다.
Windows는 Multi-Thread로 동작하지 않습니까? 위 코드를 수행하다가 다른 Thread로 제어가 넘어가게 되면 어떻게 될까요? 아마 다른 Thread의 수행시간까지 덤으로 추가되지 않을까요? 따라서 위에서 작성하신 코드들은 정확한 수행시간을 측정하지 못 할 것 같습니다. 그렇다고 제가 정확한 수행시간 측정을 위한 코드 작성 방법을 알지는 못합니다. -_-;
단, 정확한 수행시간 측정을 위해서라면 전문 Profiling Tool을 이용해 보는 것은 어떨까요? NuMega DPS 같은 제품들은 수행시간 측정을 아주 편하게 할 수 있고 측정 결과도 소스 코드 레벨까지 지원해 줍니다. 마소 부록 CD에서 평가판을 찾을 수 있습니다. 단, 사용하실 때 Development Studio 가 조금 맛이 갈겁니다. 이거 나중에 NuMega DPS 지우시면 정상으로 돌아갑니다. 그럼 이만. -- '96 박성수
p.s. NuMega 제품들이 어떻게 수행시간 측정하는지 아시는 분 글 좀 올려주시죠?
멀티쓰레드로 인해 제어권이 넘어가는 것까지 고려해야 한다면 차라리 도스 같은 싱글테스킹 OS에서 알고리즘 수행시간을 계산하는게 낫지 않을까 하는 생각도 해봅니다. (하지만, 만일 TSR 프로그램 같은 것이 인터럽트 가로챈다면 역시 마찬가지 문제가 발생할듯..) 그리고 단순한 프로그램의 병목부분을 찾기 위한 수행시간 계산이라면 Visual C++ 에 있는 Profiler 를 사용하는 방법도 괜찮을 것 같습니다. 해당 함수들의 수행시간들을 보여주니까요.