VB:Tutorials:WINAPI:QueryPerformanceCounter

From GDWiki

Jump to: navigation, search

I bet you thought GetTickCount was the bee's-knees when you first stumbled across it, didn't you? Well I've got the bee's-ankles for you my friend! QueryPerformanceCounter makes GetTickCount look like.. like.. something not very fast.

It accesses the CPU's high performance counter which changes its tick value much more frequently than the Windows system timer. This allows us to resolve differences on the order of microseconds (10^-6), rather than milliseconds (10^-3)!

Now, you're probably wondering why we need something so darned fast when a standard 40fps game loop takes 25ms, which is well within the capabilities of GetTickCount. But what if you intend to use Time Based Modelling? It might then be possible for users to obtain frame rates far in excess of 40fps! GetTickCount was not meant to resolve much lower than 10ms differences; you may begin to see jumpiness in your games at such high frame rates if you're using GetTickCount.

Another important application of QueryPerformanceCounter is, as its name implies, performance timing. Try using GetTickCount to tell you how long it takes to make an API call. It can't! Most simple functions are processed in mere microseconds. GetTickCount would detect no change. Starting to see its limitations? If you're testing two routines to determine which is faster, you want the best resolution you can get.

Ok, I think that's enough convincing :) On with the code!

Private Declare Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As Currency) As Long
Private Declare Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As Currency) As Long

You'll need both of these API functions. The first determines the frequency of the system's high performance counter (it changes from system to system!), the second simply returns the current tick count of the timer.

Note that the arguments lpFrequency and lpPerformanceCount are of the type Currency. If you look these API calls up for yourself you'll find that the arguments should be of type LONG_INTEGER. We'll use a simple workaround (explained later) to avoid the hassle.

So how do we use these calls? Observe:

Dim curFreq as Currency
Dim curStart as Currency
Dim curEnd as Currency
Dim dblResult as Double
 
    QueryPerformanceFrequency curFreq 		'Get the timer frequency
    QueryPerformanceCounter curStart 		'Get the start time

    'Some code to test

    QueryPerformanceCounter curEnd 		'Get the end time
    dblResult = (curEnd - curStart) / curFreq 	'Calculate the duration (in seconds)

First we must call QueryPerformanceFrequency and store the result for later use. Next is the first QueryPerformanceCounter call. This will give us the tick count as it was before our test code was processed. Calling QueryPerformanceCounter gets us the tick count after our test code was processed. All that remains is to calculate the duration of the test. Subtracting the starting tick count from the ending tick count yields the number of ticks that occurred during the test. Dividing this by the frequency converts it to a useable format (seconds). I suggest storing this value in a Double in order to maintain precision.

Now, the above code isn't perfect. It doesn't account for the time it takes to make the QueryPerformanceCounter call itself! Check out this source code for an accurate and fully functional performance profiler.

A few final remarks. We used variables of Currency type because 64bit variables are required by QueryPerformanceCounter and QueryPerformanceFrequency. Using Currency results in the variables being scaled by a factor of 10000 (it's just how VB handles this variable type). When we perform the division however, this factor is removed and we're left with the correct value. No sweat!

Check this out: Not all CPUs have high performance counters! But don't panic, I believe that all processors since the 386 do have high performance counters, so you're safe.. unless you're writing programs to take back in time for some reason :)

Categories