现在的位置: 首页 > 综合 > 正文

阶乘之计算从入门到精通-程序运行时间的测量

2013年03月04日 ⁄ 综合 ⁄ 共 5462字 ⁄ 字号 评论关闭

 

摘要:本文详细的讨论了在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(¤t);
	} 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;
}

抱歉!评论已关闭.