타이머 함수

Development/C/C++ 2011. 8. 13. 15:04

출처 : http://testors.net/tt/510

 

타이머 문제로 골몰하다 멀티미디어 라이브러리의 바이너리를 뒤적거리기로 했다. 한참동안 winmm.lib 의 네이티브 코드를 뒤지다가 재미난 것을 한가지 발견. 일단 해당 부분을 뜯어 간단히 인라인 어셈으로 옮겨 보았다.

inline unsigned __int64 getMisteryCount() 

   __asm 
   { 
      mov         edx,dword ptr ds:[7FFE000Ch] 
      mov         eax,dword ptr ds:[7FFE0008h] 
   } 



아무런 문서도 없이 바이너리를 뒤지다 얻어낸 정보인지라 저 주소가 무엇을 의미하는지는 나도 모른다. 구글에도 없는것을 보니 완전한 un-documented feature 같다. 어쨌든 저 주소에는 1/10000 초 단위의 시간이 저장되어 있다. 물론 아래와 같이 10000 을 나누어 milli-second 단위로 변환해서 사용하는것도 가능하다.

inline unsigned int getElapsedMilliSecond() 

   return static_castunsigned int >( getMisteryCount()/10000 ); 



mov 를 두번 호출할 뿐인지라 속도는 경이적이다. 빠르지만 최악의 정밀도를 보여주는 GetTickCount() 보다도 4배정도 빠르다. 아무일도 하지 않고 루프만 돈 noproc() 에 거의 근접한다.



getMisteryCount() 와 getElapsedMilliSecond() 의 속도차이의 이유는 64-bit div 연산에 있다. 32-bit 프로세서에서 __int64 나누기를 하려면 수십개의 인스트럭션이 필요하다.

테스트 결과 getMisteryCount() 는 Win95/98/Me 등에선 동작하지 않는다. XP , 2000, 2003 등 NT 커널 기반 OS 에서만 돌아가는 듯 하다. 어쨌건 퍼포먼스 카운트용으로 이보다 더 좋은 타이머는 없을 듯 하다.

 

 

 

>> 타이머 관련 GPG 링크

 

http://gpgstudy.com/gpgiki/시간%20다루기

 

http://gpgstudy.com/gpgiki/TimeStepRegulator

안정적인 DNS서비스 DNSEver DNS server, DNS service
Posted by 키르히아이스
,

http://www.codedream.net/blog/

 

http://www.codedream.net/bbs/view.php?id=blog&page=2&sn1=&divpage=1&category=2&sn=off&ss=on&sc=off&select_arrange=headnum&desc=asc&no=29

 

코드 실행 시간 측정 방법 정리

- 한양대 바라미 6기 이흔진    

 이번에 알아보는 것은 Visual C++에서 시간을 측정하는 방법입니다. Visual C++에서 시간을 측정하는 방법은 여러 가지가 있는데 . ANSI C의 함수를 이용하는 방법부터 윈도우 API 함수를 이용하는 방법까지 다양합니다. 그리고, 각각의 방법을 사용하기 위해서 함수에 따라 적절히 헤더를 Include 해줘야 하기도 합니다.

 다음은 DEVPIA와 MSDN 을 통해서 찾은 여러 가지 시간 측정하는 방법입니다. 여러 님들이 올리신 글들을 제가 한번 정리해 본 것이구요, 소스를 직접 테스트 해보기도 하고 수정을 하기도 했습니다. 그리고 각각의 방법들은 형태는 다르지만 모두 공통점이 있습니다. 바로 실행 시간을 측정하기 위해서 원하는 루틴의 시작점과 종료점에서 시간 측정과 관련하는 함수를 부른다는 겁니다. 그래서 그 루틴이 시작할 당시에 비해 CPU 클럭이 얼마나 지났는지를 따져서, 즉 그 시작점과 종료점의 클럭의 차이를 이용해서 실행 시간을 구합니다.

 간단한 설명은 여기서 마치고, 직접 한번 살펴보세요. 그리고, 직접 한 번 돌려보시길 바랍니다.
 눈으로만 읽어선 절대로 자기의 것이 될 수 없으니까요..^^;

        순서..
             1. clock() 함수 이용 실행시간 측정.
            
