Environment.TickCount vs DateTime.Now
是否可以使用Environment.TickCount
来计算时间跨度?
int start = Environment.TickCount; // Do stuff int duration = Environment.TickCount - start; Console.WriteLine("That took " + duration " ms");
由于TickCount
已经过签名,并且在25天之后会翻转(所有32位需要50天才能完成),但是如果要对math有任何意义,则必须取消已签名的位),似乎太有风险。
我正在使用DateTime.Now
。 这是做这个最好的方法吗?
DateTime start = DateTime.Now; // Do stuff TimeSpan duration = DateTime.Now - start; Console.WriteLine("That took " + duration.TotalMilliseconds + " ms");
使用秒表类。 在msdn上有一个体面的例子: http : //msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx
Stopwatch stopWatch = Stopwatch.StartNew(); Thread.Sleep(10000); stopWatch.Stop(); // Get the elapsed time as a TimeSpan value. TimeSpan ts = stopWatch.Elapsed;
Environment.TickCount基于GetTickCount() WinAPI函数。 以毫秒为单位但实际精度约为15.6毫秒。 所以你不能测量更短的时间间隔(或者你会得到0)
注意:返回的值是Int32,所以这个计数器会翻转〜49.7天。 你不应该用它来衡量这么长的时间间隔。
DateTime.Ticks基于GetSystemTimeAsFileTime ()WinAPI函数。 它在100s纳秒(十分之一的微型金)。 DateTime.Ticks的实际精度取决于系统。 在XP上,系统时钟的增量约为15.6毫秒,与Environment.TickCount中的一样。 在Windows 7上,它的精度是1毫秒(而Environemnt.TickCount的仍然是15.6毫秒),但是如果使用省电scheme(通常在笔记本电脑上),它也可以降低到15.6毫秒。
秒表是基于QueryPerformanceCounter() WinAPI函数(但如果系统不支持高分辨率性能计数器,则使用DateTime.Ticks)
在使用StopWatch之前注意两个问题:
- 在多处理器系统上可能不可靠(请参阅MS kb895980 , kb896256 )
- 如果CPU频率变化,这可能是不可靠的(阅读本文)
您可以使用简单的testing来评估系统的精度:
static void Main(string[] args) { int xcnt = 0; long xdelta, xstart; xstart = DateTime.UtcNow.Ticks; do { xdelta = DateTime.UtcNow.Ticks - xstart; xcnt++; } while (xdelta == 0); Console.WriteLine("DateTime:\t{0} ms, in {1} cycles", xdelta / (10000.0), xcnt); int ycnt = 0, ystart; long ydelta; ystart = Environment.TickCount; do { ydelta = Environment.TickCount - ystart; ycnt++; } while (ydelta == 0); Console.WriteLine("Environment:\t{0} ms, in {1} cycles ", ydelta, ycnt); Stopwatch sw = new Stopwatch(); int zcnt = 0; long zstart, zdelta; sw.Start(); zstart = sw.ElapsedTicks; // This minimizes the difference (opposed to just using 0) do { zdelta = sw.ElapsedTicks - zstart; zcnt++; } while (zdelta == 0); sw.Stop(); Console.WriteLine("StopWatch:\t{0} ms, in {1} cycles", (zdelta * 1000.0) / Stopwatch.Frequency, zcnt); Console.ReadKey(); }
你为什么担心滚转? 只要你正在测量的持续时间是24.9天,你计算相对的持续时间,你很好。 无论系统运行多长时间,只要您只关心自己在运行时间中的部分时间(而不是直接对开始点和结束点执行小于或大于比较)即可。 即这个:
int before_rollover = Int32.MaxValue - 5; int after_rollover = Int32.MinValue + 7; int duration = after_rollover - before_rollover; Console.WriteLine("before_rollover: " + before_rollover.ToString()); Console.WriteLine("after_rollover: " + after_rollover.ToString()); Console.WriteLine("duration: " + duration.ToString());
正确打印:
before_rollover: 2147483642 after_rollover: -2147483641 duration: 13
你不必担心标志位。 像C一样,C#让CPU处理这个问题。
这是我之前在embedded式系统中遇到的常见情况。 例如,我不会直接比较beforerollover <afterrollover。 我会始终执行减法来查找需要翻转的持续时间,然后根据持续时间进行任何其他计算。
您可能需要System.Diagnostics.StopWatch
。
如果您正在查找Environment.TickCount
的function,但没有创build新的Stopwatch
对象的开销,则可以使用静态Stopwatch.GetTimestamp()
方法(以及Stopwatch.Frequency
)来计算长时间跨度。 因为GetTimestamp()
返回一个很长的时间,所以在很长很长的时间内(超过10万年,在我用来写这个的机器上GetTimestamp()
它不会溢出。 它比Environment.TickCount
还要精确得多,最大分辨率为10到16毫秒。
使用
System.Diagnostics.Stopwatch
它有一个属性叫
EllapsedMilliseconds
Environment.TickCount似乎比其他解决scheme快得多:
Environment.TickCount 71 DateTime.UtcNow.Ticks 213 sw.ElapsedMilliseconds 1273
测量结果由以下代码生成:
static void Main( string[] args ) { const int max = 10000000; // // for ( int j = 0; j < 3; j++ ) { var sw = new Stopwatch(); sw.Start(); for ( int i = 0; i < max; i++ ) { var a = Environment.TickCount; } sw.Stop(); Console.WriteLine( $"Environment.TickCount {sw.ElapsedMilliseconds}" ); // // sw = new Stopwatch(); sw.Start(); for ( int i = 0; i < max; i++ ) { var a = DateTime.UtcNow.Ticks; } sw.Stop(); Console.WriteLine( $"DateTime.UtcNow.Ticks {sw.ElapsedMilliseconds}" ); // // sw = new Stopwatch(); sw.Start(); for ( int i = 0; i < max; i++ ) { var a = sw.ElapsedMilliseconds; } sw.Stop(); Console.WriteLine( $"sw.ElapsedMilliseconds {sw.ElapsedMilliseconds}" ); } Console.WriteLine( "Done" ); Console.ReadKey(); }
这里有一个更新和刷新总结,可能是这个主题中最有用的答案和评论+额外的基准和变体:
第一件事:正如其他人在评论中指出的那样,事情已经改变了过去的几年,用“现代”的Windows(Win XP ++)和.NET,现代硬件没有或很less有理由不使用Stopwatch()。 详情请参阅MSDN 。 语录:
“QPC的准确度是否受到电源pipe理或Turbo Boost技术造成的处理器频率变化的影响?
不可以。如果处理器具有不变的TSC ,则QPC不受这些更改的影响。 如果处理器没有不变的TSC,则QPC将恢复为不受处理器频率变化或Turbo Boost技术影响的平台硬件定时器。QPC可以在多处理器系统,多核系统和超线程系统上可靠工作吗?
是如何确定和validationQPC在我的机器上工作?
你不需要执行这样的检查。哪些处理器具有非恒定的TSC? [..读进一步..]“
但是如果你不需要Stopwatch()的精确度,或者至less想要知道Stopwatch(基于静态或基于实例)和其他可能的变体的性能,请继续阅读:
我从cskwg接手了上面的基准,并扩展了更多变体的代码。 我测量了几年前的i7 4700 MQ和C#7与VS 2017(更确切地说,用.NET 4.5.2编译,尽pipe二进制文字,它是C#6(使用这个:string文字和'使用静态'),特别是Stopwatch()的性能似乎比上述基准提高了。
这是循环中1000万次重复结果的一个例子,与往常一样,绝对值并不重要,但即使相对值在其他硬件上也可能不同:
32位,无需优化的发布模式:
测量:GetTickCount64()[毫秒]:275
测量:Environment.TickCount [ms]:45
测量:DateTime.UtcNow.Ticks [ms]: 167
测量:秒表:.ElapsedTicks [ms]:277
测量:秒表:.ElapsedMilliseconds [毫秒]:548
测量:静态Stopwatch.GetTimestamp [毫秒]:193
测量:秒表+转换为date时间[毫秒]:551
与DateTime.Now.Ticks [ms]比较: 9010
32位,发布模式,优化:
测量:GetTickCount64()[毫秒]:198
测量:Environment.TickCount [ms]:39
测量:DateTime.UtcNow.Ticks [毫秒]:66 (!)
测量:秒表:.ElapsedTicks [毫秒]:175
测量:秒表:.ElapsedMilliseconds [毫秒]: 491
测量:静态Stopwatch.GetTimestamp [毫秒]:175
测量:秒表+转换为date时间[毫秒]: 510
与DateTime.Now.Ticks [ms]比较: 8460
64位,无需优化的发布模式:
测量:GetTickCount64()[毫秒]:205
测量:Environment.TickCount [ms]:39
测量:DateTime.UtcNow.Ticks [毫秒]: 127
测量:秒表:.ElapsedTicks [ms]:209
测量:秒表:.ElapsedMilliseconds [ms]:285
测量:静态Stopwatch.GetTimestamp [毫秒]:187
测量:秒表+转换为date时间[毫秒]:319
与DateTime.Now.Ticks [ms]比较:3040
64位,发布模式,优化:
测量:GetTickCount64()[毫秒]:148
测量:Environment.TickCount [毫秒]:31 (还值得吗?)
测量:DateTime.UtcNow.Ticks [毫秒]:76 (!)
测量:秒表:.ElapsedTicks [ms]:178
测量:秒表:.ElapsedMilliseconds [毫秒]:226
测量:静态Stopwatch.GetTimestamp [毫秒]:175
测量:秒表+转换为date时间[毫秒]:246
与DateTime.Now.Ticks [ms]比较:3020
可能非常有趣的是, 创buildDateTime值来打印秒表时间似乎几乎没有成本 。 有趣的是更实际的学术方法是静态秒表稍微快一些(如预期)。 一些优化点非常有趣。 例如,我不能解释为什么Stopwatch.ElapsedMilliseconds只有32位比其他变体如此慢,例如静态的。 这和DateTime.Now比64位的速度快了一倍以上。
你可以看到:只有数百万的处决,秒表的时间才会变得重要。 如果真是这样的话(但要小心微观优化太早),可能有趣的是,使用GetTickCount64(),但特别是使用DateTime.UtcNow ,你有一个精度比秒表更less的64位(长)定时器,但更快,所以你不必乱搞32位“丑陋的”Environment.TickCount。
正如所料,DateTime.Now是迄今为止最慢的。
如果你运行它,代码也检索当前秒表的准确性和更多。
以下是完整的基准代码:
using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; using static System.Environment;
[…]
[DllImport("kernel32.dll") ] public static extern UInt64 GetTickCount64(); // Retrieves a 64bit value containing ticks since system start static void Main(string[] args) { const int max = 10_000_000; const int n = 3; Stopwatch sw; // Following Process&Thread lines according to tips by Thomas Maierhofer: https://codeproject.com/KB/testing/stopwatch-measure-precise.aspx // But this somewhat contradicts to assertions by MS in: https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396#Does_QPC_reliably_work_on_multi-processor_systems__multi-core_system__and_________systems_with_hyper-threading Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1); // Use only the first core Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High; Thread.CurrentThread.Priority = ThreadPriority.Highest; Thread.Sleep(2); // warmup Console.WriteLine($"Repeating measurement {n} times in loop of {max:N0}:{NewLine}"); for (int j = 0; j < n; j++) { sw = new Stopwatch(); sw.Start(); for (int i = 0; i < max; i++) { var tickCount = GetTickCount64(); } sw.Stop(); Console.WriteLine($"Measured: GetTickCount64() [ms]: {sw.ElapsedMilliseconds}"); // // sw = new Stopwatch(); sw.Start(); for (int i = 0; i < max; i++) { var tickCount = Environment.TickCount; // only int capacity, enough for a bit more than 24 days } sw.Stop(); Console.WriteLine($"Measured: Environment.TickCount [ms]: {sw.ElapsedMilliseconds}"); // // sw = new Stopwatch(); sw.Start(); for (int i = 0; i < max; i++) { var a = DateTime.UtcNow.Ticks; } sw.Stop(); Console.WriteLine($"Measured: DateTime.UtcNow.Ticks [ms]: {sw.ElapsedMilliseconds}"); // // sw = new Stopwatch(); sw.Start(); for (int i = 0; i < max; i++) { var a = sw.ElapsedMilliseconds; } sw.Stop(); Console.WriteLine($"Measured: Stopwatch: .ElapsedMilliseconds [ms]: {sw.ElapsedMilliseconds}"); // // sw = new Stopwatch(); sw.Start(); for (int i = 0; i < max; i++) { var a = Stopwatch.GetTimestamp(); } sw.Stop(); Console.WriteLine($"Measured: static Stopwatch.GetTimestamp [ms]: {sw.ElapsedMilliseconds}"); // // DateTime dt=DateTime.MinValue; // just init sw = new Stopwatch(); sw.Start(); for (int i = 0; i < max; i++) { var a = new DateTime(sw.Elapsed.Ticks); // using variable dt here seems to make nearly no difference } sw.Stop(); //Console.WriteLine($"Measured: Stopwatch+conversion to DateTime [s] with millisecs: {dt:s.fff}"); Console.WriteLine($"Measured: Stopwatch+conversion to DateTime [ms]: {sw.ElapsedMilliseconds}"); Console.WriteLine(); } // // sw = new Stopwatch(); var tickCounterStart = Environment.TickCount; sw.Start(); for (int i = 0; i < max/10; i++) { var a = DateTime.Now.Ticks; } sw.Stop(); var tickCounter = Environment.TickCount - tickCounterStart; Console.WriteLine($"Compare that with DateTime.Now.Ticks [ms]: {sw.ElapsedMilliseconds*10}"); Console.WriteLine($"{NewLine}General Stopwatch information:"); if (Stopwatch.IsHighResolution) Console.WriteLine("- Using high-resolution performance counter for Stopwatch class."); else Console.WriteLine("- Using high-resolution performance counter for Stopwatch class."); double freq = (double)Stopwatch.Frequency; double ticksPerMicroSec = freq / (1000d*1000d) ; // microsecond resolution: 1 million ticks per sec Console.WriteLine($"- Stopwatch accuracy- ticks per microsecond (1000 ms): {ticksPerMicroSec:N1}"); Console.WriteLine(" (Max. tick resolution normally is 100 nanoseconds, this is 10 ticks/microsecond.)"); DateTime maxTimeForTickCountInteger= new DateTime(Int32.MaxValue*10_000L); // tickCount means millisec -> there are 10.000 milliseconds in 100 nanoseconds, which is the tick resolution in .NET, eg used for TimeSpan Console.WriteLine($"- Approximated capacity (maxtime) of TickCount [dd:hh:mm:ss] {maxTimeForTickCountInteger:dd:HH:mm:ss}"); // this conversion from seems not really accurate, it will be between 24-25 days. Console.WriteLine($"{NewLine}Done."); while (Console.KeyAvailable) Console.ReadKey(false); Console.ReadKey(); }
您应该使用Stopwatch类。
我使用Environment.TickCount,因为:
- Stopwatch类不在Compact Framework中。
- Stopwatch使用与TickCount相同的底层定时机制,所以结果不会有多less精确。
- TickCount的绕回问题在很大程度上不太可能受到影响 (您必须让计算机运行27天,然后尝试测量恰好跨越整个环绕时间的时间),即使您击中它的结果将是一个巨大的负面时间跨度(所以它会脱颖而出)。
话虽如此,我也build议使用秒表,如果它是可用的。 或者你可以花大约1分钟,写一个类似Stopwatch的类来包装Environment.TickCount。
顺便说一句,在Stopwatch文档中我没有看到任何提到底层定时器机制的绕回问题,所以我不会惊讶于Stopwatch遭受同样的问题。 但是,我再也不会为此担心了。
我想说的是将它包装成秒表课程,但Grzenio已经说了正确的事情,所以我会给他一个提升。 这样的封装决定了哪种方式更好,这可能会及时改变。 我记得在某些系统上花费多less时间会感到震惊,因此有一个能够实现最好技术的地方是非常重要的。
对于一次性计时,写入更为简单
Stopwatch stopWatch = Stopwatch.StartNew(); ...dostuff... Debug.WriteLine(String.Format("It took {0} milliseconds", stopWatch.EllapsedMilliseconds)));
我猜想TickCount中的大概不太可能的环绕对于StopWatch来说更是不那么重要,因为ElapsedTicks字段很长。 在我的机器上,秒表是高分辨率,每秒2.4e9滴答。 即使以这样的速度,将超过121年来溢出蜱字段。 当然,我不知道封面上发生了什么事情,所以拿一点盐。 不过,我注意到StopWatch的文档甚至没有提到环绕问题,而TickCount的文档却没有。