代码之家  ›  专栏  ›  技术社区  ›  Jon B

environment.tickcount与datetime.now

  •  59
  • Jon B  · 技术社区  · 17 年前

    可以用吗 Environment.TickCount 计算时间跨度?

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

    因为 TickCount 是有符号的,25天后将滚动(达到所有32位需要50天,但是如果你想理解数学,就必须废弃有符号的位),这似乎太冒险了,不可能有用。

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

    DateTime start = DateTime.Now;
    // Do stuff
    TimeSpan duration = DateTime.Now - start;
    Console.WriteLine("That took " + duration.TotalMilliseconds + " ms");
    
    12 回复  |  直到 9 年前
        1
  •  67
  •   Joel Coehoorn    17 年前

    使用秒表类。在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
  •  85
  •   mistika    12 年前

    环境.tickcount 基于 GetTickCount() winapi函数。以毫秒为单位 但是它的实际精度大约是15.6毫秒,所以你不能测量更短的时间间隔(否则你会得到0)。

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

    日期时间 基于 GetSystemTimeAsFileTime ()winapi函数。它在100纳秒(十分之一微秒)内。 datetime.ticks的实际精度取决于系统。在xp上,系统时钟的增量约为15.6 ms,与environment.tickcount中相同。 在Windows7上,它的精度是1毫秒(而environment.tickcount的精度仍然是15.6毫秒),但是如果使用节能方案(通常在笔记本电脑上),它也可以下降到15.6毫秒。

    秒表 基于 QueryPerformanceCounter() winapi函数(但如果系统不支持高分辨率性能计数器,则使用datetime.ticks)

    使用秒表前,请注意两个问题:

    • 它在多处理器系统上可能不可靠(参见MS kb895980 , kb896256 )
    • 如果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();
    }
    
        3
  •  24
  •   Levi Botelho    12 年前

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

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

        4
  •  10
  •   Joel Coehoorn    17 年前
        5
  •  9
  •   Mark    15 年前

    如果你在寻找 Environment.TickCount 但是没有创造新事物的开销 Stopwatch 对象,可以使用静态 Stopwatch.GetTimestamp() 方法(连同 Stopwatch.Frequency )计算长时间跨度。因为 GetTimestamp() 返回A long 很长一段时间内不会溢出(超过100000年,在我写这个的机器上)。它也比 环境.tickcount 最大分辨率为10到16毫秒。

        6
  •  8
  •   Martin Kool    17 年前

    使用

    System.Diagnostics.Stopwatch
    

    它有一个名为

    EllapsedMilliseconds
    
        7
  •  5
  •   cskwg    9 年前

    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();
    }
    
        8
  •  5
  •   Philm    9 年前

    以下是更新和更新的摘要,总结了此线程中最有用的答案和评论+额外的基准和变体:

    第一件事:正如其他人在评论中指出的那样,在过去的几年里,事情发生了变化,随着“现代”Windows(Win XP++)和.NET,以及现代硬件的发展,没有或几乎没有理由不使用秒表()。 见 MSDN 详情。引文:

    “功率管理或涡轮增压技术引起的处理器频率变化是否影响QPC精度?
    不。 如果 处理器具有不变的TSC ,QPC不受此类更改的影响。如果处理器没有不变的TSC,QPC将恢复到平台硬件计时器,它不会受到处理器频率变化或涡轮增压技术的影响。

    QPC是否可靠地在多处理器系统、多核系统和超线程系统上工作?
    是的

    如何确定和验证QPC在我的机器上工作?
    你不需要做这种检查。

    哪些处理器具有非不变TSC? […进一步阅读..] “

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

    我从cskwg接管了上面的基准测试,并扩展了更多变体的代码。我用一些年前的i7 4700 mq和c_7(更精确地说,用.NET 4.5.2编译,尽管使用了二进制文本,但它是c_6(用于字符串文本和“使用静态”)。尤其是秒表()的性能似乎比上面提到的基准有所提高。

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

    32位,无优化的释放模式:

    测量值:GetTickCount64()[ms]:275
    测量值:environment.tickcount[ms]:45
    测量值:datetime.utcnow.ticks[ms]: 一百六十七
    测量值:秒表:.elapsedticks[ms]:277
    测量值:秒表:.elapsedmilliseconds[ms]:548
    测量值:静态秒表。GetTimeStamp[毫秒]:193
    测量值:秒表+转换为日期时间[毫秒]:551
    将其与datetime.now.ticks[ms]进行比较: 九千零一十

    32位,释放模式,优化:

    测量值:GetTickCount64()[ms]:198
    测量值:environment.tickcount[ms]:39
    测量值:datetime.utcnow.ticks[ms]:66 (!)
    测量值:秒表:.elapsedticks[ms]:175
    测量值:秒表:.elapsedmilliseconds[ms]: 四百九十一
    测量值:静态秒表。GetTimeStamp[毫秒]:175
    测量值:秒表+转换为日期时间[毫秒]: 五百一十
    将其与datetime.now.ticks[ms]进行比较: 八千四百六十

    64位,无优化的释放模式:

    测量值:GetTickCount64()[毫秒]:205
    测量值:environment.tickcount[ms]:39
    测量值:datetime.utcnow.ticks[ms]: 一百二十七
    测量值:秒表:.elapsedticks[ms]:209
    测量值:秒表:.elapsedmilliseconds[ms]:285
    测量值:静态秒表。GetTimeStamp[毫秒]:187
    测量值:秒表+转换为日期时间[毫秒]: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[毫秒]:175
    测量值:秒表+转换为日期时间[毫秒]:246
    与datetime.now.ticks[ms:3020相比

    这可能很有趣,因为 创建一个日期时间值来打印秒表时间似乎几乎没有成本 . 有趣的是,静态秒表比实际更快(如预期)。一些优化点非常有趣。 例如,我无法解释为什么只有32位的stopwatch.elapsedmilliseconds与其他变量(例如静态变量)相比如此之慢。现在,它的速度是64位的两倍多。

    你可以看到:只有数百万人被处决,秒表的时间才变得重要。如果真的是这样(但要注意微观优化太早),使用gettickCount64()可能会很有趣,但尤其是使用 数据处理程序 ,你有一个64位(长)计时器,它的精度比秒表要低,但速度要快,这样你就不必在32位的“丑陋”环境中纠结了。

    如预期的那样,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();
        }
    
        9
  •  0
  •   Community Mohan Dere    9 年前

    你应该使用 Stopwatch 而不是上课。

        10
  •  0
  •   MusiGenesis    17 年前

    我使用environment.tickcount是因为:

    1. 秒表类不在紧凑框架中。
    2. 秒表使用与TickCount相同的底层计时机制,因此结果将不再准确。
    3. 滴答数的环绕问题在大规模上不太可能被击中。 (你必须让你的电脑运行27天,然后试着测量一个刚好跨越环绕时间的时间),即使你真的击中了它,结果也会是一个巨大的负时间跨度(所以它会很突出)。

    也就是说,如果你有秒表的话,我还建议你使用它。或者你可以花1分钟写一个类似秒表的类来包装environment.tickcount。

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

        11
  •  0
  •   dongilmore    17 年前

    我本来想说把它包装成秒表课的,但格里泽尼奥已经说了正确的话,所以我会给他加薪。这样的封装因素决定了哪种方式更好,而且这会随着时间的推移而改变。我记得,在某些系统上花了这么多时间让我震惊,所以有一个地方可以实现最好的技术可能非常重要。

        12
  •  0
  •   vanmelle    17 年前

    对于一次性计时,写起来更简单

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

    我想,在滴答声中不太可能被大规模包围的情况对秒表来说更不重要,因为经过的滴答声场很长。在我的机器上,秒表是高分辨率的,每秒2.4e9滴答。即使以这样的速度,也要花121年的时间才能让虱子满地开花。当然,我不知道盖子下面发生了什么,所以拿着一粒盐。然而,我注意到秒表的文档甚至没有提到环绕式问题,而滴答计数的文档则提到了。