【问题标题】:How to get timestamp of tick precision in .NET / C#?如何在 .NET / C# 中获取刻度精度的时间戳?
【发布时间】:2010-11-27 19:38:37
【问题描述】:

到目前为止,我使用DateTime.Now 来获取时间戳,但我注意到如果你在循环中打印DateTime.Now,你会看到它以大约 10 的离散跳跃递增。 15 毫秒。但是对于我的应用程序中的某些场景,我需要尽可能获得最准确的时间戳,最好是刻度(= 100 ns)精度。有任何想法吗?

更新:

显然,StopWatch / QueryPerformanceCounter 是要走的路,但它只能用来测量时间,所以我想在应用程序启动时调用DateTime.Now,然后让StopWatch 运行然后将StopWatch 的经过时间添加到DateTime.Now 返回的初始值。至少这应该给我准确的相对时间戳,对吧?你怎么看这个(黑客)?

注意:

StopWatch.ElapsedTicksStopWatch.Elapsed.Ticks 不同!我使用前者假设 1 tick = 100 ns,但在这种情况下 1 tick = 1 / StopWatch.Frequency。因此,要获得与 DateTime 等效的刻度,请使用 StopWatch.Elapsed.Ticks。我刚刚学会了这一点。

注意 2:

使用秒表方法时,我注意到它与实时不同步。大约10个小时后,它领先了5秒。所以我想人们必须每隔 X 左右重新同步一次,其中 X 可能是 1 小时、30 分钟、15 分钟等。我不确定重新同步的最佳时间跨度是多少,因为每次重新同步都会改变偏移量长达 20 毫秒。

【问题讨论】:

  • 当然,服务器上设置的日期/时间不能保证精确到 100ns,所以我不确定拥有“准确”时间戳有什么用(因为它的准确性只会是服务器的)?
  • 我需要对准确性非常重要的实时财务数据进行计时。
  • 显然,我喜欢你的 hack。 :) 但是为什么财务数据需要精确到 100ns?如果您要对写入数据库的数据进行时间戳记,我可以理解这一点,并且您需要区分具有完全相同时间戳值的行。
  • 我正在执行需要这种精度的计算。看到顺序发生的交易时间戳具有相同的时间戳,而实际上您知道它们并非在完全相同的时间发生,这也让人感觉不对。

标签: c# .net timestamp


【解决方案1】:

DateTime.Now 读取的系统时钟值仅每 15 毫秒左右(或在某些系统上为 10 毫秒)更新一次,这就是时间围绕这些间隔进行量化的原因。由于您的代码在多线程操作系统中运行,因此会产生额外的量化效果,因此在某些情况下您的应用程序不“活跃”,因此无法测量实际的当前时间。

由于您正在寻找一个超准确的时间戳值(而不是仅仅计时任意持续时间),Stopwatch 类本身不会满足您的需求。我认为您必须自己使用DateTime/Stopwatch hybrid 来执行此操作。当您的应用程序启动时,您将存储当前的DateTime.UtcNow 值(即应用程序启动时的粗分辨率时间),然后还启动一个Stopwatch 对象,如下所示:

DateTime _starttime = DateTime.UtcNow;
Stopwatch _stopwatch = Stopwatch.StartNew();

然后,每当您需要高分辨率的DateTime 值时,您会得到这样的结果:

DateTime highresDT = _starttime.AddTicks(_stopwatch.Elapsed.Ticks);

您可能还需要定期重置 _starttime 和 _stopwatch,以防止生成的时间与系统时间太不同步(尽管我不确定这是否真的会发生,并且需要很长时间才能无论如何都会发生)。

更新:因为看起来秒表确实与系统时间不同步(每小时最多半秒),我认为这是有道理的根据调用之间经过的时间来重置混合DateTime 类以检查时间:

public class HiResDateTime
{
    private static DateTime _startTime;
    private static Stopwatch _stopWatch = null;
    private static TimeSpan _maxIdle = 
        TimeSpan.FromSeconds(10);

