C# 秒表和DateTime.UtcNow产生出乎意料的大时间变化

C# 秒表和DateTime.UtcNow产生出乎意料的大时间变化,c#,datetime,timing,stopwatch,C#,Datetime,Timing,Stopwatch,我们有记录各种昂贵操作的性能信息的应用程序日志。我们在日志记录中同时使用了秒表和日期时间.UtcNow,我们发现,即使给定日期时间,这些值的差异也远远超过预期值。UtcNow的~20ms精度。我的问题是什么会导致这种情况?它能被修复吗? 记录的资料如下: 开始时间(DateTime.UtcNow) 持续时间(TimeSpan.FromSeconds((after-before)/(double)Stopwatch.Frequency),其中after和before是操作开始和结束时的Stopw

我们有记录各种昂贵操作的性能信息的应用程序日志。我们在日志记录中同时使用了
秒表
日期时间.UtcNow
,我们发现,即使给定日期时间,这些值的差异也远远超过预期值。UtcNow的~20ms精度。我的问题是什么会导致这种情况?它能被修复吗?

记录的资料如下:

  • 开始时间(
    DateTime.UtcNow
  • 持续时间(
    TimeSpan.FromSeconds((after-before)/(double)Stopwatch.Frequency)
    ,其中
    after
    before
    是操作开始和结束时的
    Stopwatch.GetTimestamp()
  • EndTime(
    DateTime.UtcNow
您可能期望EndTime与StartTime+持续时间接近,但在某些情况下,这与相差很远。我们对10000个此类测量进行了抽样,寻找EndTime与(StartTime+持续时间)相差超过20ms的情况。我们发现:

  • 约2%的条目关闭时间超过20毫秒
  • 其中,它们的平均关断时间为100毫秒(最大关断时间为1.4秒)
  • 其中,在某些情况下,秒表计算的结束时间大于基于日期时间的结束时间,但在大多数情况下,秒表时间较小
机器信息

  • 运行在Windows Server 2012 R2之上的Windows Server 2012 R2 Hyper-V虚拟机
  • 32GB ram用于虚拟机,8个虚拟CPU

这是意料之中的。
秒表使用引擎盖下的
查询性能计数器(QPC),而
日期时间。UtcNow
使用计算机的实时时钟(RTC)

  • QPC来自您的CPU的时间戳计数器(TSC),非常精确,但与UTC相关的准确性没有依据
  • RTC位于计算机主板上,通常由廉价的晶体振荡器构成。它是精确的,但不精确
要测量经过的时间,您需要精度,因此应该使用秒表

要获取一天中的当前时间,您需要准确度,因此应该使用
DateTime.UtcNow

这是一本很棒的书,里面有很多支持性的细节

您测量到的变化是由于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;
    }
}
// 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();
然而,缺点是您假设在初始化类时RTC是完全同步的。实际上,您对此没有保证

如果你想要某种程度的保证,你可以考虑自己做NTP调用。我已经实现了一个完全正确的类。它实现了<代码> IcLoo.接口。它定期调用NTP服务器,并使用<代码>秒表< /C>跟踪调用之间的时间。你会这样使用:

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;
    }
}
// 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函数的实现。通常,除非您正在做实时的事情,如图形或音频,否则这是一种过度的操作。

下面是Eric Lippert关于为什么秒表优于调用
DateTime的部分内容。现在,我们可以用
来测量程序中经过了多少时间。@Chase我可以问你测量的操作长度吗?秒?分钟?小时?看一下,utcnow的分辨率大约为10毫秒,因此可以解释您观察到的一些差异。这些差异基于长时间运行的实际系统测量值?偏差是否与操作长度相关?一种理论可能是系统时钟正在自动调整,这会影响
DateTime.UtcNow
,但不会影响
秒表
measurements。虚拟机通常与主机同步的频率相当高。我可以相信,这些虚拟机会关闭几毫秒,但我看到会关闭数百毫秒。您所描述的内容是否真的会导致如此大的差异?当然,很可能是这样,甚至更大。这一切都取决于主板上RTC晶体的质量。有些问题是:dware比其他的要好。在我的机器上,我测量了10到60毫秒的修正,但是没有理由它们不能在其他硬件上更大。另外,你说你在虚拟机上运行这个,这完全可能会影响结果。我不完全确定RTC虚拟化的机制,但我非常确定主机OS必须将RTC值传递给客户,并且可能存在延迟。您可以考虑直接在服务器硬件上运行相同的测试,并查看它是否有区别。