2. timeGetTime()함수 이용 실행시간 측정.
            
3. QueryPerformanceCounter()함수 이용 실행시간 측정.
             4. 매크로를 이용한 실행시간 측정. [밀리세컨트(ms)단위]
             5. Debugging 중에 @CLK를 이용한 실행시간 측정.
             6. 프로파일 기능을 이용한 각 함수들의 실행시간 알아보기.

]--------------------------------------------------------------------
1. clock() 함수 이용 실행시간 측정. [초(s)단위]
--------------------------------------------------------------------

어떤 작업을 수행하는데 걸리는 시간을 알고 싶은 경우는 흔하지만, 방법을 잘 몰라서 그냥 넘어가는 경우가 많습니다. 아래 소개된 방법이 가장 일반적인 방법이며 이보다 나은 방법은 아마 없다고 생각합니다. 그다지 어렵지 않으니 천천히 보세요.

컴퓨터에는 Clock Ticks라는 것이 있습니다. 그리고 운영체제에는 어떤 프로세스가 시작한지 얼마나 지났는지 알려주는 clock() 함수를 가지고 있구요. 이것을 이용하는 방법입니다.

clock_t current_tick = clock();

이 코드로 현재 프로세스가 실행한지 얼마나 지났는지를 Clock Tick단위로 알 수 있습니다. 시간을 알고 싶으시면 이 Clock Tick단위를 초단위로 변환해주면 되죠.

             double current_sec = current_tick / CLOCKS_PER_SEC;

이 코드가 Tick 단위를 초단위로 환산해주는 코드입니다.
CLOCKS_PER_SEC은 time.h화일에 정의되어 있는 상수입니다.
간단히 예제를 만들어 보면,

 #include "stdio.h"
 
