Need help with QueryPerformanceCounter and Dual Processors

1959b9a5d7a4ad8b8114fec82f602e85
0
bignobody 101 Apr 26, 2006 at 04:21

Ok, this may get a little long and rambling so I apologize in advance.

In my game I use QueryPerformanceCounter for timing. I’ve got “the timing issue”:

http://support.microsoft.com/kb/327809
*
STATUS
Microsoft has confirmed that this is a problem in programs that make erroneous assumptions about the QueryPerformanceCounter and QueryPerformanceFrequency output.
*
Damn. I hate when I make erroneous assumptions.

  • MORE INFORMATION
    On certain computers, the result returned by QueryPerformanceFrequency will be the clock speed of the CPU. On a computer with a processor running faster than 2.1 GHz, this frequency value requires at least 32 bits of precision. Some programs work with the result of QueryPerformanceFrequency as a signed integer value, which has only 31 bits of precision and a sign flag. These programs behave incorrectly on these faster CPUs. To avoid this problem, programs must use all 64 bits returned from both QueryPerformanceFrequency and QueryPerformanceCounter.
    *

I learned to use QueryPerformanceCounter in a tutorial somewhere, but the heart of it is like this:

LARGE_INTEGER m_liTime;
LONGLONG m_llLastTime; 
LONGLONG m_llTicksPerSec; 
...
// Get current time 
QueryPerformanceCounter(&m_liTime); 
// Calculate the frame delta 
m_fTimeForFrame = (float)(m_liTime.QuadPart - m_llLastTime) / (float)m_llTicksPerSec;   
// Save the time for other calculations 
m_llLastTime = m_llTime.QuadPart; 

So then how do I go about using all 64 bits returned? Aren’t the LARGE_INTEGER and LONGLONG all a bit dodgey to begin with?

Ok, if you’re still with me, we’re off to another URL!

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/directx9_c/Game_Timing_and_Multicore_Processors.asp

  • Recommendations
    Games need accurate timing information, but also need to implement their timing code in a way that avoids the problems associated with RDTSC usage. The following steps should be taken when implementing use of high-resolution timing:
    Use the QueryPerformanceCounter and QueryPerformanceFrequency API instead of the RDTSC instruction. These APIs may make use of RDTSC, but might instead make use of a motherboard timing chip or some other system services that will provide quality high-resolution timing information.
    *

Right. Isn’t this what got me into this problem in the first place?

  • When computing deltas, the values should be clamped to ensure any bugs in the timing values do not cause crashes or unstable time-related computations. The clamp range should be from 0 (to prevent negative delta values)
  • Clamp from zero to infinity and beyond?

  • Finally while the QueryPerformanceCounter / QueryPerformanceFrequency API is intended to be multiprocessor aware, bugs in the BIOS or motherboard drivers may result in these routines returning different values as the thread moves from one processor to another.
  • Ahh, the joys of PC game development…

  • We recommend that all game timing be computed on a single thread, and that thread is set to stay running on a single processor through the SetThreadAffinityMask Windows API. Typically this would be the main game thread.
  • Ok, well I only have the main thread, running in the game’s process. I haven’t been able to find how to get a handle to that thread. Can I use SetProcessAffinityMask instead using the “pseudo-handle” I get from GetCurrentProcess? Shouldn’t that accomplish the same thing?

Even if I can, I have read the msdn entries, but I still can’t seem to figure out how to create the affinity mask itself to say “only use processor 0”.
*
dwProcessAffinityMask
[in] The affinity mask for the threads of the process.
*
Yeah, that’s helpful. Is this like a bit mask? It’s a DWORD. 0x00000000 for processor 0? 0x00000001 for processor 1?

If I call GetProcessAffinityMask

DWORD_PTR pam;
DWORD_PTR sam;
if (GetProcessAffinityMask(hProcess, &pam, &sam))

on my single processor laptop, both pam and sam are have a value of 1.

So is this all I need?

SetProcessAffinityMask(hProcess, sam)

It hasn’t made any difference. But then again, since I don’t have the ability to debug on a dual processor machine, I’m not sure what value is being returned on a multi processor setup.

Can I just do this?

SetProcessAffinityMask(hProcess, 1)

Thanks for any input!

14 Replies

Please log in or register to post a reply.

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 167 Apr 26, 2006 at 06:03

Regarding QueryPerformanceCounter (QPC):

I’m not convinced it’s the best counter to use for game timing. As the article hints at, this counter isn’t guaranteed to be monotonic, even on single-processor systems - it increases unsteadily and sometimes goes backwards! (Actually, what happens is the performance counter runs too fast, getting ahead of real time, and is then corrected by a lower-resolution timer somewhere else in the system). This isn’t what you want to do when measuring frame times for your game - even if you clamp the delta values to zero, you won’t get a smoothly increasing counter, which causes things like player movement and physics to go haywire. Moreover, on laptops the processor speed will actually vary depending on CPU usage - and the value returned by QPF may or may not change to represent this.

So basically QPC/QPF (by themselves) are fubared for use as game timers. Personally, I’ve had the best results with timeGetTime(), even though the resolution is not guaranteed to be even as small as 1 ms. There are also some articles out there discussing more complex time systems that take into account the values of timeGetTime(), QPC, and rdtsc (if available) to generate a sort of “averaged” time.

However, if you still want to try QPC, I would suggest converting LARGE_INTEGER to a floating-point value as follows:

LARGE_INTEGER i;
float f = i.HighPart * 4294967296.0f + i.LowPart;

