QueryPerformanceCounter and Overflow

I am using QueryPerformanceCounter to fulfill some deadlines in my application. However, after starting for several days, the application ceases to function normally. If I just restart the application, it will start working again. This makes me believe that I have a time code overflow problem.

// Author: Ryan M. Geiss // http://www.geisswerks.com/ryan/FAQS/timing.html class timer { public: timer() { QueryPerformanceFrequency(&freq_); QueryPerformanceCounter(&time_); } void tick(double interval) { LARGE_INTEGER t; QueryPerformanceCounter(&t); if (time_.QuadPart != 0) { int ticks_to_wait = static_cast<int>(static_cast<double>(freq_.QuadPart) * interval); int done = 0; do { QueryPerformanceCounter(&t); int ticks_passed = static_cast<int>(static_cast<__int64>(t.QuadPart) - static_cast<__int64>(time_.QuadPart)); int ticks_left = ticks_to_wait - ticks_passed; if (t.QuadPart < time_.QuadPart) // time wrap done = 1; if (ticks_passed >= ticks_to_wait) done = 1; if (!done) { // if > 0.002s left, do Sleep(1), which will actually sleep some // steady amount, probably 1-2 ms, // and do so in a nice way (cpu meter drops; laptop battery spared). // otherwise, do a few Sleep(0)'s, which just give up the timeslice, // but don't really save cpu or battery, but do pass a tiny // amount of time. if (ticks_left > static_cast<int>((freq_.QuadPart*2)/1000)) Sleep(1); else for (int i = 0; i < 10; ++i) Sleep(0); // causes thread to give up its timeslice } } while (!done); } time_ = t; } private: LARGE_INTEGER freq_; LARGE_INTEGER time_; }; 

My question is, should the code above work deterministically for weeks of continuous operation?

And if not, then where is the problem? I thought the overflow was handled

 if (t.QuadPart < time_.QuadPart) // time wrap done = 1; 

But maybe this is not enough?

EDIT: Please note that I did not write the original code, Ryan M. Geiss did, a link to the source code of the code is in the code.

+4
source share
5 answers

QueryPerformanceCounter is known for being unreliable. It is normal to use for individual short intervals if you are prepared to handle abnormal results. It is not accurate - it is usually based on the frequency of the PCI bus, and a heavily loaded bus can lead to loss of ticks.

GetTickCount is actually more stable and can give you 1 ms resolution if you called timeBeginPeriod . This will be completed in the end, so you will need to handle this.

__rdtsc should not be used if you are not profiling and controlling which kernel you are working on and are ready to process a variable processor frequency.

GetSystemTime is suitable for longer measurement periods, but will change stepwise.

Additionally, Sleep(0) does not do what you think it does. This will give the processor if it needs a different context, otherwise it will return immediately.

In short, the time on the windows is a mess. One would think that today it would be possible to get accurate long-term terms from a computer without going through hoops - but this is not so. In our gaming environment, we use several sources of time and patches from the server to ensure that all connected clients have the same game time, and there are many bad hours.

Your best bet would be to simply use GetTickCount or GetSystemTime, wrap it in something that adjusts for jump / wrap time.

In addition, you must convert the double interval to int64 milliseconds , and then use only a whole math - this avoids problems due to the different precision of floating point types depending on their contents.

+13
source

Performance counters are 64-bit, so they are quite large for many years of operation. For example, if you think that the performance counter increases by 2 billion times per second (some kind of imaginary processor 2 GHz), it will overflow after about 290 years.

+5
source

Based on your comment, you should probably use Expected Timers .

See the following examples:

+4
source

Using a nanosecond-scale timer to control something like Sleep (), which is at best accurate to a few milliseconds (and usually a few tens of milliseconds), is somewhat inconsistent.

Another approach that you might consider is to use WaitForSingleObject or a similar function. This burns less CPU cycles, for thirty fewer context switches during the day, and is more reliable than Sleep (0) too.

You could, for example, create a sample and never touch it during normal operation. A semaphore exists only because you can wait for something if you have nothing to wait for. Then you can specify a timeout in milliseconds up to 49 days in length with a single system call. And it will not only be less work, but also much more accurate.

The advantage is that if “something happens”, so you want to break before this, you only need to signal a semaphore. Call waiting will be immediately returned, and you will know from the returned value WAIT_OBJECT_0 that it should have been reported, and not because of the expiration of time. And all this without complicated logic and loop counting.

+2
source

The problem you asked most directly: if (t.QuadPart < time_.QuadPart) should be as follows: if (t.QuadPart - time_.QuadPart < 0)

The reason for this is that you want to search for packaging in relative time, not absolute time. Relative time will wrap time units (1ull <63) after the call to QPC. Absolute time can be completed (1ull <63) units of time after a reboot, but it can turn around at any other time when it looked like it was undefined.

QPC is slightly distorted on some systems (for example, earlier RDTSC-based QPCs on early multi-core processors), so it might be advisable to allow small negative deltas like this: if (t.QuadPart - time_.QuadPart < -1000000) // time binding

The actual wrapper will create very large negative time deltas so that it is safe. This is not necessary for modern systems, but a reliable microsoft is rarely a good idea.

... However, the big problem there with timing is that ticks_to_wait , ticks_passed and ticks_left are int, not LARGE_INT or as long as they should be. This makes the most of this code wrapping if any significant time periods are involved - and “significant” in this context depends on the platform, it can be on the order of 1 second in several cases (rarely these days) or even less on some hypothetical system of the future.

Other problems:

 if (time_.QuadPart != 0) 

Zero is not a special meaning and should not be construed as such. I assume the code combines QPCs by returning a time with zero return QPCs equal to zero. The return value is not a 64-bit time passed by the pointer, it is a BOOL that QPC actually returns.

Also, this Sleep (0) loop is stupid - it seems to be configured to behave correctly only at a certain level of rivalry and a specific processor performance on threads. If you need permission, which is a terrible idea, and if you don't need resolution, then this whole function should have been the only challenge to sleep.

+1
source

Source: https://habr.com/ru/post/1343611/


All Articles