Environment.TickCount与DateTime.Now

时间:2008-10-28 13:40:20

标签: c# .net datetime timespan

使用Environment.TickCount计算时间跨度是否可行?

int start = Environment.TickCount;
// Do stuff
int duration = Environment.TickCount - start;
Console.WriteLine("That took " + duration " ms");

因为TickCount已签名并且将在25天后翻转(需要50天才能达到所有32位,但如果您想对数学有任何意义,则必须废弃已签名的位),似乎喜欢这样做太冒险了。

我正在使用DateTime.Now。这是最好的方法吗?

DateTime start = DateTime.Now;
// Do stuff
TimeSpan duration = DateTime.Now - start;
Console.WriteLine("That took " + duration.TotalMilliseconds + " ms");

14 个答案:

答案 0 :(得分:86)

Environment.TickCount 基于GetTickCount() WinAPI功能。它以毫秒为单位 但它的实际精度约为15.6毫秒。所以你不能测量更短的时间间隔(或者你会得到0)

注意:返回的值是Int32,因此此计数器每隔~49.7天滚动一次。你不应该用它来衡量这么长的间隔。

DateTime.Ticks 基于GetSystemTimeAsFileTime()WinAPI函数。它在100s纳秒(十分之一的微型胶片)。 DateTime.Ticks的实际精度取决于系统。在XP上,系统时钟的增量约为15.6 ms,与Environment.TickCount相同。 在Windows 7上,它的精度是1毫秒(而Environemnt.TickCount仍然是15.6毫秒),但如果使用省电方案(通常在笔记本电脑上),它也可以降至15.6毫秒。

秒表基于QueryPerformanceCounter() WinAPI功能(但如果您的系统不支持高分辨率性能计数器,则使用DateTime.Ticks)

在使用StopWatch之前,请注意两个问题:

  • 在多处理器系统上可能不可靠(请参阅MS kb895980kb896256
  • 如果CPU频率变化(阅读this文章)
  • ,则可能不可靠

您可以通过简单的测试评估系统的精度:

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();
}

答案 1 :(得分:67)

使用秒表课程。在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;

答案 2 :(得分:24)

你为什么担心翻车?只要您测量的持续时间低于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处理这个。

这是我在嵌入式系统中计算时间之前遇到的常见情况。我永远不会比较beforerollover<例如,直接后滚动。我总是会执行减法来找到考虑翻转的持续时间,然后根据持续时间进行任何其他计算。

答案 3 :(得分:10)

您可能需要System.Diagnostics.StopWatch

答案 4 :(得分:9)

如果您正在寻找Environment.TickCount的功能但没有创建新Stopwatch对象的开销,则可以使用静态Stopwatch.GetTimestamp()方法(以及Stopwatch.Frequency )计算长时间跨度。因为GetTimestamp()返回long,所以它不会在非常长的时间内溢出(超过10万年,在我用来编写它的机器上)。它也比Environment.TickCount准确得多,其最大分辨率为10到16毫秒。

答案 5 :(得分:8)

使用

System.Diagnostics.Stopwatch

它有一个名为

的属性
EllapsedMilliseconds

答案 6 :(得分:6)

Environment.TickCount似乎比其他解决方案快得多:

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();
}

答案 7 :(得分:5)

这是一个更新和更新的摘要,可能是最有用的答案&amp;此主题中的评论+额外的基准和变体:

首先要做的事情:正如其他人在评论中指出的那样,事情在过去的几年里发生了变化,并且现代化了#34; Windows(Win XP ++)和.NET以及现代硬件没有或几乎没有理由不使用Stopwatch()。 有关详细信息,请参阅MSDN。语录:

  

&#34; QPC精度是否受到电源管理或Turbo Boost技术引起的处理器频率变化的影响?   否。如果处理器具有不变的TSC ,则QPC不受这些更改的影响。如果处理器没有不变的TSC,QPC将恢复到不受处理器频率变化或Turbo Boost技术影响的平台硬件定时器。

     

QPC能否可靠地在多处理器系统,多核系统和具有超线程的系统上工作?   

     

如何确定并验证QPC在我的机器上是否正常工作?   您不需要执行此类检查。

     

哪些处理器具有非常数TSC?   [..进一步..]   &#34;

