程式執行時間的測量

pamxy發表於2013-07-09

轉自:http://blog.csdn.net/wzdworld001/article/details/1644111

程式執行時間的測量

 
摘要:本文詳細的討論了在windows平臺中,測量程式執行時間的幾個函式,GetTickCount, QueryPerformanceCounter和RDTSC,並給出示例程式碼。
  演算法的好壞有好多評價指標,其中一個重要的指標是時間複雜度。如果兩個程式完成一個同樣的任務,即功能相同,處理的資料相同,那麼執行時間較短者為優。作業系統和庫函式一般都提供了對時間測量的函式,這麼函式一般都會返回一個代表當前時間的數值,通過在執行某個程式或某段程式碼之前呼叫一次時間函式來得到一個數值,在程式或者程式碼段執行之後再呼叫一次時間函式來得到另一個數值,將後者減去前者即為程式的執行時間。
 在windwos平臺(指windwow95及以後的版本,下同),常用的用於測量時間的函式或方法有三種:1.API函式GetTickCount或C函式clock, 2.API函式QueryPerformanceCounter, 3:彙編指令RDSTC
1.API函式GetTickCount:
函式原形:DWORD GetTickCount(VOID);
該函式取回從電腦開機至現在的毫秒數,即每個時間單位為1毫秒。他的解析度比較低,常用在測量用時較長程式,如果你的程式用時為100毫秒以上,可以使用這個函式.另一個和GetTickCount類似的函式是clock,該函式的回的時間的單位的是CLOCKS_PER_SEC,在windows95/2000作業系統,該值是1000,也就是說,在windows平臺,這兩個函式的功能幾乎完全相同。
2.API函式QueryPerformanceCounter:
函式原形:BOOL QueryPerformanceCounter( LARGE_INTEGER *lpPerformanceCount);該函式取回當前的高分辨值performance計數器,用一個64bit數來表示。如果你的硬體不支援高分辨performance計數器,系統可能返加零。不像GetTickCount,每個計數器單位表示一個固定的時間1毫秒,為了知道程式確切的執行時間,你需要呼叫函式QueryPerformanceFrequency來得到每秒performance計數器的次數,即頻率。
3.彙編指令RDTSC:
RDTSC 指令讀取CPU內部的“時間戳(TimeStamp)",它是一個64位無符號數計數器,這條指令執行完畢後,儲存在EDX:EAX寄存中。該指令從intel奔騰CPU開始引入,一些老式的CPU不支援該指令,奔騰後期的CPU包括AMD的CPU均支援這條指令。和QueryPerformanceCounter類似,要想知道程式的確實的執行時間,必須知道CPU的頻率,即平常所說的CPU的主頻。不幸的是沒有現成的函式可以得到CPU的頻率。一種辦法可行的辦法延時一段指定時間,時間的測量可以用QueryPerformanceCounter來做,在這段時間的開始和結束呼叫RDTSC,得到其時鐘數的差值,然後除以這段時間的的秒數就可以了。
 
