Are Timers and Loops in .Net accurate?
Asked Answered
T

4

2

While developing a program to calculate the frequency and pulse width of the pulse generated by a 555 timer IC, coming to the PC via PC Parallel port. I noticed that every time I run the code it shows different values, so I start testing the Loops and timers for there accuracy. I have run the following code and come to the point that they are inaccurate (I might be wrong, please correct me, if I am!):

For Timers:

    int sec = 0;
    private void button2_Click(object sender, EventArgs e)
    {
        sec = DateTime.Now.Second;
        i = 0;
        timer1.Enabled = true;
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
        if (sec == DateTime.Now.Second)
        {
            i++;
        }
        else
        {
            timer1.Enabled = false;
            MessageBox.Show(i.ToString(),"Timer Output");
        }
    }

OUTPUT: Should be same, but:

enter image description here enter image description here enter image description here

For LOOP:

    private void button1_Click(object sender, EventArgs e)
    {
        i = 0;
        CheckForIllegalCrossThreadCalls = false;
        Thread t1 = new Thread(LoopTest);
        t1.Start();
    }

    void LoopTest()
    {
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
        sw.Start();
        this.Cursor = Cursors.WaitCursor;
        while (true)
        {
            if (sw.ElapsedMilliseconds != 1000)
            {
                i++;
            }
            else
            {
                break;
            }
        }
        sw.Stop();
        this.Cursor = Cursors.Default;
        MessageBox.Show(i.ToString(), "Loop Output");
    }

OUTPUT: Should be same, but:

enter image description here enter image description here enter image description here

What should I do to make loops and timer accurate, Is there any way to do it? Or I have to go to hard and complex C code and DOS?

I think it is the basic reason for getting me wrong values in this question: Count Parallel port input frequency - C#

Tamis answered 17/7, 2012 at 21:26 Comment(2)
That's a general issue with timers on windows. see the msdn article on timeBeginPeriod for details.Stitt
For complete accuracy, you'd at the very least need to start with a Real-Time Operating System (RTOS). Neither Windows nor DOS are that. You can get somewhat close if you give your loop realtime priority, but Windows/DOS still cannot guarantee it won't be interrupted by anything else.Spriggs
C
16

1) Don't use DateTime.Now for performance measurements, use StopWatch.

2) "OUTPUT: Should be same, but .."

Why should they? You are running managed/JIT'ed code on a non RTOS (real time operating system). You're code can get bounced at any time if the OS feels like it. What lead you to believe that running the same code N times in this environment should always produce the same results to such a small degree?

