如何使用查询性能计数器?
Posted
技术标签:
【中文标题】如何使用查询性能计数器?【英文标题】:How to use QueryPerformanceCounter? 【发布时间】:2010-12-16 21:23:49 【问题描述】:我最近决定需要将 Timer 类从使用毫秒更改为微秒,经过一些研究后,我认为 QueryPerformanceCounter 可能是我最安全的选择。 (Boost::Posix
上的警告说它可能不适用于 Win32 API,这让我有点失望)。但是,我不确定如何实现它。
我正在做的是调用我正在使用的任何GetTicks()
esque 函数并将其分配给Timer 的startingTicks
变量。然后要找到经过的时间量,我只需从startingTicks
中减去函数的返回值,当我重置计时器时,我只需再次调用该函数并将startingTicks 分配给它。不幸的是,从我看到的代码来看,它并不像调用 QueryPerformanceCounter()
那样简单,而且我不确定我应该传递什么作为它的参数。
【问题讨论】:
我已将 Ramonster 的代码 sn-ps 放入此处的库中:gist.github.com/1153062 供关注者使用。 我们最近更新了 QueryPerformanceCounter 的文档,并添加了正确使用的附加信息以及常见问题解答。你可以在这里找到更新的文档msdn.microsoft.com/en-us/library/windows/desktop/… 顺便提一下__rdtsc,这是QueryPerformanceCounter使用的。 【参考方案1】:#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 sleep 不是那么准确,但应该是 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;
但实际上它是为了方便,因为它返回一个双精度值。
【讨论】:
究竟,LARGE_INTEGER 是什么? 它是一种 windows 类型,基本上是一个可移植的 64 位整数。它的定义取决于目标系统是否支持 64 位整数。如果系统不支持 64 位整数,那么它被定义为 2 个 32 位整数,一个 HighPart 和一个 LowPart。如果系统确实支持 64 位整数,那么它就是 2 个 32 位整数和一个称为 QuadPart 的 64 位整数之间的联合。 这个答案有严重缺陷。 QueryPerformanceCounter 读取一个内核特定的周期计数器寄存器,如果执行线程已在另一个内核上重新调度,则来自 QueryPerformanceCounter 的两个测量不仅包含经过的时间,而且通常包含两个内核寄存器之间的固定、大且难以精确定位的增量。所以 - 只有当您的流程绑定到特定核心时,这才能可靠地工作。 @TonyD: MSDN documentation 说: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。
@TonyD:我只是稍微研究了一下。我在StartCounter
函数中添加了以下调用:old_mask = SetThreadAffinityMask(GetCurrentThread,1);
,然后在末尾设置回SetThreadAffinityMask ( GetCurrentThread , old_mask ) ;
。我希望这能解决问题。这应该可以防止我的线程被重新安排到除第一个 CPU 内核之外的任何内容。 (这显然只是一个测试环境的解决方案)【参考方案2】:
我使用这些定义:
/** 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
【讨论】:
【参考方案3】:假设您在 Windows 上(如果是这样,您应该这样标记您的问题!),在 this MSDN page 上,您可以找到一个简单、有用的 HRTimer
C++ 类的源代码,该类包装了执行某些操作所需的系统调用非常接近您的要求(很容易向其中添加 GetTicks()
方法,尤其是完全按照您的要求进行操作)。
在非 Windows 平台上,没有 QueryPerformanceCounter 函数,因此该解决方案不能直接移植。但是,如果您确实将其包装在诸如上述HRTimer
之类的类中,则更改该类的实现以使用当前平台确实能够提供的功能会更容易(可能通过Boost 或其他方式!)。
【讨论】:
【参考方案4】:我会用 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,具体取决于所需的分辨率。
【讨论】:
以上是关于如何使用查询性能计数器?的主要内容,如果未能解决你的问题,请参考以下文章
无论如何,为了提高 SQL 查询的性能,以按标签匹配计数查找具有顺序的行
如何提高 SQL Server 查询的性能以选择具有值的行不在子查询中的一次计数