70

可以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");
4

14 回答 14

103

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

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

DateTime.Ticks是基于GetSystemTimeAsFileTime () WinAPI 函数。它在 100 纳秒(十分之一微秒)内。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 频率变化,则可能不可靠(阅读本文

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

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();
}
于 2012-01-14T21:42:17.563 回答
72

使用秒表类。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;
于 2008-10-28T13:41:34.097 回答
28

为什么担心翻车?只要您测量的持续时间低于 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 < afterrollover。我总是会执行减法以找到考虑翻转的持续时间,然后基于持续时间进行任何其他计算。

于 2009-07-03T06:59:27.530 回答
10

你可能想要System.Diagnostics.StopWatch.

于 2008-10-28T13:41:46.187 回答
9

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

于 2011-06-10T15:37:18.400 回答
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();
}
于 2017-04-11T06:43:09.793 回答
8

采用

System.Diagnostics.Stopwatch

它有一个属性叫做

EllapsedMilliseconds
于 2008-10-28T13:42:01.443 回答
8

这是该线程中可能是最有用的答案和评论的更新和刷新摘要+额外的基准和变体:

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

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

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

如何确定和验证 QPC 在我的机器上工作?
您无需执行此类检查。

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

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

我从 cskwg 接管了上面的基准测试,并为更多变体扩展了代码。我用几年前的 i7 4700 MQ 和 C# 7 和 VS 2017 进行了测量(更准确地说,用 .NET 4.5.2 编译,尽管是二进制文字,它是 C# 6(使用这个:字符串文字和'使用静态'). 与上述基准相比,尤其是 Stopwatch() 的性能似乎有所提高。

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

32位,无优化的发布模式:

测量:GetTickCount64() [ms]:275
测量:Environment.TickCount [ms]:45
测量:DateTime.UtcNow.Ticks [ms]:167
测量:秒表:.ElapsedTicks [ms]:277
测量:秒表:.ElapsedMilliseconds [ ms]:548
测量:静态 Stopwatch.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
测量:静态 Stopwatch.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
测量:静态 Stopwatch.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
测量:静态 Stopwatch.GetTimestamp [ms]:175
测量:秒表+转换为 DateTime [ms]:246
与 DateTime.Now.Ticks [ms] 进行比较: 3020

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

您可以看到:仅对于数百万次处决,秒表的时间才开始变得重要。如果情况确实如此(但要提防过早进行微优化),使用 GetTickCount64() 可能会很有趣,但尤其是使用DateTime.UtcNow时,您有一个 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, 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();
    }
于 2017-05-03T22:06:45.103 回答
0

您应该改用Stopwatch类。

于 2008-10-28T13:42:12.847 回答
0

我使用 Environment.TickCount 因为:

  1. Stopwatch 类不在 Compact Framework 中。
  2. 秒表使用与 TickCount 相同的底层计时机制,因此结果不会或多或少准确。
  3. TickCount 的环绕问题在宇宙中不太可能受到影响(您必须让计算机运行 27 天,然后尝试测量恰好跨越环绕时刻的时间),即使您这样做了击中它,结果将是一个巨大的负时间跨度(所以它会脱颖而出)。

话虽如此,如果您可以使用秒表,我也建议您使用它。或者您可以花大约 1 分钟的时间编写一个类似秒表的类,该类包含 Environment.TickCount。

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

于 2008-10-28T14:23:06.260 回答
0

我本来想说把它包进一个秒表课,但 Grzenio 已经说对了,所以我会给他一个提振。这种封装因素决定了哪种方式更好,这可能会随着时间而改变。我记得对在某些系统上花费时间的成本感到震惊,因此拥有一个可以实施最佳技术的地方可能非常重要。

于 2008-10-28T19:34:08.613 回答
0

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

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

鉴于 ElapsedTicks 字段很长,我猜想 TickCount 中的宇宙不太可能的环绕对于 StopWatch 来说更不用担心。在我的机器上,秒表是高分辨率的,每秒 2.4e9 滴答声。即使按照这样的速度,也需要超过 121 年才能溢出刻度字段。当然,我不知道幕后发生了什么,所以对此持保留态度。但是,我注意到 StopWatch 的文档甚至没有提到环绕问题,而 TickCount 的文档却提到了。

于 2009-03-17T00:25:11.097 回答
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;
            }
        }
    }
}
于 2020-07-12T11:23:14.443 回答
0

滴答计数64

对这个新功能做了一些快速测量,我发现(优化,发布 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 );
}
于 2020-07-13T06:36:32.060 回答