下面的程式碼給出使用3個函式封裝和測試程式碼,用RDTSC指令來計時的程式碼參考了一個Ticktest的原始碼,作者不詳。
getTime1,使用GetTickCount返回一個表示當前時間的值,單位秒。
getTime2,和getTime1類似,精度更高。
getTime3,返回一個64bit的一個計數器,欲轉換為秒,需除以CPU頻率。示例程式碼見函式test3.
#include "stdafx.h"
#include "windows.h"
#include "tchar.h"
double getTime1()
{
       DWORD t=GetTickCount();
       return (double)t/1000.00;
}
double getTime2() //使用高精度計時器
{     
       static LARGE_INTEGER s_freq;
       LARGE_INTEGER performanceCount;
       double t;
       if (s_freq.QuadPart==0)
       {
              if ( !QueryPerformanceFrequency( &s_freq))
                     return 0;
       }
      
       QueryPerformanceCounter( &performanceCount );
       t=(double)performanceCount.QuadPart / (double)s_freq.QuadPart;
       return t;
}
void test1()
{
       double t1,t2;
       t1=getTime1();
       Sleep(1000);
       t2=getTime1()-t1;
       printf("It take %.8f second/n",t2);
}
void test2()
{
       double t1,t2;
       t1=getTime2();
       Sleep(1000);
       t2=getTime2()-t1;
       printf("It take %.8f second/n",t2);
}
inline BOOL isNTOS() //檢測是否執行在NT作業系統
{
       typedef BOOL (WINAPI *lpfnGetVersionEx) (LPOSVERSIONINFO);
       static int bIsNT=-1;
       if (bIsNT!=1)
              return (BOOL)bIsNT;
       // Get Kernel handle
       HMODULE hKernel32 = GetModuleHandle(_T("KERNEL32.DLL"));
       if (hKernel32 == NULL)
              return FALSE;
 #ifdef _UNICODE
    lpfnGetVersionEx lpGetVersionEx = (lpfnGetVersionEx) GetProcAddress(hKernel32, _T("GetVersionExW"));
 #else
    lpfnGetVersionEx lpGetVersionEx = (lpfnGetVersionEx) GetProcAddress(hKernel32, _T("GetVersionExA"));
 #endif
 
       if (lpGetVersionEx)
       {
              OSVERSIONINFO osvi;
              memset(&osvi, 0, sizeof(OSVERSIONINFO));
              osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
              if (!lpGetVersionEx(&osvi))
                     bIsNT=FALSE;
              else
                     bIsNT=(osvi.dwPlatformId == VER_PLATFORM_WIN32_NT);
       }
       else
       {
              //Since GetVersionEx is not available we known that
              //we are running on NT 3.1 as all modern versions of NT and
              //any version of Windows 95/98 support GetVersionEx
              bIsNT=TRUE;
       }
       return bIsNT;
}
inline static BOOL checkRDSTC() //檢測CPU是否支援RDSTC指令
{
       static int bHasRDSTC= -1;
       SYSTEM_INFO sys_info;
      
       if ( bHasRDSTC !=-1 )
              return (BOOL)bHasRDSTC;
       GetSystemInfo(&sys_info);
       if (sys_info.dwProcessorType==PROCESSOR_INTEL_PENTIUM)
       {
              try
              {
                     _asm
                     {
                     _emit 0x0f                           ; rdtsc    
                     _emit 0x31
                     }
              }
              catch (...)       // Check to see if the opcode is defined.
              {
                     bHasRDSTC=FALSE; return FALSE;
              }
              // Check to see if the instruction ticks accesses something that changes.
              volatile ULARGE_INTEGER ts1,ts2;
              _asm
              {
                     xor eax,eax
                     _emit 0x0f                                  ; cpuid
                     _emit 0xa2
                     _emit 0x0f                                  ; rdtsc
                     _emit 0x31
                     mov ts1.HighPart,edx
                     mov ts1.LowPart,eax
                     xor eax,eax
                     _emit 0x0f                                  ; cpuid
                     _emit 0xa2
                     _emit 0x0f                                  ; rdtsc
                     _emit 0x31
                     mov ts2.HighPart,edx
                     mov ts2.LowPart,eax
              }
              // If we return true then there's a very good chance it's a real RDTSC instruction!
              if (ts2.HighPart>ts1.HighPart)
                  bHasRDSTC=TRUE;
              else if (ts2.HighPart==ts1.HighPart && ts2.LowPart>ts1.LowPart)
                     bHasRDSTC=TRUE;
              else
              {
                     printf("RDTSC instruction NOT present./n");
                     bHasRDSTC=FALSE;
              }
       }
       else
              bHasRDSTC=FALSE;
       return bHasRDSTC;
}
//***********************************************
void getTime3( LARGE_INTEGER *pTime) //返加當前CPU的內部計數器
{
       if (checkRDSTC())
       {
              volatile ULARGE_INTEGER ts;
       //on NT don't bother disabling interrupts as doing
       //so will generate a priviledge instruction exception
              if (!isNTOS())
                     _asm cli
           //----------------     
        _asm
              {
                     xor eax,eax
            //-------------save rigister
                     push ebx
                     push ecx
                    
                     _emit 0x0f                                  ; cpuid - serialise the processor
                     _emit 0xa2
                    
                     //------------
                     _emit 0x0f                                  ; rdtsc
                     _emit 0x31
                    
                     mov ts.HighPart,edx
                     mov ts.LowPart,eax
                    
                     pop ecx
                     pop ebx
              }
        //-----------------
              if (!isNTOS())
                     _asm      sti
              //---------       
        pTime->QuadPart=ts.QuadPart;
       }
       else
           pTime->QuadPart=0;
}
// maxDetermainTime:最大測定時間,單位毫秒,在首次呼叫該函式時,
// 將花費maxDetermineTime的時間來測定CPU頻率,以後的呼叫將直接返加靜態變數的值
double GetCPUFrequency(DWORD maxDetermineTime )
{
       static double CPU_freq;
       LARGE_INTEGER period,t1,t2;
       register LARGE_INTEGER goal,current;
      
       if (CPU_freq>1000)      //this value have been initilization
              return CPU_freq;
       if (!QueryPerformanceFrequency(&period) || !checkRDSTC())
       {
              CPU_freq=-1.00;
              return CPU_freq;
       }
       QueryPerformanceCounter(&goal);
       goal.QuadPart += period.QuadPart * maxDetermineTime/1000;
       getTime3( &t1);  //開始計時
       do    //延時maxDetermineTime毫秒
       {
              QueryPerformanceCounter(&current);
       } while(current.QuadPart<goal.QuadPart);
       getTime3(&t2);      //結束計時
      
       CPU_freq=double((t2.QuadPart-t1.QuadPart)*1000/maxDetermineTime);
      
       char buff[100];
       sprintf(buff,"Estimated the processor clock frequency =%gHz/n",CPU_freq);
    ::MessageBox(NULL,buff,"",MB_OK);
       return CPU_freq;
}
void test3()
{
       LARGE_INTEGER t,t1,t2;
       double f1,f2;
      
       GetCPUFrequency(100); //花費0.1秒時間計算CPU頻率
       f1=getTime2();
       getTime3(&t1);
       Sleep(1000);
       getTime3(&t2);
       f2=getTime2();
       t.QuadPart=t2.QuadPart-t1.QuadPart;
       printf("It take %.8f second by getTime3/n",(double)t.QuadPart/GetCPUFrequency(100));
       printf("It take %.8f second by getTime2/n",f2-f1);
}
int main(int argc, char* argv[])
{
       test1();
       test2();
       test3();
       return 0;
}

相關文章