但是如果你不需要秒表()的精确度,或者至少想知道秒表(静态与基于实例)和其他可能变体的性能,请继续阅读:

我从cskwg接管了上面的基准测试,并扩展了代码以获得更多变体。我用VS 2017测量了几年前的i7 4700 MQ和C#7(更准确地说,用.NET 4.5.2编译,尽管是二进制文字,但它是C#6(用于此:字符串文字和&#39) ;使用静态&#39;。特别是与上述基准相比,秒表()表现似乎有所改善。

这是一个循环中1000万次重复结果的例子,一如既往,绝对值并不重要,但即使相对值在其他硬件上也可能不同:

32位,没有优化的发布模式:

  

测量:GetTickCount64()[ms]:275
  测量:Environment.TickCount [ms]:45
  测量:DateTime.UtcNow.Ticks [ms]: 167
  测量:秒表:.ElapsedTicks [ms]:277
  测量:秒表:.ElapsedMilliseconds [ms]:548
  测量:静态秒表.GetTimestamp [ms]:193
  测量:秒表+转换为DateTime [ms]:551
  将其与DateTime.Now.Ticks [ms]: 9010

进行比较

32位,发布模式,已优化:

  

测量:GetTickCount64()[ms]:198
  测量:Environment.TickCount [ms]:39
  测量:DateTime.UtcNow.Ticks [ms]:66 (!)
  测量:秒表:.ElapsedTicks [ms]:175
  测量:秒表:.ElapsedMilliseconds [ms]: 491
  测量:静态秒表.GetTimestamp [ms]:175
  测量:秒表+转换为DateTime [ms]: 510
  与DateTime.Now.Ticks [ms]: 8460

进行比较

64位,没有优化的发布模式:

  

测量:GetTickCount64()[ms]:205
  测量:Environment.TickCount [ms]:39
  测量:DateTime.UtcNow.Ticks [ms]: 127
  测量:秒表:.ElapsedTicks [ms]:209
  测量:秒表:.ElapsedMilliseconds [ms]:285
  测量:静态秒表.GetTimestamp [ms]:187
  测量:秒表+转换为DateTime [ms]:319
  将其与DateTime.Now.Ticks [ms]:3040

进行比较

64位,发布模式,已优化:

  

测量:GetTickCount64()[ms]:148
  测量:Environment.TickCount [ms]:31 (它还值得吗?)
  测量:DateTime.UtcNow.Ticks [ms]:76 (!)
  测量:秒表:.ElapsedTicks [ms]:178
  测量:秒表:.ElapsedMilliseconds [ms]:226
  测量:静态秒表.GetTimestamp [ms]:175
  测量:秒表+转换为DateTime [ms]:246
  将其与DateTime.Now.Ticks [ms]:3020

进行比较

可能非常有趣的是,创建一个DateTime值来打印秒表时间似乎几乎没有成本。有趣的是更具学术性而非实用性的方式是静态秒表略快(如预期的那样)。一些优化点非常有趣。 例如,我无法解释为什么只有32位的Stopwatch.ElapsedMilliseconds与它的其他变体(例如静态变体)相比是如此之慢。这和DateTime.Now的速度是64位的两倍多。

你可以看到:只有数百万次执行,秒表的时间开始变得重要。如果真的是这种情况(但要注意过早地进行微优化),使用GetTickCount64()可能会很有趣,但特别是对于 DateTime.UtcNow ,你有一个64位(长)计时器精度低于秒表,但更快,所以你不必乱用32位&#34;丑陋&#34; 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, e.g. 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();
    }

答案 8 :(得分:0)

您应该使用Stopwatch课程。

答案 9 :(得分:0)

我使用Environment.TickCount,因为:

  1. Stopwatch类不在Compact Framework中。
  2. 秒表使用与TickCount相同的基础计时机制,因此结果不会更准确或更准确。
  3. TickCount的环绕问题在很大程度上不太可能被击中(您必须让计算机运行27天,然后尝试测量恰好跨越换行的时间(即时)),即使你确实击中了它,结果也会是一个巨大的负时间跨度(所以它会很突出)。
  4. 话虽如此,我还建议使用秒表,如果你可以使用的话。或者你可以花大约1分钟写一个类似秒表的类来包装Environment.TickCount。

    顺便说一句,我在秒表文档中没有看到提及底层计时器机制的环绕问题,所以我不会惊讶地发现秒表遇到了同样的问题。但同样,我也不会花时间担心它。

