如何使用QueryPerformanceCounter?


97

我最近决定将Timer类的使用时间从毫秒更改为微秒,经过研究后,我认为QueryPerformanceCounter可能是我最安全的选择。(关于Boost::Posix它可能无法在Win32 API 上运行的警告让我有点失望)。但是,我不太确定如何实现它。

我正在做的是调用GetTicks()我正在使用的任何esque函数,并将其分配给Timer的startingTicks变量。然后要找到经过的时间,我只需从中减去函数的返回值startingTicks,并在重置计时器时再次调用该函数并为其分配startingTicks。不幸的是,从代码中我已经看到它并不像调用那样简单QueryPerformanceCounter(),而且我不确定应该作为参数传递什么。


2
我已经采用了Ramonster的代码段并将其放入此处的库中:gist.github.com/1153062供关注者使用。
rogerdpack 2011年

3
我们最近更新了QueryPerformanceCounter的文档,并添加了正确使用其他信息以及FAQ的答案。您可以在此处找到更新的文档msdn.microsoft.com/zh-cn/library/windows/desktop/…–
Ed Briggs

就像提到__rdtsc一样,这是QueryPerformanceCounter使用的。
colin lamarre

Answers:


159
#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()上次被调用为double 以来的毫秒数,因此,如果GetCounter()返回0.001,则自StartCounter()调用以来约为1微秒。

如果要让计时器使用秒数,请更改

PCFreq = double(li.QuadPart)/1000.0;

PCFreq = double(li.QuadPart);

或者如果您想要微秒,则使用

PCFreq = double(li.QuadPart)/1000000.0;

但是实际上,这是关于便利性的,因为它返回了两倍。


5
确实是什么LARGE_INTEGER?
匿名

5
这是Windows类型,基本上是一个可移植的64位整数。它的定义取决于目标系统是否支持64位整数。如果系统不支持64位整数,则将其定义为2个32位整数,HighPart和LowPart。如果系统确实支持64位整数,则它是2个32位整数和一个称为QuadPart的64位整数之间的联合。
拉蒙斯特,

8
这个答案有严重的缺陷。QueryPerformanceCounter读取一个特定于内核的周期计数器寄存器,如果执行线程已在另一个内核上重新计划,则来自QueryPerformanceCounter的两次测量不仅包含经过的时间,而且通常包含两个内核寄存器之间固定的,较大的且难以确定的增量。因此-仅当您的进程绑定到特定的核心时,此方法才能像显示的那样可靠地工作。
Tony Delroy 2012年

15
@TonyD:MSDN文档说:On a multiprocessor computer, it should not matter which processor is called. However, you can get different results on different processors due to bugs in the basic input/output system (BIOS) or the hardware abstraction layer (HAL).此代码的缺陷并不严重,但是有些BIOS或HAL。
卢卡斯

3
@TonyD:我只是稍微研究了一下。我在StartCounter函数中添加了以下调用:old_mask = SetThreadAffinityMask(GetCurrentThread,1);然后将其设置回末尾SetThreadAffinityMask ( GetCurrentThread , old_mask ) ;。我希望可以解决问题。这样可以防止我的线程重新安排到第一个CPU内核以外的任何线程。(显然,这只是测试环境的解决方案)
卢卡斯(Lucas

19

我用这些定义:

/** 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(如果需要,则应这样标记您的问题!),在此MSDN页面上,您可以找到一个简单而有用的HRTimerC ++类的源代码,该类包装了所需的系统调用,以非常接近您的要求进行操作(向其中添加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,具体取决于所需的分辨率。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.