    public static DateTime UtcNow
    {
        get
        {
            if ((_stopWatch == null) || 
                (_startTime.Add(_maxIdle) < DateTime.UtcNow))
            {
                Reset();
            }
            return _startTime.AddTicks(_stopWatch.Elapsed.Ticks);
        }
    }

    private static void Reset()
    {
        _startTime = DateTime.UtcNow;
        _stopWatch = Stopwatch.StartNew();
    }
}

如果您以某个固定的时间间隔(例如每小时或其他时间)重置混合计时器,您可能会将时间设置回上次读取时间之前,这有点像一个微型夏令时问题。

【讨论】:

  • 是的,这也是我在阅读有关 StopWatch/QueryPerformanceCounter 的其他回复时想到的。感谢您提供代码示例。我敢肯定,其他人也会从中受益。
  • 顺便说一句,使用 DateTime.UtcNow 而不是 DateTime.Now 实际上对于时间戳很重要。否则,每年秋天,夏令时都会突然将时间戳值向后推一小时。
  • 谢谢。我已经必须实现我自己的 'Date'、'Time' 和 'DateAndTime' 类来解决 DateTime 的所有缺点(例如,没有对时区的内在支持)。在日期/时间方面重新发明轮子似乎很极端,但我实际上有点惊讶 DateTime 的设计和实现有多糟糕。您会认为,凭借他们拥有的所有资源和知识,他们本可以做得更好。
  • @FrankHileman:只要读者了解 Ian 的答案在 Datetime 上的精度限制为 16 毫秒。最好将此答案修改为线程安全的。
  • 这家伙似乎已经解决了这个答案中的所有线程问题,并提出了一个更准确的时钟,nimaara.com/2016/07/06/high-resolution-clock-in-net
【解决方案2】:

要获得高分辨率的滴答计数,请使用静态 Stopwatch.GetTimestamp() 方法:

long tickCount = System.Diagnostics.Stopwatch.GetTimestamp();
DateTime highResDateTime = new DateTime(tickCount);

看看 .NET 源代码:

    public static long GetTimestamp() {
        if(IsHighResolution) {
            long timestamp = 0;    
            SafeNativeMethods.QueryPerformanceCounter(out timestamp);
            return timestamp;
        }
        else {
            return DateTime.UtcNow.Ticks;
        }   
    }

这里的源代码:http://referencesource.microsoft.com/#System/services/monitoring/system/diagnosticts/Stopwatch.cs,69c6c3137e12dab4

【讨论】:

  • 感谢您的评论。我总是试图通过使用 .NET 框架的可用功能找到解决问题的最短方法。并分享它。
  • 它似乎不适用于 .NET 4.5.2 和 Win 10 x64。日期时间的年份为 0001。
【解决方案3】:

[接受的答案似乎不是线程安全的,并且它自己承认可以及时倒退导致重复的时间戳,因此这个替代答案]

如果您真正关心的(根据您的评论)实际上是一个以严格升序分配且与系统时间尽可能接近的唯一时间戳,您可以尝试这种替代方法:

public class HiResDateTime
{
   private static long lastTimeStamp = DateTime.UtcNow.Ticks;
   public static long UtcNowTicks
   {
       get
       {
           long orig, newval;
           do
           {
               orig = lastTimeStamp;
               long now = DateTime.UtcNow.Ticks;
               newval = Math.Max(now, orig + 1);
           } while (Interlocked.CompareExchange
                        (ref lastTimeStamp, newval, orig) != orig);

           return newval;
       }
   }
}

【讨论】:

  • +1。请参阅stackoverflow.com/questions/5608980/… 了解我的想法。
  • 郑重声明,我不承认我的方法可以及时倒退。我写道:“如果您以某个固定的时间间隔(例如每小时或其他时间)重置混合计时器,您将冒着将时间设置回上次读取时间之前的风险”,这解释了我为什么选择 not 定期重置计时器。但是,我的解决方案绝对不是线程安全的,而且我喜欢您解决方案的非重复功能。
  • 这个方案的局限在于它的resolution被DateTime限制为1ms或16ms(取决于硬件)。虽然时间戳是唯一的,但它们不会精确地测量刻度。 @MusiGenesis 方法将提供更精确的测量结果。
