如何使用QueryPerformanceCounter?

102

最近我决定将我的计时器类从毫秒改为微秒,经过一些研究后,我决定使用QueryPerformanceCounter作为我的最佳选择。(Boost::Posix上的警告可能无法在Win32 API上运行让我有些放弃)。然而,我并不确定该如何实现它。

我的做法是调用任何类似于GetTicks()的函数,并将其分配给Timer的startingTicks变量。然后,为了找到经过的时间量,我只需将函数的返回值从startingTicks中减去,当我重置计时器时,我只需再次调用该函数并将起始点分配给它。不幸的是,从我看到的代码来看,调用QueryPerformanceCounter()并不像简单,而且我不知道该传递什么参数。


2
我将 Ramonster 的代码片段制作成一个库,供大家使用:https://gist.github.com/1153062。 - rogerdpack
3
我们最近更新了QueryPerformanceCounter的文档,并添加了有关正确使用和常见问题解答的额外信息。您可以在这里找到更新后的文档:http://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx - Ed Briggs
只是想提一下__rdtsc,这是QueryPerformanceCounter使用的内容。 - colin lamarre
4个回答

169
#include <windows.h>

double PCFreq = 0.0;
__int64 CounterStart = 0;

void StartCounter()
{
    LARGE_INTEGER li;
    if(!QueryPerformanceFrequency(&li))
    cout << "QueryPerformanceFrequency failed!\n";

    PCFreq = double(li.QuadPart)/1000.0;

    QueryPerformanceCounter(&li);
    CounterStart = li.QuadPart;
}
double GetCounter()
{
    LARGE_INTEGER li;
    QueryPerformanceCounter(&li);
    return double(li.QuadPart-CounterStart)/PCFreq;
}

int main()
{
    StartCounter();
    Sleep(1000);
    cout << GetCounter() <<"\n";
    return 0;
}

这个程序应该输出接近1000的数字(Windows睡眠并不那么精确,但应该接近999)。 StartCounter() 函数记录性能计数器中的滴答数,并将其保存在 CounterStart 变量中。 GetCounter() 函数以双精度形式返回自上次调用 StartCounter() 以来的毫秒数,因此如果 GetCounter() 返回 0.001,则距离上次调用 StartCounter() 大约已经过去了1微秒。
如果您想让定时器使用秒而不是毫秒,请更改
PCFreq = double(li.QuadPart)/1000.0;

为了

PCFreq = double(li.QuadPart);

如果您需要微秒,则可以使用

PCFreq = double(li.QuadPart)/1000000.0;

但实际上,这是为了方便起见,因为它返回一个double类型的值。


5
LARGE_INTEGER是什么? - Anonymous
6
这是一个Windows类型,基本上是一个可移植的64位整数。它的定义取决于目标系统是否支持64位整数。如果系统不支持64位整数,则被定义为2个32位整数:HighPart和LowPart。如果系统支持64位整数,则它是2个32位整数和一个名为QuadPart的64位整数的联合体。 - Ramónster
10
这个答案存在严重缺陷。QueryPerformanceCounter读取一个特定核心的循环计数器寄存器,如果执行线程已被重新调度到另一个核心上,则QueryPerformanceCounter的两个测量将不仅包含经过的时间,而且通常还包括两个核心寄存器之间的固定、大且难以确定的差值。因此,如果您的进程绑定到特定的核心,那么只有按照演示文稿中所呈现的方式才能可靠地工作。 - Tony Delroy
16
@TonyD: MSDN文档指出:在多处理器计算机上,调用哪个处理器应该并不重要。但是,由于基本输入/输出系统(BIOS)或硬件抽象层(HAL)中的错误,您可能会在不同的处理器上获得不同的结果。这段代码没有严重缺陷,但可能受到某些BIOS或HAL的影响。 - Lucas
4
@TonyD: 我刚刚更深入地了解了一下。我在 StartCounter 函数中添加了以下调用:old_mask = SetThreadAffinityMask(GetCurrentThread,1);,并在结尾处设置回来 SetThreadAffinityMask ( GetCurrentThread , old_mask ) ;。我希望这样做可以解决问题。这将防止我的线程被重新调度到除第一个CPU核心以外的任何地方。(显然,这只是针对测试环境的解决方案) - Lucas
显示剩余21条评论

20

我使用这些定义:

/** Use to init the clock */
#define TIMER_INIT \
    LARGE_INTEGER frequency; \
    LARGE_INTEGER t1,t2; \
    double elapsedTime; \
    QueryPerformanceFrequency(&frequency);


/** Use to start the performance timer */
#define TIMER_START QueryPerformanceCounter(&t1);

/** Use to stop the performance timer and output the result to the standard stream. Less verbose than \c TIMER_STOP_VERBOSE */
#define TIMER_STOP \
    QueryPerformanceCounter(&t2); \
    elapsedTime=(float)(t2.QuadPart-t1.QuadPart)/frequency.QuadPart; \
    std::wcout<<elapsedTime<<L" sec"<<endl;

使用方法(括号用于防止重新定义):

TIMER_INIT

{
   TIMER_START
   Sleep(1000);
   TIMER_STOP
}

{
   TIMER_START
   Sleep(1234);
   TIMER_STOP
}

使用示例的输出:

1.00003 sec
1.23407 sec

2
假设您使用的是Windows系统(如果是,请将您的问题标记为Windows!),您可以在此MSDN页面上找到一个简单有用的C++类“HRTimer”的源代码。这个类封装了所需的系统调用以实现非常接近您要求的功能(特别是添加GetTicks()方法很容易,确切地实现了您要求的内容)。
在非Windows平台上,没有QueryPerformanceCounter函数,因此无法直接移植解决方案。但是,如果您像上述的“HRTimer”类一样封装它,那么更改类的实现以使用当前平台确实能够提供的解决方案将会更加容易(也许通过Boost或其他方式)。

1
我想通过一个获取时间的NDIS驱动程序示例来扩展这个问题。众所周知,KeQuerySystemTime(在NdisGetCurrentSystemTime下模拟)在毫秒以上具有低分辨率,并且有一些进程(如网络数据包或其他IRP)可能需要更好的时间戳;
这个示例非常简单:
LONG_INTEGER data, frequency;
LONGLONG diff;
data = KeQueryPerformanceCounter((LARGE_INTEGER *)&frequency)
diff = data.QuadPart / (Frequency.QuadPart/$divisor)

其中除数为10^3或10^6,具体取决于所需分辨率。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接