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

// Get the current time (start time)

ULONGLONG qwStartTime = GetTickCount64();



// Perform complex algorithm here



// Subtract start time from current time to get duration

ULONGLONG 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不具备自动调频的能力。