#include "time.h"

 void main()
{
  
clock_t before;
   double 
result;
   before  =
clock();

   for ( int i = 0; i <32765; i++ )  printf("%d\n", i );
   result = (double)(clock() - before) / CLOCKS_PER_SEC;

   printf("걸린시간은 %5.2f 입니다.\n", result); 

 간단하게 C 스타일로 예를 들었지만, MFC환경에서도 잘 돌아갑니다.

                                                                  --김현승(Puzzle)님이 올려주신 글입니다.

 

 --------------------------------------------------------------------
         2. timeGetTime() 함수 이용. [밀리세컨드(ms)단위]
--------------------------------------------------------------------

 제가 즐겨 쓰는 방법은 multimedia timer를 사용하는 겁니다.
함수는
timeGetTime()
이구요, 밀리세컨드(ms)단위로 측정가능합니다.
방법은 아주 간단합니다.

  DWORD dwStartTime = timeGetTime();


  // 처리부분

  ...

  // 처리부분 종료

  DWORD dwEndTime = timeGetTime();

  printf("%d ms", dwEndTime-dwStartTime);

 필요한 부분에 추가하시고 쓰면 됩니다.
단, 사용하기 위해서는 사용하는 파일에 Mmsystem.h 를 Include 해주고,
winmm.lib 를 Project=>Setting(ALT+F7) 메뉴의 LINK 텝에서 Object/library modules: 에 추가를 해주어야 합니다..

 --DEVPIA에 올리신 김영훈(zenk)님의 글입니다.      

 

 -----------------------------------------------------------------------------
3. QueryPerformanceCounter() 함수 이용 [ ms단위]
 -----------------------------------------------------------------------------

 다음은 QueryPerformanceFrequency() 와 QueryPerformanceCounter()를 이용한 방법입니다. 밀리 세컨드 단위로 측정 가능하구요, #include <Windows.h> 를 해주어야 합니다. 한가지 단점은 시스템에 따라 얼마나 작은 시간을 잴 수 있는지가 제한된다는 것입니다. 하지만 CPU의 속도에 의해 결정되는 것은 아니므로 조금 느린 시스템이라고 해서 걱정하실 필요는 없습니다. API 의 도움말을 보면 시스템에 따라 CPU 클럭의 레벨까지도 체크가 가능하다는군요. 사용하는 API 함수는 High Performance Timer Functions라고 불리는 QueryPerformanceFrequency()QueryPerformanceCounter() 라는 두 개의 함수입니다.

앞의 놈은 자기 시스템이 최대 어느 정도까지의 timer resolution을 지원하는지를 판별하는데 쓰이구요, 뒤엣 놈은 현재의 카운터를 알아내는 데 사용됩니다. 예를 들어 앞의 함수를 콜한 후 넘겨준 파라미터 값이 10000 으로 되어 있다면 그 시스템은 10000분의 1초 (=> 1/10000) 즉 0.1밀리초까지 판별할 수 있습니다.  

 그리고 어떤 작업을 수행하기전에 QueryPerformanceCounter를 한번실행하고 수행후 다시 콜함으로써 각각 얻어진 카운터값의 차이를 구함으로써 수행시간을 판단할 수 있습니다

//멤버변수나 로컬변수로 변수지정

LARGE_INTEGER Frequency;
LARGE_INTEGER BeginTime;
LARGE_INTEGER Endtime;

//프로그램이나 클래스 시작부분에
QueryPerformanceFrequency( &Frequency );

//사용하고자 하는 부분에 다음 코딩
QueryPerformanceCounter( &BeginTime );

처리함수();

QueryPerformanceCounter( &endtime );
int64 elapsed = Endtime.QuadPart- BeginTime.QuadPart;
double duringtime = (double)elapsed / (double)Frequency.QuadPart;

 

//다음은 변수를 달리 했을 때의 사용법입니다.

__int64 Frequency;
__int64 BeginTime;
__int64 Endtime;

//프로그램이나 클래스 시작부분에
QueryPerformanceFrequency((LARGE_INTEGER *) &Frequency );

//사용하고자 하는 부분에 다음 코딩
QueryPerformanceCounter((LARGE_INTEGER *) &BeginTime );

처리함수();

QueryPerformanceCounter((LARGE_INTEGER *) &endtime );
int64 elapsed = Endtime - BeginTime;
double duringtime = (double)elapsed / (double)Frequency.QuadPart;

  --DEVPIA에 올리신 오희백(ohpwin)님의 글을 수정한 것입니다.      

 -------------------------------------------------------------------------
4. 매크로를 이용한 실행시간 측정. [밀리세컨드(ms)단위]
 -------------------------------------------------------------------------

//다음 두줄을 복사해서 쓰시면 됩니다.
// 이 매크로는 Win32 함수를 이용하여 nano second까지 측정할 수 있습니다.

#define CHECK_TIME_START  __int64 freq, start, end; if (QueryPerformanceFrequency((_LARGE_INTEGER*)&freq))  {QueryPerformanceCounter((_LARGE_INTEGER*)&start);

 // a는 float type  milli second이고 b가 FALSE일때는 에러입니다

#define CHECK_TIME_END(a,b) QueryPerformanceCounter((_LARGE_INTEGER*)&end);  a=(float)((double)(end - start)/freq*1000); b=TRUE;                        } else b=FALSE;

 위의 내용을 조금 바꾸면 나노까지 측정 가능하죠.. 우선은 mili가지 측정됩니다.
그리고 windows.h포함해야 합니다. 매크로로 만들어 놔서 조금은 편하답니다.
이용하는 구체적인 방법은 다음 코드를 참조하세요.

#include <windows.h>
#include <stdio.h>

#define CHECK_TIME_START  __int64 freq, start, end; if (QueryPerformanceFrequency((_LARGE_INTEGER*)&freq))  {QueryPerformanceCounter((_LARGE_INTEGER*)&start);  

// a는 float type  milli second이고 b가 FALSE일때는 에러입니다
#define CHECK_TIME_END(a,b)
  QueryPerformanceCounter((_LARGE_INTEGER*)&end);  a=(float)((double)(end - start)/freq*1000); b=TRUE;                        } else b=FALSE;

