C# Why are timer frequencies extremely off?
Asked Answered
C

10

15

Both System.Timers.Timer and System.Threading.Timer fire at intervals that are considerable different from the requested ones. For example:

new System.Timers.Timer(1000d / 20);

yields a timer that fires 16 times per second, not 20.

To be sure that there are no side-effects from too long event handlers, I wrote this little test program:

int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };

// Test System.Timers.Timer
foreach (int frequency in frequencies)
{
    int count = 0;

    // Initialize timer
    System.Timers.Timer timer = new System.Timers.Timer(1000d / frequency);
    timer.Elapsed += delegate { Interlocked.Increment(ref count); };

    // Count for 10 seconds
    DateTime start = DateTime.Now;
    timer.Enabled = true;
    while (DateTime.Now < start + TimeSpan.FromSeconds(10))
        Thread.Sleep(10);
    timer.Enabled = false;

    // Calculate actual frequency
    Console.WriteLine(
        "Requested frequency: {0}\nActual frequency: {1}\n",
        frequency, count / 10d);
}

The output looks like this:

Requested: 5 Hz; actual: 4,8 Hz
Requested: 10 Hz; actual: 9,1 Hz
Requested: 15 Hz; actual: 12,7 Hz
Requested: 20 Hz; actual: 16 Hz
Requested: 30 Hz; actual: 21,3 Hz
Requested: 50 Hz; actual: 31,8 Hz
Requested: 75 Hz; actual: 63,9 Hz
Requested: 100 Hz; actual: 63,8 Hz
Requested: 200 Hz; actual: 63,9 Hz
Requested: 500 Hz; actual: 63,9 Hz

The actual frequency deviates by up to 36% from the requested one. (And evidently cannot exceed 64 Hz.) Given that Microsoft recommends this timer for its "greater accuracy" over System.Windows.Forms.Timer, this puzzles me.

Btw, these are not random deviations. They are the same values every time. And a similar test program for the other timer class, System.Threading.Timer, shows the exact same results.

In my actual program, I need to collect measurements at precisely 50 samples per second. This should not yet require a real-time system. And it is very frustrating to get 32 samples per second instead of 50.

Any ideas?

@Chris: You are right, the intervals all seem to be integer multiples of something around 1/64th second. Btw, adding a Thread.Sleep(...) in the event handler doesn't make any difference. This makes sense given that System.Threading.Timer uses the thread pool, so each event is fired on a free thread.

Calcic answered 6/1, 2009 at 13:33 Comment(4)
I'm not sure that: "This should not yet require a real-time system." can be true, given : "I need to collect measurements at precisely 50 samples per second."Troika
The MultiMediaTimer (winmm.dll) usage presented by Justin Tanner works perfect. Thanks a lot man.Monolayer
I agree. Sounds like a hard real time requirement to me - exactly what a RTOS is meant to solve. Event every 20ms with a guaranteeed response of 1-2 ms.Foolscap
If you need "precisely 50 samples per second" your best bet is to use an external board, microcontroller or other hardware that you have control over. Windows has a LOT going on and you have very limited control and visibility into the proprietary code.Jephthah
C
3

Well, I'm getting different number up to 100 Hz actually, with some big deviations, but in most cases closer to the requested number (running XP SP3 with most recent .NET SPs).

The System.Timer.Timer is implemented using System.Threading.Timer, so this explains why you see same results. I suppose that the timer is implemented using some kind of scheduling algorithm etc. (it's internal call, maybe looking at Rotor 2.0 might shed some light on it).

I would suggest to implement a kind of timer using another thread (or combination thereof) calling Sleep and a callback. Not sure about the outcome though.

Otherwise you might take a look at multimedia timers (PInvoke).

Cantina answered 6/1, 2009 at 14:14 Comment(0)
B
23

If you use winmm.dll you can use more CPU time, but have better control.

Here is your example modified to use the winmm.dll timers

const String WINMM = "winmm.dll";
const String KERNEL32 = "kernel32.dll";

delegate void MMTimerProc (UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2);

[DllImport(WINMM)]
static extern uint timeSetEvent(
      UInt32            uDelay,      
      UInt32            uResolution, 
      [MarshalAs(UnmanagedType.FunctionPtr)] MMTimerProc lpTimeProc,  
      UInt32            dwUser,      
      Int32             fuEvent      
    );

[DllImport(WINMM)]
static extern uint timeKillEvent(uint uTimerID);

// Library used for more accurate timing
[DllImport(KERNEL32)]
static extern bool QueryPerformanceCounter(out long PerformanceCount);
[DllImport(KERNEL32)]
static extern bool QueryPerformanceFrequency(out long Frequency);

static long CPUFrequency;

static int count;

static void Main(string[] args)
{            
    QueryPerformanceFrequency(out CPUFrequency);

    int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };

    foreach (int freq in frequencies)
    {
        count = 0;

        long start = GetTimestamp();

        // start timer
        uint timerId = timeSetEvent((uint)(1000 / freq), 0, new MMTimerProc(TimerFunction), 0, 1);

        // wait 10 seconds
        while (DeltaMilliseconds(start, GetTimestamp()) < 10000)
        {
            Thread.Sleep(1);
        }

        // end timer
        timeKillEvent(timerId);

        Console.WriteLine("Requested frequency: {0}\nActual frequency: {1}\n", freq, count / 10);
    }

    Console.ReadLine();
}

static void TimerFunction(UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2)
{
    Interlocked.Increment(ref count);
}

static public long DeltaMilliseconds(long earlyTimestamp, long lateTimestamp)
{
    return (((lateTimestamp - earlyTimestamp) * 1000) / CPUFrequency);
}