【解决方案4】:

这些建议看起来都太难了!如果您使用的是 Windows 8 或 Server 2012 或更高版本,请使用 GetSystemTimePreciseAsFileTime,如下所示:

[DllImport("Kernel32.dll", CallingConvention = CallingConvention.Winapi)]
static extern void GetSystemTimePreciseAsFileTime(out long filetime);

public DateTimeOffset GetNow()
{
    long fileTime;
    GetSystemTimePreciseAsFileTime(out fileTime);
    return DateTimeOffset.FromFileTime(fileTime);
}

这比 DateTime.Now 准确得多,而且准确度要高得多。

有关详细信息,请参阅 MSDN:http://msdn.microsoft.com/en-us/library/windows/desktop/hh706895(v=vs.85).aspx

【讨论】:

  • 当然,所有这些答案都是在 Windows 8 / Server 2012 出现之前编写的。即使在今天,假设客户有 8 / 2012 也不是一个好的假设,对于大多数代码。
【解决方案5】:

它确实返回操作系统已知的最准确的日期和时间。

操作系统还通过QueryPerformanceCounterQueryPerformanceFrequency(.NET Stopwatch 类)提供更高分辨率的计时。这些可让您确定时间间隔,但不提供日期和时间。您可能会争辩说,这些可以为您提供非常准确的时间和日期,但我不确定它们在很长一段时间内的偏差有多严重。

【讨论】:

  • 在应用程序启动时调用 DateTime.Now 是否有意义,然后运行 ​​StopWatch,然后将 StopWatch 的经过时间添加到 DateTime.Now 返回的初始值中以获得更多信息准确的时间戳?
  • Hermann,值的差异将是准确的,但基数 (DateTime.Now) 仍将受到约 20 毫秒的精度。您将获得的绝对值也是如此。
  • 是的,但考虑到您的系统时间可能并没有完全同步,偏移量不会有太大的不同。至少这样相对时间戳更准确。
【解决方案6】:

1)。如果需要高分辨率绝对精度:不能使用DateTime.Now 当它基于具有 15 毫秒间隔的时钟时(除非它 是可能的“滑动”阶段)。

相反,更好的绝对分辨率的外部来源 准确时间(例如 ntp),t1 下面,可以与高 分辨率计时器(StopWatch / QueryPerformanceCounter)。


2)。如果您只需要高分辨率:

DateTime.Now (t1) 与来自 高分辨率计时器 (StopWatch / QueryPerformanceCounter) (tt0)。

如果高分辨率计时器的当前值为tt,则 当前时间,t,是:

t = t1 + (tt - tt0)

3)。另一种方法可能是解开绝对时间和 金融事件的顺序:绝对时间的一个值 (15 毫秒的分辨率,可能会延迟几分钟)和一个 订单的值(例如,将一个值每次加一 时间并存储它)。订单的起始值可以是 基于某些系统全局值或从 应用程序启动的绝对时间。

此解决方案将更加健壮,因为它不依赖于 时钟/定时器的底层硬件实现 (可能因系统而异)。