void main()
{
      float Time;
     
BOOL err;

      CHECK_TIME_START;

      //....측정하고자 하는 구간..예를들어..printf()같은거
      printf("나는 바보 입니다.");

      CHECK_TIME_END(Time, err);

      if(err) printf("printf() 걸린 시간은.. %8.6f미리세컨입니다.",Time);
}

  --DEVPIA에 올리신 김태연(MonoEye)님의 글을 수정한 것입니다.      

 

--------------------------------------------------------------------
5. Debugging 중에 @CLK를 이용한 실행시간 측정.
--------------------------------------------------------------------

 다음은 실행시간을 측정하는 간단한 Debugging Technic 입니다.
 프로그램을 하다 보면, 특정한 루틴이 과연 얼마나 많은 시간을 차지하는지 알고 싶은 경우가 있습니다. 물론, 많은 경우는 아니지만, 특정한 알고리즘을 만든 경우.. 다른 알고리즘과 처리 시간을 비교해 보고 싶은 경우가 있죠. 이럴 때 시스템 시간을 구해서 비교하는 방법들을 많이 사용하는데요,
 실제로는 그렇게 할 필요가 없죠.. Watch Window 에서 앞에 골뱅이(@) 붙여서 사용하는 레지스트리 값들있죠? 골뱅이 시리즈 중에 하나를 사용하시면 된답니다.

 @CLK

 (CLK == Clock)

위에 있는 @CLK는 말이죠. 현재 프로세스의 Clocking을 나타내줍니다. 그런데 위의 값을 사용자가 임의로 초기화 할 수 있기 때문에, 유용하게 사용할 수가 있는 것이랍니다.

 자.. 예를 들어서..
A 라는 루틴이 있는데, A 가 과연 얼마나 시간이 걸리는지 알고 싶습니다.
그러면 어떻게 @CLK를 사용해야 할까요?

우선 A 에 들어가기 전에 Break Pointer를 걸어두고, @CLK를 초기화 하시면 됩니다.
그리고 A 가 끝나는 부분에 다시 Break Pointer를 걸어두시면 바뀐 @CLK 값을 확인해 보실 수 있겠죠. @CLK 값은 밀리세컨드 단위니까. @CLK/1000,d 라고 하시면 초 단위로 보실 수 있습니다.
그럼 간단한 예제를 통해서 실제 사용방법을 살펴 보고 , 마무리 하겠습니다.

#include "stdafx.h"

int main(int argc, char* argv[])
{
    // Break Pointer 설치하는 방법
   
// Alt+F9 를 눌러서 Break at 란에.. 아래와 같이..
   
// {,"ClockTest.cpp",} .23
   
// {,"ClockTest.cpp",} .39
   
// 입력합니다.
   
// 첫번째 Break pointer
   
// 이 부분에서 'User Breakpointer' 가 call 되면
   
// Watch Window 에 아래의 값을 넣습니다.
   
// ----
   
// @CLK
    //
@CLK=0
    // ----
   
// 위에서 @CLK 는 시스템 Clocking 을 사용하겠다는 말이고,
   
// @CLK=0 는 스스템 Clocking 을 0으로 초기화 하겠다는 말입니다.
   
// 이제 이 상태에서 다시 Go 버튼을 눌러서 프로그램을 실행하면..
    // 두번째 User Breakpointer 가 call 됩니다.

    int i, j, k;
   
for (i = 0 ; i <1000 ; i++)
       
for (j = 0 ; j <1000 ; j++)
           
for (k = 0 ; k <1000 ; k++)
                ;

    // 두번째 Break Pointer
   
// 이 부분에서 'User Breakpointer' 가 call 되면 Watch Window 에 있는
   
// 첫번째 @CLK 값이 초과된 시간을 보여줍니다..
   
// 물론 이 값에는 Debugging 때문에 여러가지 추가 작업들이
   
// 행해져서 정확한 프로그램 실행시간이 아니지만, 여러가지
    //작업을 상대적으로 비교할 수는  있겠죠..

    return 0;
}

 --DEVPIA에 올리신 서우석(seaousak)님의 글입니다.

 ------------------------------------------------------------------------------
