Wednesday, January 29, 2014

C# limitation in real-time computing

You'll find that exact same behaviour with C++, calling the Windows API "Sleep" function. It happens because the default resolution of the system clock is several milliseconds (up to about 20 or so, I think). Any calls to Sleep() will only be accurate to the nearest system clock tick.



As the documentation for the Windows API Sleep() says:



This function causes a thread to relinquish the remainder of its time slice and become unrunnable for an interval based on the value of dwMilliseconds. The system clock "ticks" at a constant rate. If dwMilliseconds is less than the resolution of the system clock, the thread may sleep for less than the specified length of time. If dwMilliseconds is greater than one tick but less than two, the wait can be anywhere between one and two ticks, and so on. To increase the accuracy of the sleep interval, call the timeGetDevCaps function to determine the supported minimum timer resolution and the timeBeginPeriod function to set the timer resolution to its minimum. Use caution when calling timeBeginPeriod , as frequent calls can significantly affect the system clock, system power usage, and the scheduler. If you call timeBeginPeriod , call it one time early in the application and be sure to call the timeEndPeriod function at the very end of the application.


However, it's possible to make the system clock run at a higher resolution. Doing that affects the system clock for ALL applications! Some applications such as Windows Media Player change the system clock setting, so if you do some timings while running Windows Media Player, you will see an increased Sleep() resolution. Yeah, it's horrible.



Anyway, here's some sample code. To run it, replace the contents of a default console app's "Program.cs" file with the following code. Run the program WITHOUT Windows Media Player running (or WinAmp or any other media player). Then run it again while Windows Media Player is running. Note how it uses the Windows API function "timeBeginPeriod" to adjust the system clock resolution.



Interesting. :)




using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

namespace Test
{
public static class Program
{
public static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();

for (int i = 0; i < 10; ++i)
{
sw.Reset();
sw.Start();
Thread.Sleep(50);
sw.Stop();

Console.WriteLine("(default) Slept for " + sw.ElapsedMilliseconds);

TimeBeginPeriod(1);
sw.Reset();
sw.Start();
Thread.Sleep(50);
sw.Stop();
TimeEndPeriod(1);

Console.WriteLine("(highres) Slept for " + sw.ElapsedMilliseconds + "\n");
}
}

[DllImport("winmm.dll", EntryPoint="timeBeginPeriod", SetLastError=true)]
private static extern uint TimeBeginPeriod(uint uMilliseconds);

[DllImport("winmm.dll", EntryPoint="timeEndPeriod", SetLastError=true)]
private static extern uint TimeEndPeriod(uint uMilliseconds);
}
}

No comments:

Post a Comment