static public long GetTimestamp()
{
    long result;
    QueryPerformanceCounter(out result);
    return result;
}

And here is the output I get:

Requested frequency: 5
Actual frequency: 5

Requested frequency: 10
Actual frequency: 10

Requested frequency: 15
Actual frequency: 15

Requested frequency: 20
Actual frequency: 19

Requested frequency: 30
Actual frequency: 30

Requested frequency: 50
Actual frequency: 50

Requested frequency: 75
Actual frequency: 76

Requested frequency: 100
Actual frequency: 100

Requested frequency: 200
Actual frequency: 200

Requested frequency: 500
Actual frequency: 500

Hope this helps.

Bustamante answered 1/4, 2009 at 0:44 Comment(0)
P
5

These classes aren't intended for real-time use and are subject to the dynamic scheduling nature of an operating system like Windows. If you need real-time execution you'd probably want to look at some embedded hardware. I'm not 100% sure but I think .netcpu may be a real-time version of a smaller .NET runtime on a chip.

http://www.arm.com/markets/emerging_applications/armpp/8070.html

Of course - you need to evaluate how important accuracy of those intervals are given that the code attached to them is going to be executing on a non-realtime operating system. Unless of course this is a purely academic question (in which case - yes it is interesting! :P).

Premillenarian answered 6/1, 2009 at 13:50 Comment(0)
H
4

It looks like your actual timer frequencies are 63.9 Hz or integer multiples thereof.

That would imply a timer resolution of about 15 msec (or integer multiples therefof, i.e. 30 msec, 45 msec, etc.).

This, timers based on integer multiples of a 'tick', is to be expected (in DOS for example the 'tick' value was 55 msec / 18 Hz).

I don't know why your tick count is 15.65 mec instead of an even 15 msec. As an experiment, what if you sleep for several msec within the timer handler: might we be seeing 15 msec between ticks, and 0.65 msec in your timer handler at each tick?

Headed answered 6/1, 2009 at 14:1 Comment(0)
A
3

Windows (and therefore .NET running on top of it) is a preemptively multitasking operating system. Any given thread can be stopped at any time by another thread, and if the preempting thread doesn't behave properly, you won't get control back when you want it or need it.

That, in a nutshell, is why you cannot be guaranteed to get exact timings, and why Windows and .NET are unsuitable platforms for certain types of software. If lives are in danger because you don't get control EXACTLY when you want it, choose a different platform.

Adulthood answered 6/1, 2009 at 14:1 Comment(1)
Well this is almost true, there are more accurate timers available. It's not like accurate timing is impossible just because you're using Windows.Sturges
C
3

Well, I'm getting different number up to 100 Hz actually, with some big deviations, but in most cases closer to the requested number (running XP SP3 with most recent .NET SPs).

The System.Timer.Timer is implemented using System.Threading.Timer, so this explains why you see same results. I suppose that the timer is implemented using some kind of scheduling algorithm etc. (it's internal call, maybe looking at Rotor 2.0 might shed some light on it).

I would suggest to implement a kind of timer using another thread (or combination thereof) calling Sleep and a callback. Not sure about the outcome though.

Otherwise you might take a look at multimedia timers (PInvoke).

Cantina answered 6/1, 2009 at 14:14 Comment(0)
P
2

If you do need to make the jump to a real-time environment, I've used RTX in the past when deterministic sampling was needed (from a custom serial device) and had very good luck with it.

http://www.pharlap.com/rtx.htm

Pantomimist answered 6/1, 2009 at 14:36 Comment(0)
D
2

Here is a nice implementation of a timer using multimedia timer http://www.softwareinteractions.com/blog/2009/12/7/using-the-multimedia-timer-from-c.html

Drawplate answered 12/2, 2010 at 2:34 Comment(0)
H
1

There's a lot of reasons that the timer frequencies off. Example with the hardware, the same thread is busy with another processing, and so on...

If you want more accurately time, use the Stopwatch class in the System.Diagnostics namespace.

Hewes answered 6/1, 2009 at 13:42 Comment(1)
Thanks for your answer, robertpnl. Unfortunately, Stopwatch is for measuring time only, not for periodic events. And in the code example, the CPU load is close to zero, so that can't be the problem.Calcic
F
0

Part of the problem is that Timers have two delays to account. This can vary depending on how the timer is implemented in the OS.

  1. The time that you request to wait for
  2. The time between when #1 occurs and the process gets its turn in the scheduling queue.

The timer has good control of #1 but almost no control over #2. It can signal to the OS that it would like to run again but the OS is free to wake it up when it feels like it.

Fluke answered 6/1, 2009 at 14:18 Comment(0)
E
0

Based on your comments, you shouldn't be using a timer at all. You should be using a loop with a stopwatch to check the interval and a spinlock so you don't lose the quantum.

Eaton answered 1/4, 2009 at 0:48 Comment(3)
I don't think that is going to be any more accurate... The thread with the loop could be stopped at any time by the OS to run something else.Lilia
I guess you could set the priority to real time, use the concurrent GC, and make it a requirement that this program is only installed on multi-core machines. Not perfect, but it is probably as close as you can get before dropping into the kernel.Eaton
Setting a process priority to Realtime instructs the OS that you never want the process to yield CPU time to another process. If your program enters an accidental infinite loop, you might find even the operating system locked out, with nothing short of the power button left to rescue you! For this reason, High is usually the best choice for real-time applications. BUT. There are a lot of problems with High too, if your real-time application has a user interface, elevating the process priority gives screen updates excessive CPU time. So using MMTimers for this case is THE BEST.Eleneeleni

© 2022 - 2024 — McMap. All rights reserved.