Windows via C/C++:线程的执行时间(1)

来源:互联网 发布:java和hadoop 编辑:程序博客网 时间:2024/06/05 18:15

计算线程执行某项任务消耗的时间时,许多开发人员会调用GetTickCount/GetTickCount64编写如下的代码:

// Get the current time (start time)ULONGLONG qwStartTime = GetTickCount64();// Perform complex algorithm here// Subtract start time from current time to get durationULONGLONG dwElapsedTime = GetTickCount64() - qwStartTime; 

这段代码假设当前线程不会被中断。然而在Windows这样的基于优先级的操作系统中,开发人员无法得知线程被调度的准确时间。当线程在执行任务中被中断时,使用上面的方法根本无法获得线程所消耗的时间。我们需要一个可以返回线程消耗的CPU时间(既被调度时间)的函数,幸运的是,在Vista之前的操作系统已经提供了GetThreadTimes做到这一点:

BOOL GetThreadTimes(HANDLE hThread, PFILETIME pftCreationTime,PFILETIME pftExitTime,PFILETIME pftKernelTime,PFILETIME pftUserTime);

GetThreadTime函数会将线程相关的时间信息写入为其传递的PFILETIME参数中,各个参数返回值的含义如下表所示:

参数意义pftCreationTime从1601年1月1号凌晨开始到指定线程被创建时的时间,以100纳秒为单位pftExitTime从1601年1月1号凌晨开始到指定线程结束所花的时间,以100纳秒为单位,假如线程尚未终止,则该值未定义pftKernelTime线程在内核模式下运行所花的时间,以100纳秒为单位pftUserTime线程在用户模式下运行所花的时间,以100纳秒为单位

使用GetThreadTimes可以计算线程所消费的CPU时间,比如下面的代码:

