Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

High Resolution Time For Windows 7

4.89/5 (3 votes)
22 Jul 2015Apache3 min read 16.3K   290  
Provides high-resolution estimate of time for Windows 7 and other OS versions that don't support GetSystemTimePreciseAsFileTime

Introduction

Here, I present a possible solution (or at least hopefully a pretty good polyfill) for those who want the precision of the new GetSystemTimePreciseAsFileTime API but have OS versions that do not support it. The GetSystemTimePreciseAsFileTime API is only available in Windows 8 or later or Windows Server 2012 or later. Many people still use Windows 7 and this will probably remain true for several years.

Background

Calling DateTime.UtcNow is a useful way to obtain the current date and time in C#. It returns a DateTime struct which is a 64-bit value that stores "ticks". A tick is a 100-nanosecond interal, or 0.1 microseconds. This is a nice level of precision, but unfortunately the resolution is limited to the Windows system timer resolution, which on most systems cannot get better than 5000 ticks or half a millisecond, and is closer to 15ms in practice on most machines I've observed. (Use NtQueryTimerResolution to see your system's timer resolution.)

Say your app creates 'widgets' that have a date/time applied to them. You care about sorting these widgets by date/time, but you come to realize that if you create several widgets in a short period of time, then they all appear to have the same date/time. Maybe you can apply a monotonically increasing 'sequence number' to each widget, but somehow this seems unsatisfactory. The 64-bit tick count clearly provides enough space to store everything we need, yet we can only see up to 0.02% of it (assuming: 1 tick / min system res = 0.0001ms / 0.5ms = 0.0002).

Of course, QueryPerformanceCounter has long provided a high-resolution way to measure elapsed time, and .NET's Stopwatch class uses it (where available). Note that QPC allows at least 10% of the ticks value to be used (0.0001ms / 0.001ms), as it provides no worse than 1-microsecond resolution. I highly recommend reading Acquiring high-resolution time stamps on MSDN which provides you everything you ever wanted to know about QPC and how to use it. QueryPerformanceCounter, though, does not give a date/time, it gives a high-resolution monotonically-increasing value that can be used to calculate a time span. So why don't we combine QueryPerformanceCounter with the API calls that use the Windows system timer, and create a high-resolution DateTime value?

This is the essence of my code, and while others have implemented similar concepts on the surface, my code not only provides a close approximation to real time but it also ensures monotonically-increasing values (for calls on the same thread).

Using the Code

The static class DateTimeHighResWin7 provides two public properties, UtcNow and Now, which function similarly to DateTime's like-named properties but provide higher resolution. It's as easy as that. You can even combine DateTimeHighResWin7 with Sebastian Krysmanski's HighResolutionDateTime to provide the full .NET high-resolution date/time experience for old and new OSes alike.

C#
var now = HighResolutionDateTime.IsAvailable ?
HighResolutionDateTime.UtcNow : DateTimeHighResWin7.UtcNow;

The code for DateTimeHighResWin7 is short enough to reproduce in full below.

C#
/// <summary>
/// Provides high-resolution estimate of time for Windows 7 and other OS versions that
/// don't support GetSystemTimePreciseAsFileTime.
/// </summary>
public static class DateTimeHighResWin7
{
    public static DateTime UtcNow
    {
        get
        {
            return MakeHighResolution(DateTime.UtcNow);
        }
    }

    public static DateTime Now
    {
        get
        {
            return MakeHighResolution(DateTime.Now);
        }
    }

    static DateTimeHighResWin7()
    {
        // This is the value to multiply by Stopwatch timestamp values to get DateTime ticks.
        TicksAdjustNormal = TimeSpan.TicksPerSecond * 1.0 / Stopwatch.Frequency;

        // This is a value less than TicksAdjustNormal used to scale back estimates when needed.
        TicksAdjustSlowdown = 1.0;

        // Our Stopwatch has to be high-resolution and have a
        // good-enough frequency to get high-res estimates.
        Unreliable = !Stopwatch.IsHighResolution ||
        TicksAdjustNormal < 1.414; // arbitrary number a little more than one
                // (640K/10*2^15.5 ought to be enough for anybody)
    }

