秒表和DateTime.UtcNow产生意外的大时序变化

时间:2015-03-16 18:47:37

标签: c# datetime timing stopwatch

我们有应用程序日志记录各种昂贵操作的性能信息。我们在日志记录中同时使用StopwatchDateTime.UtcNow,我们发现这些值可能比预期的要大很多,即使给定DateTime.UtcNow的精度为~20ms 。我的问题是什么可能导致这种情况并且可以修复?

记录的信息是:

  • StartTime(DateTime.UtcNow
  • 持续时间(TimeSpan.FromSeconds((after - before) / (double)Stopwatch.Frequency),其中afterbefore是操作开始和结束时Stopwatch.GetTimestamp()的值
  • 结束时间(DateTime.UtcNow

你会期望EndTime接近StartTime + Duration,但在某些情况下它会偏离。我们对10000个这样的测量进行了采样,寻找EndTime和(StartTime + Duration)相差超过20ms的情况。我们发现了以下内容:

  • 〜2%的参赛作品被> 20ms的
  • 其中,它们的平均时间为100毫秒(最长为1.4秒)
  • 其中,在某些情况下,秒表计算的结束时间大于基于日期时间的结束时间,但在大多数情况下,秒表时间较短

计算机信息

  • 在Windows Server 2012 R2上运行的Windows Server 2012 R2 Hyper-V VM
  • VM为32GB RAM,8个虚拟CPU

1 个答案:

答案 0 :(得分:6)

这是可以预料的。 StopWatch使用QueryPerformanceCounter(QPC),DateTime.UtcNow使用计算机的实时时钟(RTC)。

  • QPC来自您的CPU的时间戳计数器(TSC),并且具有高度精确度,但与UTC相关的准确度没有依据。
  • RTC位于计算机主板上,通常使用廉价的晶体振荡器。它准确,但不是精确

为了测量经过的时间,您需要精确度,因此应使用Stopwatch

要获取当前时间,您需要准确性,因此应使用DateTime.UtcNow

包含大量支持详情的精彩阅读can be found here on MSDN

您测量的变化是由于RTC中的时钟漂移造成的。你的RTC(全部都是)会在实时提前或落后几毫秒。这很常见,并且通过NTP同步定期更正。对于微小的调整,操作系统将在几秒钟内以较小的间隔(几毫秒)分散校正。

就解决方法而言,你可以考虑这样一个类:

public static class PreciseClock
{
    private static readonly DateTime StartTime = DateTime.UtcNow;
    private static readonly Stopwatch Stopwatch = Stopwatch.StartNew();

    public static DateTime GetCurrentUtcTime()
    {
        return StartTime + Stopwatch.Elapsed;
    }
}

然而,缺点是你假设在初始化类时RTC完全同步。实际上,你无法保证这一点。

如果您想要一些级别的保证,您可以考虑自己进行NTP呼叫 。我已经在NodaTime.NetworkClock中实现了一个完全相同的类。它实现了Noda TimeIClock接口。它会定期拨打NTP服务器,并使用Stopwatch跟踪呼叫之间的时间。你可以这样使用它:

// grab the clock's singleton instance
var clock = NetworkClock.Instance;

// optionally set the ntp server during your app's startup
clock.NtpServer = "pool.ntp.org"; // or whatever server you want to sync with

// Get the current utc time whenever you like
DateTime utcNow = clock.Now.ToDateTimeUtc();

此外,还有大多数现代硬件都可以使用的“多媒体计时器”或“高精度事件计时器”(HPET)。它比QPC提供甚至更高的精度。但是,没有直接类在.NET Framework中公开它。如果你搜索,你会发现一些包含提供它的Win32函数的实现。通常情况下,除非您正在进行图形或音频之类的实时操作,否则这样做太过分了。