Obviously you loose some precision this way (64-bit int to 23-bit mantissa), but you don’t need that much precision for your application anyway. In fact, you could probably just throw away the LowPart.

Regarding affinity masks:

SetThreadAffinityMask

“A thread affinity mask is a bit vector in which each bit represents the processors that a thread is allowed to run on. A thread affinity mask must be a subset of the process affinity mask for the containing process of a thread. A thread can only run on the processors its process can run on.”

You can get the thread handle with, of all things, GetCurrentThread :) And, as mentioned in the paragraph I just quoted, you can also set the process affinity mask and that will also act as an affinity mask for all threads.

340bf64ac6abda6e40f7e860279823cb
0
_oisyn 101 Apr 26, 2006 at 12:06

@Reedbeta

and the value returned by QPF may or may not change to represent this.

Even while the MSDN docs say the frequency is guaranteed to be equal from PC startup to shutdown. Go figure.

But I read somewhere timing is becoming more and more of an issue due to the release of multicore CPU’s to the main public so it won’t be long before regular mainboards have high-resolution timing hardware available.

1959b9a5d7a4ad8b8114fec82f602e85
0
bignobody 101 Apr 27, 2006 at 01:50

Thanks for the info, Reedbeta.

Seems the SetProcessAffinityMask worked for my friends home machine (dual processor), but not on his work machine (hyperthreaded).

So in my latest attempt I’ve ditched QueryPerformanceCounter and got back to timeGettime for the cinematic sequences where I’m having the issue.

@Reedbeta

However, if you still want to try QPC, I would suggest converting LARGE_INTEGER to a floating-point value as follows:

LARGE_INTEGER i;
float f = i.HighPart * 4294967296.0f + i.LowPart;

Obviously you loose some precision this way (64-bit int to 23-bit mantissa), but you don’t need that much precision for your application anyway. In fact, you could probably just throw away the LowPart.

Just out of curiosity (and since I’m frequently mathematically inept), can you explain how this magic number works? ( 4294967296.0f )

Damn, I don’t know how I missed GetCurrentThread, as that was just what I was looking for. Sure is easy to get lost in the msdn…

Thanks for the help!

Regards,

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 167 Apr 27, 2006 at 01:53

It’s simply 2\^32. It’s as if you’re bit-shifting the high part left by 32 bits and then adding the low part, except done in floating point.

1959b9a5d7a4ad8b8114fec82f602e85
0
bignobody 101 Apr 27, 2006 at 02:08

Ah! Perfect, thanks! :yes:

6aa952514ff4e5439df1e9e6d337b864
0
roel 101 Apr 27, 2006 at 08:43

it is quite interesting to look at timeGetTime’s code in winmm.dll. I don’t have much time (and correct tools) now, but it looks like it is simply using gettickcount.

1959b9a5d7a4ad8b8114fec82f602e85
0
bignobody 101 Apr 27, 2006 at 16:37

Heh, at this point I don’t care what it’s doing, my problem has been resolved. Now I can finish and release my product!

Regards,

6d318bb67270aa12b325e2cd7b64ff7a
0
pater 101 Apr 30, 2006 at 21:03

@.oisyn

Even while the MSDN docs say the frequency is guaranteed to be equal from PC startup to shutdown. Go figure.

Err… Just a second… You’re basically saying that the MSDN is giving wrong information here? Has anybody effectively observed that the frequency was changing or the timer jumping backwards in time (on a more or less recent system)? Since I’ll need the timing for accurate real-time process automation tasks, I need it to stay at least ever incrementing. For absolute synchronisation, I’ll have a GPS clock available.

Does anybody know wheter SetSystemTimeAdjustment()/GetSystemTimeAdjustment() has anything to do with this? And what do these functions actually do? I’m not really getting what the MSDN says about these…

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 167 Apr 30, 2006 at 21:40

I have definitely observed the QPC value jumping backwards on my laptop (Athlon 64 3200, not cutting edge but certainly recent). And the QPF returned value may well be constant, but the values returned by QPC may be based on the variable-clock-speed processor. So what I’m saying is that QPF doesn’t necessarily give you the correct result with which to convert QPC values to real time.

B91eae75cd6245bd8074bd0c3f1cc495
0
Nils_Pipenbrinck 101 Apr 30, 2006 at 21:53

I shipped numerous titles using QueryPerformanceCounter and never had problems with it.

Using the full 64 bit is important, but I can’t see a reason why one wouldn’t do so.. the QUADPART of LARGE_INTEGER directly maps to __int64, so you can do arithmetic on them them directly.

Btw, doing differences on time and summing them into a float is a bad idea.. That will introduce rounding errors that add up over time. A showstopper for network games.

6d318bb67270aa12b325e2cd7b64ff7a
0
pater 101 May 01, 2006 at 06:11

@Reedbeta

And the QPF returned value may well be constant, but the values returned by QPC may be based on the variable-clock-speed processor. So what I’m saying is that QPF doesn’t necessarily give you the correct result with which to convert QPC values to real time.

So what would you suggest? timeGetTime() is not really an option for me, since having only millisecond resolution would not be as accurate as I desired to be.

6d318bb67270aa12b325e2cd7b64ff7a
0
pater 101 May 01, 2006 at 14:40

@juhnu

http://www.mindcontrol.org/\~hplus/pc-timers.html

So basically, there’s nothing I can do except doing a lot of heuristics :angry:

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 167 May 01, 2006 at 15:24

Yeah, that’s about the size of it. :)