2

I'm having trouble with something and couldn't find any answers about it, as I don't even know what to search for. I have a done a timer class using QueryPerformanceCounter, from my application, I launch a second thread object that has its own instanced timer and I just have an infinite loop getting delta time from the timer and using it to output the number of loop iterations per second.

I've noticed that it was giving me weird values so I started printing delta time and found out it was coming as 0 sometimes, so I went inside the method that returns delta time and did some testing. This is my deltaTime() method:

    double MyTimer2::deltaTime()
    {
        LARGE_INTEGER timenow;
        QueryPerformanceCounter(&timenow);
        //std::cout << "timenow=" << (double)timenow.QuadPart << "   currentticks=" << (double)m_currentTicks.QuadPart << std::endl;

        double m_deltaTime = (double)(timenow.QuadPart - m_currentTicks.QuadPart) /* 1000.0*/ / (double)m_frequency.QuadPart;

        m_currentTicks = timenow;

        if(m_deltaTime < 0.000001)
            return 0.0;

        return m_deltaTime;
    }

So, I put a breakpoint on "return 0.0;" and what happens is that it gets there most of the time, which is not correct. However, if I uncomment the printing code and run, I will never stop on the breakpoint. So in theory, my printing code is making it work correctly, whereas if I remove it, things stop working as they should! How is this possible, why is it happening and how can I fix it? I've tried _ReadWriteBarrier() unsuccessfully.

Thanks in advance!

