ATTENTION READERS! Lucky's VB Gaming Site is no longer active. For updated game programming information and tutorials, please visit The Game Programming Wiki!
QueryPerformanceCounter
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 Modeling?
It might then be possible for users to obtain frame rates far in excess of 40fps!
I get 144fps (which is the highest refresh frequency my monitor can handle) when playing Galaxy :)
This requires
the game loop to cycle approximately every 7ms. 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 :)