3) Timers on Windows have a ~15ms resolution. Your best bet for very accurate timings is the HighPerformanceTimer API on systems (CPU's) which support it. You haven't even shown us the timer's interval.

You're failing to consider many variables here and you're basing your predictions upon faulty assumptions. How many times are you even measuring this code? Are you taking into account the time needed to compile it the first time through? Are you running in release mode? Through VS? Are there many tasks running in the background? I could go on.

Carlina answered 17/7, 2012 at 21:32 Comment(3)
I am running it through VS, there is no other task running on the PC, only some background programs such as Antivirus, are running. So what should I do to get full accuracy, actually I have to create a program to calculate the parallel port input frequency.Tamis
@Farid-ur-Rahman: I think you missed my point. Define "full accuracy". Your assumptions are incorrect. You expect code to take almost exactly the same amount of time every time it is running, but you are running on Windows (non-RTOS, not even soft real time) and invoking managed code which must be compiled on the first pass. Running through VS is also not the same as running it normally. You need to test to ensure that the code, as it stands, fulfills your requirements, not some misguided assumption that it should always take N ms to invoke a timer...Carlina
... and then "verifying" that assumption with the wrong measurement tools. You're not even testing meaningful code.Carlina
B
13

A careful implementation allows the measurement of time periods on most windows platforms down to an accuracy of just a few microseconds. Note the following facts:

  1. Windows is not a real-time OS: This does not matter here!

  2. Make use of process/thread priorities: SetPriorityClass at up to REALTIME_PRIORITY_CLASS and SetThreadPriority at up to THREAD_PRIORITY_TIME_CRITICAL. Make sure to have safe code, because these priorities can lock the system while the calling thread is busy. (Process.PriorityClass and Thread.Priority are not quite capable to raise priorities to the required levels.)

  3. Possibly increase the systems interrupt period and time update interval by means of Multimedia Timer. (There are various .NET projects that wrap these multimedia timer functions.)

  4. On multicore systems the choice of the core can also influence the accuracy. It is advantagous to force the thread waiting for a timer event to wait on Processor0. The SetThreadAffinityMask function allows to bind a thread to a specific cpu. (For .Net applications see Thread.ProcessorAffinity.)

  5. Use QueryPerformanceCounter and QueryPerformanceFrequency as rescources for high frequency time measures. (For .Net applications see: Creating a QueryPerfCounter Wrapper Class)

  6. Make sure that the value of the performance counter frequency is calibrated. The value returned by QueryPerformanceFrequency deviates from the observed value by an offset and by some thermal drift. This may/will introduce errors of many us/s. See the Windows Timestamp Project to find out how such a calibration can be done.

And: Yes, you may have to go for some hard coding. But microsecond timing can be observed very reliable on windows platforms.

Note: Windows is not a real-time operating system. But the timers available on windows are very precise. They do exactly what they are supposed to do and the do it at very high accuracy. The fact that there are lots of complaints about windows timers and their accuracy is that their behavior depends a lot on the underlying hardware. That is also the reason why the documentation has many flaws. It is strongly recommended to diagnose the hardware in order to find the individual time service capabilities. Unfortunately this causes any program to have some extra lines of code to be platform independent.

Befitting answered 18/7, 2012 at 8:44 Comment(0)
D
2

First of all, you don't know how far into the current second the time is when you button2_click is called. So, it's basically random how much of a second is left when the MessageBox is displayed--which is what you're seeing.

Second, how many CPU cycles a loop gets within a span of time has nothing to do with accuracy.

How many cycles any given thread gets depends on what else is going on in the system. If the system decided that a whole bunch of cycles needed to go to another process, your thread would be "starved" for some time.

Maybe you can detail what you're really trying to do, and someone can offer some advice.

Doy answered 17/7, 2012 at 21:34 Comment(1)
Agree. It's really problematic to use .Second for this, for it gives only the integer part (whole number of seconds of the current minute). For example if the time is 2012-07-17T23:47:29.8057621+02:00 you get just 29.Communion
T
1

Ed's answer is correct: timers on Windows are not accurate. Well, certainly not accurate enough to measure hardware generated signals.

But, if I had to measure frequency and pulse width, I'd take a hundred samples or so and average them. Perhaps like so:

private StopWatch _Sw = new StopWatch();
private List<TimeSpan> _Samples = new List<TimeSpan>();
private Timer _Timer = new Timer(TimerTick, TimeSpan.FromSeconds(1));
private const int RequiredSamples = 100;

private void StartSampling()
{
    // You can change this next line to PriorityClass.RealTime if you're careful.
    System.Diagnostics.Process.GetCurrentProcess().BasePriority 
                              = PriorityClass.High;
    _Samples.Capacity = RequiredSamples;
    _Timer.Start();
    _Sw.Start();
    Hook555Timer(On555Pulse);
}

private void On555Pulse(object sender, EventArgs e)
{
    _Sample.Add(_Sw.Elapsed);
}

private void TimerTick(object sender, EventArgs e)
{
    if (_Samples.Count > RequiredSamples)
    {
        System.Diagnostics.Process.GetCurrentProcess().BasePriority 
                                    = PriorityClass.Normal;
        _Timer.Stop();
        _Sw.Stop();
        UnHook555Timer(On555Pulse);

        // You can now use the time between each TimeSpan 
        // in _Samples to determine statistics about your timer.
        // Eg: Min / Max duration, average and median duration.
        var durations = _Samples
                .Zip(_Samples.Skip(1), 
                    (a,b) => new { First = a, Second = b } )
                .Select(pair => pair.Second.Subtract(pair.First));
        var minTime = durations.Min(ts => ts.TotalMilliseconds);
        var maxTime = durations.Max(ts => ts.TotalMilliseconds);
        var averageTime = durations.Average(ts => ts.TotalMilliseconds);
        // I don't think LINQ has a Median() aggregate out of the box.
        // Some comment about "an exercise for the reader" goes here.
        var medianTime = durations.Median(ts => ts.TotalMilliseconds);

        var frequency = _Samples.Last()
                                 .Subtract(_Samples.First())
                                      .TotalSeconds / _Samples.Count;
    }
}

(NB: code written in notepad, not likely to work without further modifications)

My precision is now determined by StopWatch rather than the Timer (note that Stopwatch may not be any more precise than a Timer on your system, check the Frequency and IsHighResolution properties). And I'm increasing the priority of the process during sampling to minimise other processes pre-empting my process.

But even so, because Windows is not a RTOS, the best you can do is take lots of samples and use some statistics to get an approximation of the answer.

Trillium answered 17/7, 2012 at 23:15 Comment(2)
+1, for avarage value. But I cannot take 100 samples, because it consumes lots of time. Actually I am creating a Control panel consisting of many buttons, when user press the button for 50ms, it generates some pulses which PC have to calculate, so if I increased the number of samples, button pressing time is also increases, and user have to press the button for much longer time.Tamis
Ahh! I thought you were trying to measure some sort of regular waveform generated from the 555. Perhaps you should ask another question about detecting button presses. But I suspect you'd do better with buttons attached to an external embedded controller (Arduino or Netduino) and hook the controller to the PC via USB.Trillium

© 2022 - 2024 — McMap. All rights reserved.