在.NET / C#中如何获得刻度精度的时间戳?
到目前为止,我使用DateTime.Now
来获取时间戳,但是我注意到如果你在一个循环中打印DateTime.Now
,你会发现它在大约的离散跳跃中增加。 15毫秒 但是对于我的应用程序中的某些场景,我需要获得最准确的时间戳,最好是使用tick(= 100 ns)精度。 有任何想法吗?
更新:
显然, StopWatch
/ QueryPerformanceCounter
是要走的路,但它只能用于测量时间,所以我想在调用DateTime.Now
时,应用程序启动,然后只有StopWatch
运行,然后只是添加从StopWatch
经过的时间从DateTime.Now
返回的初始值。 至less应该给我准确的相对时间戳,对吗? 你怎么看待这个(黑客)?
注意:
StopWatch.ElapsedTicks
与StopWatch.Elapsed.Ticks
不同! 我用前者假设1 tick = 100 ns,但在这种情况下1 tick = 1 / StopWatch.Frequency
。 所以要得到相当于DateTime的滴答使用StopWatch.Elapsed.Ticks
。 我刚刚学会了这个难题。
笔记2:
使用StopWatch方法,我发现它与实时不同步。 大约10个小时之后,它超前了5秒。 所以我想我们不得不重新同步每X左右,其中X可能是1小时30分钟,15分钟等我不知道什么是最佳时间resyncing将是因为每个resync会改变偏移量可以是最多20毫秒。
DateTime.Now
读取的系统时钟的值只是每15 ms左右更新一次(或在某些系统上为10 ms),这就是为什么时间量化在这些时间间隔的原因。 由于您的代码运行在multithreading操作系统中,因此存在额外的量化效应,因此您的应用程序不是“活动的”,因此不能测量实际的当前时间。
由于您正在寻找一个超精确的时间戳值(而不是仅仅计时一个任意的持续时间), Stopwatch
类本身不会做你所需要的。 我想你必须自己做一个DateTime
/ Stopwatch
混合。 当您的应用程序启动时,您将存储当前的DateTime.UtcNow
值(即应用程序启动时的粗分辨率时间),然后启动一个Stopwatch
对象,如下所示:
DateTime _starttime = DateTime.UtcNow; Stopwatch _stopwatch = Stopwatch.StartNew();
然后,每当你需要一个高分辨率的DateTime
值,你会得到这样的:
DateTime highresDT = _starttime.AddTicks(_stopwatch.Elapsed.Ticks);
您也可能需要定期重置_starttime和_stopwatch,以防止由于与系统时间的过度同步而导致的时间过长(尽pipe我不确定这是否会发生,而且无论如何也需要很长时间才能发生) 。
更新 :因为看起来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(); } }
如果您以某个固定时间间隔(例如每小时或某些时间)重置混合计时器,则可能会在最后一次读取时间之前重新设置时间,就像微型夏令时问题。
[被接受的答案似乎不是线程安全的,并且由于自己的承认可以及时倒退造成重复的时间戳,因此这个替代答案]
如果你真正关心的(根据你的评论)实际上是一个独特的时间戳,它是按严格的升序排列的,并且尽可能接近系统时间,你可以尝试这种替代方法:
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; } } }
这些build议都看起来太辛苦了! 如果您使用的是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
它确实返回操作系统已知的最准确的date和时间。
操作系统还通过QueryPerformanceCounter
和QueryPerformanceFrequency
(.NET Stopwatch
类)提供更高分辨率的时间。 这些让你时间间隔,但不给你date和时间。 你可能会争辩说,这些能够给你一个非常准确的时间和日子,但是我不确定他们在很长一段时间内有多么严重。
要获得高分辨率的滴答计数,请使用静态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
1)。 如果您需要高分辨率的绝对精度:当基于时间间隔为15毫秒的时钟时,不能使用DateTime.Now
(除非可以“滑动”相位)。
相反,更高分辨率的绝对准确时间(例如ntp), t1
以下的外部源可以与高分辨率定时器(StopWatch / QueryPerformanceCounter)结合使用。
2)。 如果你只需要高分辨率:
从高分辨率计时器(StopWatch / QueryPerformanceCounter)( tt0
)取样DateTime.Now
( t1
)一次。
如果高分辨率定时器的当前值是tt
那么当前时间t
是:
t = t1 + (tt - tt0)
3)。 另一种方法可能是解决金融事件的绝对时间和顺序:绝对时间的一个值(15毫秒的分辨率,可能是几分钟的时间)和一个订单的值(例如,每次增加一个值并存储那)。 订单的起始值可以基于某个系统全局值,也可以从应用程序启动时的绝对时间导出。
这个解决scheme将更加强大,因为它不依赖于时钟/定时器的底层硬件实现(这可能在不同的系统之间有所不同)。
对于这么简单的工作来说,这是太多的工作。 只需在交易数据库中插入一个DateTime即可。 那么要获得交易订单使用你的身份栏应该是一个递增的价值。
如果您插入多个数据库并尝试在事实之后进行协调,那么由于数据库时间稍有差异,您将错误地计算交易订单(如您所说,即使是ns增量)
为了解决多个数据库问题,您可以公开一个单一的服务,每个交易命中以获得一个订单。 该服务可能会返回一个DateTime.UtcNow.Ticks和一个递增的交易号码。
即使使用以上任何一种解决scheme,从networking上的一个位置进行更多延迟的交易,也可能首先进行交易(现实世界),但由于等待时间的原因,它们的input顺序错误。 因此,贸易必须考虑放在数据库,而不是用户的控制台上。
15 ms(实际上可以是15-25 ms)精度基于Windows 55 Hz / 65 Hz定时器分辨率。 这也是TimeSlice的基本时期。 受影响的是Windows.Forms.Timer , Threading.Thread.Sleep , Threading.Timer等
要获得准确的时间间隔,您应该使用秒表类 。 它将使用高分辨率(如果可用)。 尝试以下语句来找出:
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纳秒。 它应该足够你的目的。
如果需要时间戳执行基准testing,使用比DateTime.Now更好的精确度的StopWatch 。
我会添加以下有关MusiGenesis答复重新同步时间。
含义:我应该什么时候重新同步( MusiGenesis答案中的_maxIdle )
你知道这个解决scheme并不完全准确,那就是你重新同步的原因。 但是,你所隐含的要求与Ian Mercer的解决scheme是一样的:
按严格升序分配的唯一时间戳
因此,两次重新同步之间的时间量( _maxIdle
让我们称之为_maxIdle
)应该是4件事情的函数:
-
DateTime.UtcNow
分辨率 - 你想要的精度的比例
- 你想要的精确度
- 估算您机器的不同步比率
显然,这个variables的第一个约束是:
不同步比率<=准确率
例如:我不希望我的准确性比0.5s / hrs或1ms / day等更差(英文不好:我不想要比0.5s / hrs = 12s / day更错误)。
所以你不能达到比你的PC上的秒表提供更好的准确性。 这取决于你的不同步比率,这可能不是恒定的。
另一个约束是两次重新同步之间的最短时间:
Synctime> = DateTime.UtcNow
分辨率
在这里,精度和精度是相互关联的,因为如果你使用高精度(例如存储在数据库中)而精度较低,那么你可能会打破严格的升序的伊恩·默瑟(Ian Mercer)声明 。
注意: 似乎DateTime.UtcNow可能有一个更大的默认Res比15毫秒(我的机器上1毫秒)请点击链接: 高精度DateTime.UtcNow
举个例子:
想象一下上面所说的不同步比率。
大约10个小时之后,它超前了5秒。
说我想要一个微秒的精度。 我的计时器资源是1ms (见上面注)
所以逐点:
-
DateTime.UtcNow
分辨率: 1ms - 准确率> =不同步比率,让我们采取最准确的可能: 准确率=不同步比率
- 您想要的精度级别: 1微秒
- 估计你的机器的不同步比率: 0.5s /小时 (这也是我的准确性)
如果你每10秒复位一次,想象你的复位前的9.999s,1ms。 在这里您可以在此间隔内拨打电话。 您的function将绘制的时间提前:0.5 / 3600 * 9.999s当量1.39ms。 您将显示10.000390秒的时间。 在UtcNow打勾后,如果你在390微秒内拨打电话,你的电话号码会比前一个电话号码less。 如果这个不同步的比率是随机的CPU负载或其他的东西,是更糟糕的。
现在让我们说,我把SyncTime的最小值>我每1ms重新同步
做同样的想法会使我的时间提前0.139微秒(低于我想要的精度)。 因此,如果我在9.999 ms调用函数,那么在复位之前1microsec将绘制9.999。 而我刚刚将10.000的阴谋。 我会有一个很好的秩序。
所以这里的另一个约束是:准确率x同步时间<精度等级,可以说是肯定的,因为数量可以四舍五入, 精度比x同步时间<精度等级/ 2是好的。
问题已解决。
所以快速回顾一下:
- 检索您的计时器分辨率。
- 计算不同步比率的估计值。
- 准确率> =不同步比率估计值, 最佳准确度=不同步比率
- select您的精确度水平考虑以下几点:
- 定时器分辨率<= SyncTime <= PrecisionLevel /(2 *精度比)
- 您可以达到的最佳精度是定时器分辨率* 2 *不同步比率
对于上述比率(0.5 /小时),正确的同步时间将是3.6ms,所以四舍五入到3ms 。
具有上述比例和1ms的定时器分辨率。 如果你想要一个单精度级 (0.1microsec),你需要一个不超过180ms /小时的不同步比率。
在最后一个回答自己的答案MusiGenesis状态:
@Hermann:在过去的两个小时里,我一直在进行类似的testing(没有进行复位校正),而基于秒表的定时器只在2小时后运行了大约400毫秒,所以偏斜本身似乎是可变的仍然非常严重)。 我很惊讶,这个歪斜是不好的; 我想这就是Stopwatch在System.Diagnostics中的原因。 – MusiGenesis
所以秒表的准确度接近200ms /小时,几乎是180ms /小时。 我们的号码和这个号码如此接近,有什么关系吗? 不知道。 但是这个准确度对于我们来说可以达到Tick-Precision
最好的PrecisionLevel:对于上面的例子是0.27微秒。
然而,如果我在9.999ms和重新同步之间多次调用它,会发生什么情况。
2调用函数可能最终返回相同的TimeStamp时间将是9.999两者(因为我没有看到更多的精度)。 为了避免这种情况,您不能触摸精度级别,因为它通过上述关系链接到SyncTime。 所以你应该为这些情况实施Ian Mercer解决scheme。
请不要犹豫,评论我的答案。