Jump to content


Need help with QueryPerformanceCounter and Dual Processors


14 replies to this topic

#1 bignobody

    Valued Member

  • Members
  • PipPipPip
  • 155 posts

Posted 26 April 2006 - 04:21 AM

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.microsof..._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!
-bignobody
notsoftgames.com - Creator of Shlongg!

#2 Reedbeta

    DevMaster Staff

  • Administrators
  • 5311 posts
  • LocationSanta Clara, CA

Posted 26 April 2006 - 06:03 AM

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.
reedbeta.com - developer blog, OpenGL demos, and other projects

#3 .oisyn

    DevMaster Staff

  • Moderators
  • 1842 posts

Posted 26 April 2006 - 12:06 PM

Reedbeta said:

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.
C++ addict
-
Currently working on: the 3D engine for Tomb Raider.

#4 bignobody

    Valued Member

  • Members
  • PipPipPip
  • 155 posts

Posted 27 April 2006 - 01:50 AM

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 said:

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,
-bignobody
notsoftgames.com - Creator of Shlongg!

#5 Reedbeta

    DevMaster Staff

  • Administrators
  • 5311 posts
  • LocationSanta Clara, CA

Posted 27 April 2006 - 01:53 AM

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.
reedbeta.com - developer blog, OpenGL demos, and other projects

#6 bignobody

    Valued Member

  • Members
  • PipPipPip
  • 155 posts

Posted 27 April 2006 - 02:08 AM

Ah! Perfect, thanks! :yes:
-bignobody
notsoftgames.com - Creator of Shlongg!

#7 roel

    Senior Member

  • Members
  • PipPipPipPip
  • 698 posts

Posted 27 April 2006 - 08:43 AM

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.

#8 bignobody

    Valued Member

  • Members
  • PipPipPip
  • 155 posts

Posted 27 April 2006 - 04:37 PM

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,
-bignobody
notsoftgames.com - Creator of Shlongg!

#9 pater

    Valued Member

  • Members
  • PipPipPip
  • 117 posts

Posted 30 April 2006 - 09:03 PM

.oisyn said:

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...

#10 Reedbeta

    DevMaster Staff

  • Administrators
  • 5311 posts
  • LocationSanta Clara, CA

Posted 30 April 2006 - 09:40 PM

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.
reedbeta.com - developer blog, OpenGL demos, and other projects

#11 Nils Pipenbrinck

    Senior Member

  • Members
  • PipPipPipPip
  • 597 posts

Posted 30 April 2006 - 09:53 PM

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.

#12 pater

    Valued Member

  • Members
  • PipPipPip
  • 117 posts

Posted 01 May 2006 - 06:11 AM

Reedbeta said:

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.

#13 juhnu

    Valued Member

  • Members
  • PipPipPip
  • 292 posts

Posted 01 May 2006 - 06:42 AM

http://www.mindcontr.../pc-timers.html

#14 pater

    Valued Member

  • Members
  • PipPipPip
  • 117 posts

Posted 01 May 2006 - 02:40 PM

juhnu said:

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

#15 Reedbeta

    DevMaster Staff

  • Administrators
  • 5311 posts
  • LocationSanta Clara, CA

Posted 01 May 2006 - 03:24 PM

Yeah, that's about the size of it. :)
reedbeta.com - developer blog, OpenGL demos, and other projects





1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users