答案 10 :(得分:0)

我打算把它包装成一个秒表课,但Grzenio已经说了正确的话,所以我会给他一个提升。这种封装因素决定了哪种方式更好,这可以及时改变。我记得在某些系统上花费多少时间感到震惊,因此拥有一个可以实现最佳技术的地方非常重要。

答案 11 :(得分:0)

对于一次性定时,写入

更简单
Stopwatch stopWatch = Stopwatch.StartNew();
...dostuff...
Debug.WriteLine(String.Format("It took {0} milliseconds",
                              stopWatch.EllapsedMilliseconds)));

考虑到ElapsedTicks字段很长,我猜想TickCount中的宇宙不太可能的回绕甚至不太关注StopWatch。在我的机器上,StopWatch是高分辨率,每秒2.4e9滴答。即使按照这个速度,也需要超过121年的时间才能超过蜱虫领域。当然,我不知道封面下发生了什么,所以拿出一粒盐。但是,我注意到StopWatch的文档甚至没有提到回绕问题,而TickCount的文档也没有。

答案 12 :(得分:0)

溢流补偿

如前所述,过渡可能会在24.9天后发生,或者,如果使用uint cast,则可能会在49.8天后发生。 因为我不想pInvoke GetTickCount64,所以我写了这个溢出补偿。示例代码使用“字节”保持数字方便。请查看它,它仍然可能包含错误:

using System;
namespace ConsoleApp1 {
    class Program {
        //
        static byte Lower = byte.MaxValue / 3;
        static byte Upper = 2 * byte.MaxValue / 3;
        //
        ///<summary>Compute delta between two TickCount values reliably, because TickCount might wrap after 49.8 days.</summary>
        static short Delta( byte next, byte ticks ) {
            if ( next < Lower ) {
                if ( ticks > Upper ) {
                    return (short) ( ticks - ( byte.MaxValue + 1 + next ) );
                }
            }
            if ( next > Upper ) {
                if ( ticks < Lower ) {
                    return (short) ( ( ticks + byte.MaxValue + 1 ) - next );
                }
            }
            return (short) ( ticks - next );
        }
        //
        static void Main( string[] args ) {
            // Init
            Random rnd = new Random();
            int max = 0;
            byte last = 0;
            byte wait = 3;
            byte next = (byte) ( last + wait );
            byte step = 0;
            // Loop tick
            for ( byte tick = 0; true; ) {
                //
                short delta = Delta( next, tick );
                if ( delta >= 0 ) {
                    Console.WriteLine( "RUN: last: {0} next: {1} tick: {2} delta: {3}", last, next, tick, delta );
                    last = tick;
                    next = (byte) ( last + wait );
                }
                // Will overflow to 0 automatically
                step = (byte) rnd.Next( 0, 11 );
                tick += step;
                max++; if ( max > 99999 ) break;
            }
        }
    }
}

答案 13 :(得分:0)

TickCount64

我发现对此新功能进行了一些快速测量(优化,发布64位,1000mio循环):

Environment.TickCount: 2265
Environment.TickCount64: 2531
DateTime.UtcNow.Ticks: 69016

未优化代码的测量结果相似。 测试代码:

static void Main( string[] args ) {
    long start, end, length = 1000 * 1000 * 1000;
    start = Environment.TickCount64;
    for ( int i = 0; i < length; i++ ) {
        var a = Environment.TickCount;
    }
    end = Environment.TickCount64;
    Console.WriteLine( "Environment.TickCount: {0}", end - start );
    start = Environment.TickCount64;
    for ( int i = 0; i < length; i++ ) {
        var a = Environment.TickCount64;
    }
    end = Environment.TickCount64;
    Console.WriteLine( "Environment.TickCount64: {0}", end - start );
    start = Environment.TickCount64;
    for ( int i = 0; i < length; i++ ) {
        var a = DateTime.UtcNow.Ticks;
    }
    end = Environment.TickCount64;
    Console.WriteLine( "DateTime.UtcNow.Ticks: {0}", end - start );
}