    private static readonly double TicksAdjustNormal;
    private static readonly double TicksAdjustSlowdown;
    private static readonly bool Unreliable;

    /// <summary>
    /// LastTicks and LastTS are used to estimate time values.
    /// LastTicks + (CurrentTS - LastTS) * Scale_Factor
    /// </summary>
    [ThreadStatic]
    private static long LastTicks;

    /// <summary>
    /// LastTS is the Stopwatch timestamp value observed when LastTicks was last assigned.
    /// </summary>
    [ThreadStatic]
    private static long LastTS;

    /// <summary>
    /// Set to true when our estimate overtakes the real system ticks value, so we can
    /// start scaling back future estimates until we converge with the real time.
    /// </summary>
    [ThreadStatic]
    private static bool AdjustTicks;

    /// <summary>
    /// Stores last real system ticks value observed.
    /// </summary>
    [ThreadStatic]
    private static long LastSystemTicks;

    private static DateTime MakeHighResolution(DateTime dt)
    {
        // we're given a DateTime, so let's quickly get a high-res timestamp to accompany it.
        long ts = Stopwatch.GetTimestamp();

        if (Unreliable) return dt;

        bool systemTicksChanged = false;
        if (dt.Ticks != LastSystemTicks)
        {
            LastSystemTicks = dt.Ticks;
            systemTicksChanged = true;
        }

        if (LastTS == 0L)
        {
            // first time through on this thread, so we'll return system ticks
            LastTicks = dt.Ticks;
            LastTS = ts;
            AdjustTicks = false;
        }
        else
        {
            // give our best estimate of what a new high-res ticks value might be
            long newTicks = LastTicks + (long)((ts - LastTS) *
                (AdjustTicks ? TicksAdjustSlowdown : TicksAdjustNormal));
            // if system ticks is not less then we'll trust system ticks
            if (dt.Ticks >= newTicks)
            {
                LastTicks = dt.Ticks;
                LastTS = ts;
                AdjustTicks = false;
            }
            else
            {
                // we calculated a value higher than what
                // we have been given for system ticks.
                // the most likely scenario is that this
                // is a pretty good estimate and it lies somewhere
                // between system ticks we have now and the next system ticks we get.
                // however, there is the possibility that we'll
                // be returning a value higher than the next
                // system ticks that comes through.
                // ...this could be due to being just off a bit
                // due to rounding or inopportune thread interruption,
                // or it could be that the real time between the
                // first time we see one system ticks value
                // and the first time we see the next system ticks
                // value is more than the difference between
                // the two tick values. system ticks don't come in
                // at regular intervals. we need to be prepared....
                // we want to handle this scenario as best as possible
                // while maintaining two invariants:
                // 1. each value we return (on a particular thread)
                // should be >= the previous value we returned.
                // 2. the value we return should be close
                // to the actual time (within range of system
                //    timer resolution)
                // so here if a new system ticks has come in and
                // it's less than what we calculated, then
                // we'll begin to scale back our future calculations
                // until we have recalibrated, and
                // soon the system ticks will overtake our calculated values again.
                if (systemTicksChanged)
                {
                    // a new system ticks was observed and it's less than newTicks,
                    // so we will slow down our future calculations
                    LastTicks = newTicks;
                    LastTS = ts;
                    AdjustTicks = true;
                }
                dt = new DateTime(newTicks);
            }
        }

        return dt;
    }
}

Points of Interest

Bundled in the source code is a console app that runs tests and does timing comparisons between DateTimeHighResWin7 and DateTime (and GetSystemTimePreciseAsFileTime where available). I believe the code and the comments speak for themselves. Please study them, run your own tests, and I welcome any feedback you may have.

History

  • July 21, 2015 -- Code first published
  • July 24, 2015 -- Use "Unreliable" flag

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0