6. 프로파일 기능을 이용한 각 함수들의 실행시간 알아보기
 ------------------------------------------------------------------------------

 프로그램을 짜면서 각 함수가 어느 정도의 시간이 걸리는 지 알아보려는 노력들을 많이 합니다. 실제 실행시간일 수도 있고 전체 프로그램에서 어느 정도의 비율로 작용하는 지 알고싶을 때도 있구요...
 
이 때 프로그램의 각 함수별로 실행시간과 전체에서의 비율, 호출 횟수등을 알 수 있는 방법이 있습니다. 이것은 Debug모드에서만 가능합니다… 

 

1. Project – Settings 메뉴를 선택한다
2. Link 탭으로 간다. ‘Enable profiling’을 선택한다.

3. 그러고나면 Build 메뉴의 ‘Profile…’ 메뉴가 활성화가 된다.

4.  반드시 Rebuild 시킨후에 ‘Profile…’ 메뉴를 선택한다. 만일 그렇지 않으면 다음과 같은 메시지박스가 뜬다

5. Rebuild 시킨후 ‘Profile…’ 메뉴를 선택하면 다음과 같은 다이알로그가 뜬다

6. 여기서 ‘Function timing’을 선택하고 ‘OK’를 누르면 프로그램이 실행이 될 것이다.

7. 실행이 끝나고 나면 Output 창에 결과를 보여줄 것이다. 함수별 실행시간과 불려진 횟수 등을 확인할 수 있다.

  
 

이것을 이용하면 프로그램을 짠 후 오랜 시간이 걸리거나 짧으면서도 많이 호출되는 함수를 찾아서 프로그램을 개선시킬 수가 있다. 단, 실행되는 것이 디버그모드이고 이 때는 일반 디버그모드보다 시간이 더 걸린다는 것을 명심해야 합니다. 이 외에도 프로파일에는 여러 가지 기능이 있으니 사용해 보시기 바랍니다.

-DEVPIA에 올리신 이광진(mirjini)님의 글입니다

안정적인 DNS서비스 DNSEver DNS server, DNS service
Posted by 키르히아이스
,

출처 : http://anyflow.egloos.com/776231

 

커널 객체를 이용한 쓰래드 동기화(5) - 뮤텍스

제프리 릭터의 Programming Applications for Microsoft Windows에 담긴 내용입니다.

뮤텍스(Mutex) 커널 객체 : 사용 카운트, 쓰래드 ID, 재귀 카운트(현재 뮤텍스를 소유하는 쓰래드가 몇번 소유하고 있는지를 나타냄)로 구성된다. 쓰래드로 하여금 단일 리소스에 독점적으로(mutual exclusive) 접근할 수 있도록 보장한다. (mutual exclusive를 직역하면 '상호 배제'정도 되는 것같은데, 왜 상호(mutual)라는 말이 붙는지는 몰겠네요.) 그러므로 사용자 모드 객체인 크리티컬 섹션(critical section)과 동일하게 동작하더라.. 하지만 뮤텍스는 커널 객체이므로, 크리티컬 섹션보다 느리다. 이 말은 단일 뮤텍스에 서로 다른 프로세스의 쓰래드가 접근할 수 있고, 리소스를 얻기위한 쓰래드 대기 시간 값을 지정할 수 있다는 뜻이다.
가장 많이 사용되는 커널 객체이기도 하며, 보통 여러 쓰래드에서 접근하는 메모리 블록을 보호하는 데 사용된다.