EDIT: I need a high-resolution timer for physics simulation!

  • 9
    Um, the printing code takes time. And from the evidence, it probably takes more than 0.000001 seconds. – Raymond Chen Apr 29 '12 at 15:14
  • Ok another example: I've had this code in an OpenGL application with V-Sync on and printing delta time. It gave me 0.016s once every 4 iterations, the other 3 prints were 0. The correct answer was to print 0.016s all the time, but it was printing 0 most (but not all) of the time... Why is my delta time method returning 0?? I'm subtracting the previous number of ticks with my current number of ticks... are you saying that my code takes 0 CPU ticks to run? – Francisco Inácio Apr 29 '12 at 15:28
  • 1
    The floating point stuff is a little worrying. Typically, the QPC returns are subtracted as 64-bit integers and, with an average ticks/us value derived from a long wall-time period, a reasonable delta time in uS can be calcuated without FP. – Martin James Apr 29 '12 at 15:50
  • Sorry, Martin, I couldn't understand what you said. How would you calculate delta time then? What is uS? nanoseconds? – Francisco Inácio Apr 29 '12 at 15:54
  • Also, .016 seconds, 16ms sometimes, 0, others, is very suspicious. 16ms looks like the sort of time that a thread might be ready if there is one more ready thread than there are cores. – Martin James Apr 29 '12 at 15:57
  • @FranciscoInácio - sorry, 'us' - microseconds, 10^-6 seconds. I should not have used uppercase 'S' for seconds :( – Martin James Apr 29 '12 at 15:58
  • Martin makes a good point. 64-bit integers do not fit in a double. You're going to suffer rounding errors if you convert to double too soon. – Raymond Chen Apr 29 '12 at 16:12
  • @MartinJames how would you do it? – Francisco Inácio Apr 29 '12 at 16:15
  • @RaymondChen QuadPart is a LONGLONG, isn't that smaller than 64? And how can I calculate delta time differently? – Francisco Inácio Apr 29 '12 at 16:18
  • @RaymondChen: The frequency may be stored in a 64-bit integer, but it's nowhere near the threshold (2**53) where `double` starts to lose precision. For a cycle counter, it's about `2**32`, for an HPET somewhere around `2**24`. – Ben Voigt Apr 29 '12 at 16:50
  • timenow.QuadPart can exceed the 53 bit limit. (There is no requirement that it start at zero.) – Raymond Chen Apr 29 '12 at 20:49
  • I don't know. Using FP for this just seems 'off'. The QPC() returns an int64 & so doing as much calculation in int64 first seems just natural. Whenever I've attempted this, I've calculated the frequency in ticks/us using seconds of wall-time and then multiplied by the delta to get us. I only use FP when I have to. – Martin James Apr 30 '12 at 00:54
  • I also don't know how the QPC comes up. I would imagine that it starts at zero upon power up, but I don't know for certain. I would be very surprised if it starts as some random bit pattern but, if it did, the chances are overwhelming that it starts above 2^53. – Martin James Apr 30 '12 at 00:58
  • QPC is ZERO at startup. What is the freqency `m_frequency`? There is no `QueryPerformanceFrequency()` call in your code (global?). Some systems do have a frequency of a very few MHz. Therefore your m_deltaTime may report **ZERO** when two succesive are less then the counter granularity apart form eachother. I'd also suggest to change the type conversion this way: `double m_deltaTime = (double)((timenow.QuadPart - m_currentTicks.QuadPart) / m_frequency.QuadPart);` to reduce rounding errors. _Printing makes it work_ clearly indicates that you're calling (too) fast. – Arno Aug 08 '12 at 11:51
  • The print itself is likely to take about 1 ms when directed to the console. I have written some more details regarding microsecond timing [here](http://stackoverflow.com/questions/2904887/sub-millisecond-precision-timing-in-c-or-c/11474662#11474662), [here](http://stackoverflow.com/questions/5801813/c-usleep-is-obsolete-workarounds-for-windows-mingw/11470617#11470617), and [here](http://stackoverflow.com/questions/3162826/fastest-timing-resolution-system/11474459#11474459). – Arno Aug 08 '12 at 12:04

3 Answers3

3

A couple processor generations ago, QueryPerformanceCounter() would read the CPU's cycle counter (e.g. rdtsc). Using this method, the number of ticks from successive reads would never be zero. The resolution was equal to the CPU clock rate, e.g. 3 GHz.

Modern processors have two characteristics which make the cycle counter useless for timing. First, you have multiple cores, which each have their own cycle counter. Threads can migrate between cores, and if you read the cycle counter from two different cores, the difference would not be related to elapsed time. It could even be negative. Secondly, you have dynamic clocking based on load (both underclocking to save power and overclocking for performance). Intel calls these "SpeedStep" and "Turbo Boost", respectively. When the cycle rate isn't fixed, there's no way to convert from ticks to time.

So, QueryPerformanceCounter now uses a dedicated piece of hardware called a High-Performance Event Counter (HPET), with a resolution of several MHz. Importantly, there's only one regardless of how many cores you have, and it doesn't change speed dynamically. But, since the resolution is lower, it is now possible to read it twice between ticks, in which case you'll get an elapsed time reported as zero.

In practice, this isn't a problem. If you need timing more precise than what the HPET can provide, then a general purpose computer is not suitable for you. Timing in the nanosecond range will be severely affected by interrupts.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • That is very interesting to know, thank you, but how would I fix my timer class then? I want something accurate, that is thread safe and doesn't screw up my code by returning 0 – Francisco Inácio Apr 29 '12 at 16:41
  • @FranciscoInácio: Measure a bigger chunk of code. The timing results on a very small piece of code are worthless, as I mentioned, because of interrupts which can add about a microsecond pause at any time. Instead of trying to get the time of one loop iteration, which will jump around horribly because of interrupts, measure the time of 100 or 1000 iterations and divide. – Ben Voigt Apr 29 '12 at 16:42
  • I still haven't done any actual physics calculations so, maybe after I do so, things won't be messy. – Francisco Inácio Apr 29 '12 at 16:45
1

First of all, your timer is wrong: it consumes your CPU intensively. On the single core machine it will slow down all the system. If you want to create a timer and target Windows, you can use timer functions.

Then, every not negative value, returned by your deltaTime() function is valid. While you hosted not in real-time operating system, every operation can take arbitrary amount of time. One iteration can take about tens cycles of processor ticks, or tens years. No one guarantee.

Third, about experimental results. It seems that if context will be switched once between two consecutive time measurement, you get value about 0.016s, if not, you get value bellow 0.000001s that is floored to 0s.

As it was said, printing to console is relatively heavy operation and you actually always get context switched when you enable it.

EDIT

While QueryPerformanceCounter seems to offer great resolution, it traps you. You will never get actually high resolution timer, unless you work in real-time OS.

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Lol4t0
  • 12,444
  • 4
  • 29
  • 65
  • So how can I do a delta time function that does not return 0 but rather the real delta time? How would I wrap all of that in a timer class? – Francisco Inácio Apr 29 '12 at 16:03
  • 1
    @FranciscoInácio, `0` is valid return value. It means that real delta time is _almost_ `0`. If you want _timer_, use _timer functions_. – Lol4t0 Apr 29 '12 at 16:22
  • Are you saying that if my loop code is more expensive, I will never have deltatime=0? I am already using timer functions, if you look at the link you gave me, QueryPerformance timer functions are there. I need a high-resolution timer to get accurate deltatimes to do physics simulation. – Francisco Inácio Apr 29 '12 at 16:26
  • 1
    @FranciscoInácio: Wall-clock time is pretty useless in a simulation. Normally you simulate a clock also. – Ben Voigt Apr 29 '12 at 16:28
  • @FranciscoInácio, I see only `setTimer` and `killTimer` on the link :) Also see my edit. – Lol4t0 Apr 29 '12 at 16:34
  • @Lol4t0: Do you have any idea how horrible the precision of the timer functions you linked to is? Any idea? Even `timeGetTime` and `GetSystemTimeAsFileTime` are immensely better. He's using the best API available for the job. What you linked to are not timers, they are timed event triggers. – Ben Voigt Apr 29 '12 at 16:39
  • @BenVoigt, Ok, what I actually think about idea of `QueryPerformanceCounter` is under **EDIT**. It results are unreliable. – Lol4t0 Apr 29 '12 at 16:41
  • @BenVoigt, also resolution of functions mentioned is actually reachable. Better resolution is suspicious due to context switching. – Lol4t0 Apr 29 '12 at 16:43
  • @Lol4t0: Detecting context-switching is an important ability of QPC. One just has to remember that it isn't measuring time taken by your code, but by your code + interrupts + maybe context switch. – Ben Voigt Apr 29 '12 at 16:53
  • @BenVoigt, the problem that this hides precision and leads to strange behavior. If you could note, Francisco tells he got results `0s` and `0.016s`, so we can conclude that real precision was `16ms`! – Lol4t0 Apr 29 '12 at 17:00
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/10664/discussion-between-ben-voigt-and-lol4t0) – Ben Voigt Apr 29 '12 at 17:01
1

What could possibly be the purpose of this block?

if(m_deltaTime < 0.000001)
    return 0.0;

It has no value, it simply screws with the results, telling you the time was zero when it actually wasn't.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Thats for testing and m_deltaTime is indeed zero everytime it enters that if statement. That is my problem – Francisco Inácio Apr 29 '12 at 16:09
  • 1
    What if you calculate `long long elapsedticks = timenow.QuadPart - m_currentTicks.QuadPart;` separately? Maybe you're losing precision during the `double` division. – Ben Voigt Apr 29 '12 at 16:15
  • I would then have the delta ticks, I would still need to divide by frequency to get the delta time :S how can I get a proper delta time function? – Francisco Inácio Apr 29 '12 at 16:20
  • 1
    @FranciscoInácio: But you would know if the time is REALLY zero, or if that's just a result of double arithmetic. – Ben Voigt Apr 29 '12 at 16:24
  • I've changed my code to first calculate deltaticks, then "if(deltaticks == 0) deltaticks = 1;" and finally dividing it by frequency. After 7 seconds it stopped on the breakpoint I put on the if statement, so deltaticks was eventually 0. – Francisco Inácio Apr 29 '12 at 16:31