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

Cha*_*ion 4 c# datetime timing stopwatch

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

记录的信息是:

  • StartTime(DateTime.UtcNow)
  • 持续时间(TimeSpan.FromSeconds((after - before) / (double)Stopwatch.Frequency),其中afterbeforeStopwatch.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

Mat*_*int 6

这是可以预料的. 在引擎盖下StopWatch使用QueryPerformanceCounter(QPC),同时DateTime.UtcNow使用计算机的实时时钟(RTC).

  • QPC来自CPU的时间戳计数器(TSC),并且非常精确,但与UTC相关的准确性没有根据.
  • RTC位于计算机主板上,通常由廉价的晶体振荡器构成.这是准确的,但不准确.

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

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

可以在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;
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,缺点是您在初始化类时假设RTC处于完全同步状态.实际上,你无法保证这一点.

如果您需要某种程度的保证,可以考虑自己进行NTP调用.我已经在NodaTime.NetworkClock中实现了一个完全相同的类.它实现了Noda TimeIClock界面.它定期拨打NTP服务器,并使用a跟踪呼叫之间的时间.你会像这样使用它: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();
Run Code Online (Sandbox Code Playgroud)

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