뮤텍스가 따르는 규칙
  • 쓰래드 ID가 0이면, 뮤텍스는 어떤 쓰래드에도 소유된 것이 아니며, 신호 상태이다.
  • 쓰래드 ID가 0이 아니면, 어떤 쓰래드가 소유하고 있는 것이며, 비신호 상태이다.
  • 다른 커널 객체와는 달리, 일반 규칙에 반하는 것을 허용하는 특별한 코드가 OS 내에 존재한다.

    뮤텍스 vs 크리티컬 섹션
  • 성능    뮤텍스 : 느리다 / 크리티컬 섹션 : 빠르다
  • 프로세스 영역을 넘어 사용 가능한지
    뮤텍스 : 가능 / 크리티컬 섹션 : 불가
  • 임의 시간 대기
    뮤텍스 : WaitForSingleObject(hmtx, dwMilliSeconds); / 크리티컬 섹션 : 불가
  • 다른 커널 객체와 함께 대기 상태가 가능한지
    뮤텍스 : 가능(WaitForMultipleObjects()나 이와 비슷한 함수를 통해) / 크리티컬 섹션:불가
    HANDLE CreateMutex (
        PSECURITY_ATTRIBUTES psa,
        BOOL fInitialOwner,
        PCTSTR pszName );

    fInitialOwner : 초기 상태 설정 값. FALSE가 넘어올 경우, 뮤텍스의 쓰래드 ID와 재귀 카운터가 0으로 설정된다. 이 말은 뮤텍스가 비소유 상태가 되고, 따라서 신호 상태로 된다는 뜻이다. TRUE가 넘어올 경우에는 뮤텍스 쓰래드 ID가 호출자 쓰래드의 ID로 설정되고, 재귀 카운터는 1로 설정된다. 따라서 비신호 상태가 된다.

    HANDLE OpenMutex (
        DWORD fdwAccess,
        BOOL bInheritHandle,
        PCTSTR pszName );

    //뮤텍스의 재귀 카운트를 1만큼 감소시킨다. 재귀 카운터가 0이되면
    //쓰래드 ID도 0으로 설정 되고, 뮤텍스는 신호 상태가 된다.
    BOOL ReleaseMutex ( HANDLE hMutex );
  • 안정적인 DNS서비스 DNSEver DNS server, DNS service
    Posted by 키르히아이스
    ,

    출처 : http://anyflow.egloos.com/776075

     

    커널 객체를 이용한 쓰래드 동기화(4) - 세마포어

    제프리 릭터의 Programming Applications for Microsoft Windows에 담긴 내용입니다.

    세마포어(semaphore) 커널 객체 : 리소스 카운팅에 이용된다. 사용 카운트 외에도 두 개의 부호없는 32비트 값(최대 리소스 카운트, 현재 리소스 카운트)을 갖는다. 최대 리소스 카운트는 세마포어가 제어할 수 있는 최대 리소스 갯수를 나타내고, 현재 리소스 카운트는 현재 사용 가능한 리소스의 갯수를 나타낸다.

    세마포어가 따르는 규칙
  • 현재 리소스 카운트가 0보다 크면, 세마포어는 신호 상태에 있다.
  • 현재 리소스 카운트가 0이면, 세마포어는 비신호 상태에 있다.
  • 시스템은 세마포어의 현재 리소스 카운트가 음수가 되는 것을 허용하지 않는다.
  • 현재 리소스 카운트는 최대 리소스 카운트보다 클 수 없다.
    HANDLE CreateSemaphore (
        PSECURITY_ATTRIBUTE psa,
        LONG lInitialCount,
        LONG lMaximumCount,
        PCTSTR pszName );

    lInitialCount : 현재 가능한 리소스의 갯수. 초기값으로 임의의 수를 정할 수 있다.
    lMaximumCount : 가능한 리소스의 최대 갯수

    HANDLE OpenSemaphore (
        DWORD fdwAccess,
        BOOL bInheritHandle,
        PCTSTR pszName );

    //이 함수는 단지 세마포어의 현재 리소스 카운트에
    //lReleaseCount의 값을 더한다.
    //plPreviousCount더하기 전의 현재 리소스 카운트
    BOOL ReleaseSemaphore (
        HANDLE hsem,
        LONG lReleaseCount,
        PLONG plPreviousCount );
  • 안정적인 DNS서비스 DNSEver DNS server, DNS service
    Posted by 키르히아이스
    ,