【讨论】:

    【解决方案7】:

    对于这么简单的事情来说,工作量太大了。只需在交易的数据库中插入一个 DateTime。然后使用您的身份列获取交易订单,该列应该是一个递增值。

    如果您要插入多个数据库并在事后尝试协调,那么由于数据库时间的任何细微差异(甚至如您所说的 ns 增量),您将错误计算交易订单

    要解决多数据库问题,您可以公开每笔交易点击的单个服务以获取订单。该服务可以返回一个 DateTime.UtcNow.Ticks 和一个递增的交易编号。

    即使使用上述解决方案之一,任何从网络上延迟较长的位置进行交易的人都可能首先进行交易(现实世界),但由于延迟,它们会以错误的顺序输入。因此,必须考虑将交易放在数据库中,而不是放在用户的控制台中。

    【讨论】:

    • 虽然这确实给出了交易订单,但它并没有按照 OP 的要求为时间戳提供 sub-ms 准确度
    【解决方案8】:

    15 毫秒(实际上可以是 15-25 毫秒)精度基于 Windows 55 赫兹/65 赫兹计时器分辨率。这也是基本的 TimeSlice 周期。受影响的有Windows.Forms.TimerThreading.Thread.SleepThreading.Timer等。

    要获得准确的间隔,您应该使用Stopwatch class。如果可用,它将使用高分辨率。请尝试以下语句找出答案:

    Console.WriteLine("H = {0}", System.Diagnostics.Stopwatch.IsHighResolution);
    Console.WriteLine("F = {0}", System.Diagnostics.Stopwatch.Frequency);
    Console.WriteLine("R = {0}", 1.0 /System.Diagnostics.Stopwatch.Frequency);
    

    我得到 R=6E-08 秒,或 60 ns。它应该足以满足您的目的。

    【讨论】:

    • -1 因为 15ms 量化基于 Windows 调度程序。 ~55Hz 计时器分辨率来自一个较旧的、粗糙的计时器机制,每秒触发 18 个事件。
    • 我系统上的分辨率约为 2.8 滴答 (2.79365114840015E-07),这非常好。特别是与 DateTime.NowUtc 相比,它的准确度提高了 50,000 倍。
    • 音乐天才,1/55 = 18 毫秒,那么差异是多少?
    • @Henk:哎哟(“音乐天才”)。 :)
    • @Henk:对不起,我误读了您的原始答案,说 15 ms 是基于 Windows 55 ms 计时器分辨率(恰好是旧的分辨率-学校 18 Hz 计时器)。我的错。
    【解决方案9】:

    我将添加以下关于 MusiGenesis 的答案,用于重新同步时间。

    含义:我应该用什么时间重新同步(MusiGenesis 答案中的_maxIdle

    您知道,使用此解决方案您并不完全准确,这就是您重新同步的原因。 但是,您隐含地想要的是与 Ian Mercer 解决方案相同的东西:

    按严格升序分配的唯一时间戳

    因此,两次重新同步之间的时间量(_maxIdle 我们称之为 SyncTime)应该是 4 件事的函数:

    • DateTime.UtcNow 分辨率
    • 你想要的准确率
    • 您想要的精度级别
    • 估计您的机器的不同步率

    显然,对这个变量的第一个约束是:

    不同步率

    例如:我不希望我的准确度比 0.5s/hrs 或 1ms/day 等最差...(用糟糕的英语:我不希望比 0.5s/hrs=12s/day 更差)。

    因此,您无法获得比秒表在您的 PC 上为您提供的更好的准确度。这取决于您的不同步率,这可能不是恒定的。

    另一个约束是两次重新同步之间的最短时间:

    同步时间 >= DateTime.UtcNow 分辨率

    这里准确度和精确度是联系在一起的,因为如果您使用较高的准确度(例如存储在数据库中)但较低的准确度,您可能会破坏 Ian Mercer 语句,即严格的升序。

    注意:似乎 DateTime.UtcNow 的默认 Res 可能大于 15 毫秒(我的机器上为 1 毫秒) 点击链接: High accuracy DateTime.UtcNow

    举个例子:

    想象一下上面提到的不同步比率。

    大约 10 小时后,它提前了 5 秒。

    假设我想要一个微秒的精度。我的计时器 res 是 1ms (见上文注)

    逐点说明:

    • DateTime.UtcNow 分辨率:1ms
    • 准确率 >= 不同步率, 让我们尽可能准确:accuracy ratio = out-of-sync ratio
    • 您想要的精度级别:1 微秒
    • 估计您的机器的不同步率:0.5 秒/小时(这也是我的准确度)

    如果您每 10 秒重置一次,想象一下您在 9.999 秒,即重置前 1 毫秒。 您可以在此间隔期间拨打电话。您的函数将绘制的时间提前:0.5/3600*9.999s eq 1.39ms。 您将显示 10.000390 秒的时间。在 UtcNow 打勾后,如果您在 390 微秒内拨打电话,您的号码将低于前一个号码。如果这种不同步比率是随机的,取决于 CPU 负载或其他因素,情况会更糟。

    现在假设我将 SyncTime 设置为最小值 > 我每 1 毫秒重新同步一次

    做同样的想法会让我提前 0.139 微秒

    所以这里的另一个约束是:accuracy-ratio x SyncTime accuracy-ratio x SyncTime 是好的.

    问题已解决。

    所以快速回顾一下:

    • 检索您的计时器分辨率。
    • 计算不同步比率的估计值。
    • 准确率 >= 不同步比率估计,最佳准确率 = 不同步比率
    • 请考虑以下因素来选择您的精度级别:
    • 计时器分辨率
    • 您可以达到的最佳精度是计时器分辨率*2*不同步率

    对于上述比率(0.5/小时),正确的 SyncTime 应为 3.6 毫秒,因此向下舍入到 3 毫秒

    具有上述比率和1ms的定时器分辨率。如果您想要单刻精度级别(0.1 微秒),您需要不超过:180 毫秒/小时的不同步率。

    在其对自己的回答 MusiGenesis 的最后回答中表示:

    @Hermann:过去两个小时我一直在运行类似的测试(没有重置更正),并且基于秒表的计时器在 2 小时后仅提前约 400 毫秒运行,因此偏斜本身似乎是变量(但仍然非常严重)。我很惊讶偏斜如此糟糕。我想这就是 Stopwatch 在 System.Diagnostics 中的原因。 – 音乐创世纪

    所以秒表精度接近 200 毫秒/小时,几乎是我们的 180 毫秒/小时。为什么我们的号码和这个号码如此接近有什么联系吗?不知道。但是这个精度足以让我们实现Tick-Precision

    最佳精度级别:对于上面的示例,它是 0.27 微秒。

    但是,如果我在 9.999 毫秒和重新同步之间多次调用它会发生什么。

    对函数的 2 次调用最终可能会返回相同的 TimeStamp,两者的时间都是 9.999(因为我看不到更精确的)。为了避免这种情况,您不能触及精度级别,因为它通过上述关系链接到 SyncTime。因此,您应该针对这些情况实施 Ian Mercer 解决方案。

    请不要犹豫,评论我的回答。

    【讨论】:

    • MusiGenesis 的方法是优越的,只要(1)秒表的漂移很小(确实如此),并且(2)请求中有间隙,足够长的时间来执行重置。他缺少的一个功能是 Ian 的“自动增量”(可以很容易地添加)。在任何情况下都不应单独使用 Ian,如果您关心刻度精度:DateTime 仅精确到 16 毫秒。
    • 如果(2)有问题,因为没有足够的gap,那么MG的解决方案需要调整做“持续修正”:每次调用都要检查Now与上一次调用是否有变化.如果有,则重置计时器,但使用 Ian 的“Max(Now, lasttimestamp+1) 确保您始终增加。
    【解决方案10】:

    如果需要时间戳来执行基准测试,请使用StopWatch,它比 DateTime.Now 具有更好的精度。

    【讨论】:

      【解决方案11】:

      我认为这是解决此问题的最佳方法:

      long timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds();
      

      【讨论】:

      • 这个答案如何与这里的其他 10 个答案进行比较/对比?您能否详细说明为什么这可能会更好?
      • Jason,首先:1.) 它应该工作得更快 2.) 它不像所谓的绿色答案那样包含任何容易出错的逻辑(比如调用 DateTime.UtcNow 可以得到我们回到过去以及有关多线程环境的其他问题)。 3.) 这种实施方式将更受支持。 4.)最后一个:我没有真正测试其他答案,但我怀疑它们根本不会在一些可能的极端情况下工作。
      • 这很好,弗拉基米尔。我只建议在发布解决方案时陈述这些想法是好的。这将对其他遇到您的答案的人有所帮助。
      • 如果我的答案不明显,我很抱歉。所以要明确我的确切观点是使用这个 DateTimeOffset.Now.ToUnixTimeMilliseconds();而不是这个 HiResDateTime.TimestampNow;谢谢!
      猜你喜欢
      • 2014-03-20
      • 2021-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-09-26
      • 2019-01-11
      • 2021-06-06
      • 2013-12-28
      相关资源
      最近更新 更多