Saturday, June 02, 2007

Measuring Time on .NET CF 2

If you develop code on both the desktop with the full .NET and on the mobile platform with .NET CF, you have to be careful with the behavioral differences of some classes. DateTime is one such class. On the desktop, DateTime.Now is capable of 10 millisecond resolution (at least on WinXP and Vista). On the mobile platform, however, DateTime.Now only has a resolution of 1 second. Quite the difference if you're using DateTime.Now as an easy way to timestamp data. Note that this also includes the DateTime.Now.Ticks, which on the desktop is measured in 100-nanosecond units but is only at the 1-second level in Windows Mobile. So, what are the alternatives?

The most straightforward method may be Environment.TickCount (which is different from DateTime.Ticks). Environment.TickCount represents a 32-bit signed integer containing the amount of time in milliseconds that has pass since the last time the computer was started. The problem with TickCount, however, is that it is only a 32 bit value. Therefore, if the system runs continuously (e.g., no restarting) for 24.9 days the TickCount value will reach int.MaxValue and then wrap to int.MinValue, which is a negative value. Then, for the next 24.9 days, Environment.TickCount will increment from int.MinValue to 0 and start the cycle all over again.

Alternatively, you can P/Invoke QueryPerformanceCounter and QueryPerformanceFrequency. The QueryPerformanceCounter function retrieves the current value of the high-resolution performance counter, if one exists, on the computer. The QueryPerformanceFrequency function retrieves the frequency of the high-performance counter, if it exists. The frequency cannot change while the system is running. The frequency is also platform dependent. I'm not sure if any mobile device ships with a performance counter that offers higher resolution than Environment.TickCount but it is worth experimenting with. Here's what MSDN has to say about "high resolution timers"

If a high-resolution performance counter exists on the system, you can use the QueryPerformanceFrequency function to express the frequency, in counts per second. The value of the count is processor dependent. On some processors, for example, the count might be the cycle rate of the processor clock.

The QueryPerformanceCounter function retrieves the current value of the high-resolution performance counter. By calling this function at the beginning and end of a section of code, an application essentially uses the counter as a high-resolution timer. For example, suppose that QueryPerformanceFrequency indicates that the frequency of the high-resolution performance counter is 50,000 counts per second. If the application calls QueryPerformanceCounter immediately before and immediately after the section of code to be timed, the counter values might be 1500 counts and 3500 counts, respectively. These values would indicate that .04 seconds (2000 counts) elapsed while the code executed


Here's the code to use the QueryPerformanceCounter in .NET CF 2 on Windows Mobile.

public static class PerformanceUtils
{
[DllImport("coredll.dll", EntryPoint = "QueryPerformanceCounter")]
private static extern bool QueryPerformanceCounter(out long count);

[DllImport("coredll.dll", EntryPoint = "QueryPerformanceFrequency")]
private static extern bool QueryPerformanceFrequency(out long countsPerSecond);

//these two variables are initialized in the PerformanceUtils static constructor
public static readonly long Frequency;
public static readonly long FrequencyInMs;

static PerformanceUtils()
{
if (QueryPerformanceFrequency(out Frequency) == false)
{
throw new Exception("The high resolution timer is not available on this device.");
}

FrequencyInMs = Frequency / 1000;
}

public static long GetTimestampMs()
{
long count;
QueryPerformanceCounter(out count);
return (long)Math.Round(count / (double)FrequencyInMs);
}

public static long GetPerformanceCount()
{
long count;
QueryPerformanceCounter(out count);
return count;
}
}


Update 06/07/2007 @ 1:45PM: Note that on a PocketPC the Environment.TickCount value is reset when you "soft or hard reset" the device. It is not reset when you suspend and resume (power off/on) the device. This is according to Ercan Turkarslan from Microsoft Mobile Devices Developer Support. On a Pocket PC Phone or a SmartPhone, the Environment.TickCount value is reset when you power off/on the device.