__int64 FileTimeToQuadWord(PFILETIME pft) {  return (Int64ShllMod32(ptf->dwHighDateTime, 32) | ptf->dwLowDateTime);}void PerformLongOperation() {  FILETIME ftKernelTimeStart, ftKernelTimeEnd;  FILETIME ftUserTiimeStart, ftUserTimeEnd;  FILETIME ftDummy;  __int64 qwKernelTimeElapsed, qwUserTimeElapsed, qwTotalTimeElapsed;  // Get starting times  GetThreadTimes(GetCurrentThread(), &ftDummy, &ftDummy, &ftKernelTimeStart, &ftUserTimeStart);  // Perform complex algorithm here  ...  // Get the ending times  GetThreadTimes(GetCurrentThread(), &ftDummy, &ftDummy, &ftKernelTimeEnd, &ftUserTimeEnd);  // Get the elapsed kernel and user times by converting the start  // and the times from FILETIMEs to quad words, and then subtract  // the start times from the end times.  qwKernelTimeElapsed = FileTimeToQuadWord(&ftKernelTimeEnd) -     FileTimeToQuadWord(&ftKernelTimeStart);  qwUserTimeElapsed = FileTimeToQuadWord(&ftUserTimeEnd) -     FileTimeToQuadWord(&ftUserTimeStart);  // Get total time duration by adding the kernel and user times.  qwTotalTimeElapsed = qwKernelTimeElapsed + qwUserTimeElapsed;  }

函数GetProcessTimes用来返回进程中所有线程累计的时间信息:

BOOL GetProcessTimes(  HANDLE hProcess,   PFILETIME pftCreationTime,  PFILETIME pftExitTime,  PFILETIME pftKernelTime,  PFILETIME pftUserTime);

GetProcessTimes的返回值是进程中所有线程(包括已终止线程)的累计信息,比如,pftKernelTime参数返回进程中所有线程在内核模式下执行的所有时间之和。

上面讨论的方法适合Vista及之前的系统,但在Vista中,查询线程CPU时间的方法有些变化。Vista不再依赖间隔约为10~15毫秒的系统内部定时器,而是使用处理器的时间戳计数器(Time Stamp Counter,TSC),该计数器使用64位的值记录系统启动以来的CPU周期数。在目前广泛使用的GHz级处理器上,这种方法显然要比毫秒值精确的多。

当线程被停止调度时,系统会计算当前TSC和线程开始调度时TSC的差值,并将该值累加到线程消耗的CPU周期数中。QueryThreadCycleTime函数和QueryProcessCycleTime函数返回指定线程消耗的CPU周期数或指定进程中所有线程的消耗的CPU周期数之和。此外,可以使用ReadTimeStampCounter返回自上次重置以来系统的TSC值,ReadTimeStampCounter是定义在WinNT.h中的宏,指向C++编译器提供内置函数__rdtsc。

对于精度要求较高的分析,GetThreadTimes可能无法胜任,为此Windows提供了以下两个高精度性能分析函数: 
BOOL QueryPerformanceFrequency(LARGE_INTEGER* pliFrequency); 
BOOL QueryPerformanceCounter(LARGE_INTEGER* pliCount); 
函数QueryPerformanceFrequency返回当前硬件平台的高精度性能计数器(High-resolution Performance Counter,HRPC)的频率,注意该值并不是CPU的主频。假如当前硬件平台不支持HRPC,则函数返回0,否则返回非0值。QueryPerformanceCounter返回当前HRPC的值,若当前硬件平台不支持HRPC,函数返回0,否则返回非0值。要注意这两个函数假设调用者线程不会被抢占,不过大多数高精度的分析是在小段代码块内完成的,因此这一点不用担心。下面是我使用这些函数包装的一个C++类,可以很方便的用来进行时间性能分析:

class CStopwatch {public:  CStopwatch() {     QueryPerformanceFrequency(&m_liPerfFreq);    Start();  };  void Start() {    QueryPerformanceCounter(&m_liPerfStart);  }  __int64 Now() const { // 返回自Start调用以来的毫秒数    LARGE_INTEGER liPerfNow;    QueryPerformanceCounter(&liPerfNow);    return (liPerfNow.QuadPart - liPerfStart.QuadPart)*1000/m_liPerfFreq.QuadPart;  }  __int64 NowInMicro() const { // 返回自Start调用以来的微秒数    LARGE_INTEGER liPerfNow;    QueryPerformanceCounter(&liPerfNow);    return (liPerfNow.QuadPart - liPerfStart.QuadPart)*1000000/m_liPerfFreq.QuadPart;  }private:  LARGE_INTEGER m_liPerfFreq;  // HSPC的频率  LARGE_INTEGER m_liPerfStart;  // HSPC的初始值};

下面是一个使用CStopwatch类的例子:

 

CStopwatch stopwatch;// 在此处执行待测试的代码...// 获得代码执行时间__int64 qwElapsedTime = stopwatch.Now();

除了测试代码执行时间,还可以使用上面的函数估算当前计算机系统CPU的主频,代码如下:

DWORD GetCpuFrequencyInMHz() {  // change the priority to ensure the thread will have more chances  // to be scheduled when Sleep() ends  int currentPriority = GetThreadPriority(GetCurrentThread());  SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);  // Keep track of the elapsed time with the other timer  __int64 elapsedTime = 0;  // Create a stopwatch timer which defaults to the current time  __int64 perfCountStart = stopwatch.NowInMicro();  // get the current number of cycles  unsigned __int64 cyclesOnStart = ReadTimeStampCounter();  // wait for about 1 second  Sleep(1000);  // get the number of cycles after about 1 second  unsigned __in64 numberOfCycles = ReadTimeStampCounter() - cyclesOnStart;  // Get how much time has elapsed with greater precision  elpasedTime = stopwatch.NowInMicro() - perfCountStart;  // Restore the thread priority  SetThreadPriority(GetCurrentThread(), currentPriority);  // Compute the frequency in MHz  return (DWORD)(numberOfCycles/elaspedTime);}

上面代码的含义比较清晰,只是要注意用这种方法得到的只是CPU频率的估计值,因为Sleep函数的调用效果大多数情况下不可能精确到指定的1秒,且上述方法假设CPU不具备自动调频的能力。

0 0