Environment.TickCount 与 DateTime.Now

发布于 2024-07-07 20:12:10 字数 675 浏览 7 评论 0原文

是否可以使用 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");

Is it ever OK to use Environment.TickCount to calculate time spans?

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

Because TickCount is signed and will rollover after 25 days (it takes 50 days to hit all 32 bits, but you have to scrap the signed bit if you want to make any sense of the math), it seems like it's too risky to be useful.

I'm using DateTime.Now instead. Is this the best way to do this?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

剑心龙吟 2024-07-14 20:12:10

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

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

DateTime.Ticks 基于 GetSystemTimeAsFileTime ()WinAPI 函数。 它以 100 纳秒(十分之一微秒)为单位。
DateTime.Ticks 的实际精度取决于系统。 在Windows 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();
}

Environment.TickCount is based on GetTickCount() WinAPI function. It's in milliseconds.
But the actual precision of it is about 15.6 ms. So you can't measure shorter time intervals (or you'll get 0).

Note: The returned value is Int32, so this counter rolls over each ~49.7 days. You shouldn't use it to measure such long intervals.

DateTime.Ticks is based on GetSystemTimeAsFileTime() WinAPI function. It's in 100s nanoseconds (tenths of microsoconds).
The actual precision of DateTime.Ticks depends on the system. On Windows XP, the increment of system clock is about 15.6 ms, the same as in Environment.TickCount.
On Windows 7 its precision is 1 ms (while Environemnt.TickCount's is still 15.6 ms). However, if a power saving scheme is used (usually on laptops) it can go down to 15.6 ms as well.

Stopwatch is based on QueryPerformanceCounter() WinAPI function (but if high-resolution performance counter is not supported by your system, DateTime.Ticks is used)

Before using StopWatch notice two problems:

  • it can be unreliable on multiprocessor systems (see MS kb895980, kb896256)
  • it can be unreliable if CPU frequency varies (read this article)

You can evaluate the precision on your system with simple test:

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();
}
罗罗贝儿 2024-07-14 20:12:10

使用秒表类。 MSDN 上有一个不错的示例:秒表类

    Stopwatch stopWatch = Stopwatch.StartNew();
    Thread.Sleep(10000);
    stopWatch.Stop();
    // Get the elapsed time as a TimeSpan value.
    TimeSpan ts = stopWatch.Elapsed;

Use the Stopwatch class. There is a decent example on MSDN: Stopwatch Class

    Stopwatch stopWatch = Stopwatch.StartNew();
    Thread.Sleep(10000);
    stopWatch.Stop();
    // Get the elapsed time as a TimeSpan value.
    TimeSpan ts = stopWatch.Elapsed;
前事休说 2024-07-14 20:12:10

为什么担心翻车? 只要您测量的持续时间低于 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 < 例如,直接在翻转后。 我总是会执行减法来找到考虑了展期的持续时间,然后根据持续时间进行任何其他计算。

Why are you worried about rollover? As long as the duration you are measuring is under 24.9 days and you calculate the relative duration, you're fine. It doesn't matter how long the system has been running, as long as you only concern yourself with your portion of that running time (as opposed to directly performing less-than or greater-than comparisons on the begin and end points). I.e. this:

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

correctly prints:

 before_rollover: 2147483642
 after_rollover: -2147483641
 duration: 13

You don't have to worry about the sign bit. C#, like C, lets the CPU handle this.

This is a common situation I've run into before with time counts in embedded systems. I would never compare beforerollover < afterrollover directly, for instance. I would always perform the subtraction to find the duration that takes rollover into account, and then base any other calculations on the duration.

混浊又暗下来 2024-07-14 20:12:10

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

Time function           Time   Time per
                        [ms]   call [ns]
----------------------------------------
Environment.TickCount     71     7.1
DateTime.UtcNow.Ticks    213    21.3
sw.ElapsedMilliseconds  1273   127.3

测量值是由以下代码生成的:

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

Environment.TickCount seems to be much faster than the other solutions:

Time function           Time   Time per
                        [ms]   call [ns]
----------------------------------------
Environment.TickCount     71     7.1
DateTime.UtcNow.Ticks    213    21.3
sw.ElapsedMilliseconds  1273   127.3

The measurements were generated by the following code:

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(
quot;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(
quot;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(
quot;sw.ElapsedMilliseconds {sw.ElapsedMilliseconds}");
    }
    Console.WriteLine("Done");
    Console.ReadKey();
}
追我者格杀勿论 2024-07-14 20:12:10

以下是该主题中可能最有用的答案和评论的更新和刷新摘要。 加上额外的基准和变体:

首先:正如其他人在评论中指出的那样,过去几年情况发生了变化,对于“现代”Windows(Windows XP 及更高版本)和.NET,以及现代硬件,没有或几乎没有原因不要使用 Stopwatch()。
请参阅 MSDN 了解详细信息。 报价:

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

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

如何确定和验证 QPC 在我的计算机上运行?
您不需要执行此类检查。

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

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

我接管了 cskwg 的基准,并扩展了更多变体的代码。我已经用一些进行了测量岁 Core i7 i7-4700MQ(“Haswell-MB”(四核,22 nm)。 2013 年份)和 C# 7Visual Studio 2017(更准确地说,使用 .NET 4.5.2 编译,尽管是二进制文字,但它是 C# 6(使用此:字符串文字和“使用静态”)。 特别是 Stopwatch() 性能与上述基准相比似乎有所提高。

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

32 位,未经优化的发布模式:

测量:GetTickCount64() [毫秒]:275
测量值:Environment.TickCount [ms]:45
测量值:DateTime.UtcNow.Ticks [ms]:167
测量:秒表:.ElapsedTicks [ms]:277
测量:秒表:.ElapsedMilliseconds [ms]:548
测量:静态 Stopwatch.GetTimestamp [ms]:193
测量:秒表+转换为日期时间 [ms]:551
与 DateTime.Now.Ticks [ms] 进行比较:9010

32 位,发布模式,优化:

测量:GetTickCount64() [毫秒]:198
测量值:Environment.TickCount [ms]:39
测量值:DateTime.UtcNow.Ticks [ms]:66 (!)
测量:秒表:.ElapsedTicks [ms]:175
测量:秒表:.ElapsedMilliseconds [ms]:491
测量:静态 Stopwatch.GetTimestamp [ms]:175
测量:秒表+转换为日期时间 [ms]:510
与 DateTime.Now.Ticks [ms] 进行比较:8460

64 位,未优化的发布模式:

测量:GetTickCount64() [毫秒]:205
测量值:Environment.TickCount [ms]:39
测量值:DateTime.UtcNow.Ticks [ms]:127
测量:秒表:.ElapsedTicks [ms]:209
测量:秒表:.ElapsedMilliseconds [ms]:285
测量:静态 Stopwatch.GetTimestamp [ms]:187
测量:秒表+转换为日期时间 [ms]:319
与 DateTime.Now.Ticks [ms] 进行比较:3040

64 位,发布模式,优化:

测量:GetTickCount64() [毫秒]:148
测量值:Environment.TickCount [ms]:31 (还值得吗?)
测量值:DateTime.UtcNow.Ticks [ms]:76 (!)
测量:秒表:.ElapsedTicks [ms]:178
测量:秒表:.ElapsedMilliseconds [ms]:226
测量:静态 Stopwatch.GetTimestamp [ms]:175
测量:秒表+转换为日期时间 [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();
    }

Here is kind of an updated&refreshed summary of what may be the most useful answers and comments in this thread. Plus extra benchmarks and variants:

First thing first: As others have pointed out in comments, things have changed the last years and with "modern" Windows (Windows XP and later) and .NET, and modern hardware there are no or little reasons not to use Stopwatch().
See MSDN for details. Quotations:

"Is QPC accuracy affected by processor frequency changes caused by power management or Turbo Boost technology?
No. If the processor has an invariant TSC, the QPC is not affected by these sort of changes. If the processor doesn't have an invariant TSC, QPC will revert to a platform hardware timer that won't be affected by processor frequency changes or Turbo Boost technology.

Does QPC reliably work on multi-processor systems, multi-core system, and systems with hyper-threading?
Yes

How do I determine and validate that QPC works on my machine?
You don't need to perform such checks.

Which processors have non-invariant TSCs?
[..Read further..]
"

But if you don't need the precision of Stopwatch() or at least want to know exactly about the performance of Stopwatch (static vs. instance-based) and other possible variants, continue reading:

I took over the benchmark from cskwg, and extended the code for more variants. I have measured with a some years old Core i7 i7-4700MQ ("Haswell-MB" (quad-core, 22 nm). 2013 vintage) and C# 7 with Visual Studio 2017 (to be more precise, compiled with .NET 4.5.2, despite binary literals, it is C# 6 (used of this: string literals and 'using static'). Especially the Stopwatch() performance seems to be improved compared to the mentioned benchmark.

This is an example of results of 10 million repetitions in a loop. As always, absolute values are not important, but even the relative values may differ on other hardware:

32 bit, Release mode without optimization:

Measured: GetTickCount64() [ms]: 275
Measured: Environment.TickCount [ms]: 45
Measured: DateTime.UtcNow.Ticks [ms]: 167
Measured: Stopwatch: .ElapsedTicks [ms]: 277
Measured: Stopwatch: .ElapsedMilliseconds [ms]: 548
Measured: static Stopwatch.GetTimestamp [ms]: 193
Measured: Stopwatch+conversion to DateTime [ms]: 551
Compare that with DateTime.Now.Ticks [ms]: 9010

32 bit, Release mode, optimized:

Measured: GetTickCount64() [ms]: 198
Measured: Environment.TickCount [ms]: 39
Measured: DateTime.UtcNow.Ticks [ms]: 66 (!)
Measured: Stopwatch: .ElapsedTicks [ms]: 175
Measured: Stopwatch: .ElapsedMilliseconds [ms]: 491
Measured: static Stopwatch.GetTimestamp [ms]: 175
Measured: Stopwatch+conversion to DateTime [ms]: 510
Compare that with DateTime.Now.Ticks [ms]: 8460

64 bit, Release mode without optimization:

Measured: GetTickCount64() [ms]: 205
Measured: Environment.TickCount [ms]: 39
Measured: DateTime.UtcNow.Ticks [ms]: 127
Measured: Stopwatch: .ElapsedTicks [ms]: 209
Measured: Stopwatch: .ElapsedMilliseconds [ms]: 285
Measured: static Stopwatch.GetTimestamp [ms]: 187
Measured: Stopwatch+conversion to DateTime [ms]: 319
Compare that with DateTime.Now.Ticks [ms]: 3040

64 bit, Release mode, optimized:

Measured: GetTickCount64() [ms]: 148
Measured: Environment.TickCount [ms]: 31 (is it still worth it?)
Measured: DateTime.UtcNow.Ticks [ms]: 76 (!)
Measured: Stopwatch: .ElapsedTicks [ms]: 178
Measured: Stopwatch: .ElapsedMilliseconds [ms]: 226
Measured: static Stopwatch.GetTimestamp [ms]: 175
Measured: Stopwatch+conversion to DateTime [ms]: 246
Compare that with DateTime.Now.Ticks [ms]: 3020

It may be very interesting, that creating a DateTime value to print out the Stopwatch time seems to have nearly no costs. Interesting in a more academic than practical way is that static Stopwatch is slightly faster (as expected). Some optimization points are quite interesting.
For example, I cannot explain why Stopwatch.ElapsedMilliseconds only with 32 bit is so slow compared to it's other variants, for example the static one. This and DateTime.Now more than double their speed with 64 bit.

You can see: Only for millions of executions, the time of Stopwatch begins to matter. If this is really the case (but beware micro-optimizing too early), it may be interesting that with GetTickCount64(), but especially with DateTime.UtcNow, you have a 64 bit (long) timer with less precision than Stopwatch, but faster, so that you don't have to mess around with the 32 bit "ugly" Environment.TickCount.

As expected, DateTime.Now is by far the slowest of all.

If you run it, the code retrieves also your current Stopwatch accuracy and more.

Here is the full benchmark code:

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(
quot;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(
quot;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(
quot;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(
quot;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(
quot;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(
quot;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(
quot;Measured: Stopwatch+conversion to DateTime [s] with millisecs: {dt:s.fff}");
            Console.WriteLine(
quot;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(
quot;Compare that with DateTime.Now.Ticks [ms]: {sw.ElapsedMilliseconds*10}");

        Console.WriteLine(
quot;{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(
quot;- 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(
quot;- 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(
quot;{NewLine}Done.");

        while (Console.KeyAvailable)
            Console.ReadKey(false);
        Console.ReadKey();
    }
π浅易 2024-07-14 20:12:10

如果您正在寻找 Environment.TickCount 的功能,但又不想产生创建新 Stopwatch 对象的开销,则可以使用静态 Stopwatch.GetTimestamp()< /code> 方法(以及 Stopwatch.Frequency)来计算长时间跨度。 因为 GetTimestamp() 返回一个 long,所以它在非常非常长的时间内不会溢出(超过 100,000 年,在我用来写这个的机器上) 。 它也比 Environment.TickCount 更准确,后者的最大分辨率为 10 到 16 毫秒。

If you're looking for the functionality of Environment.TickCount but without the overhead of creating new Stopwatch objects, you can use the static Stopwatch.GetTimestamp() method (along with Stopwatch.Frequency) to calculate long time spans. Because GetTimestamp() returns a long, it won't overflow for a very, very long time (over 100,000 years, on the machine I'm using to write this). It's also much more accurate than Environment.TickCount which has a maximum resolution of 10 to 16 milliseconds.

蓬勃野心 2024-07-14 20:12:10

使用

System.Diagnostics.Stopwatch

它有一个属性称为

EllapsedMilliseconds

Use

System.Diagnostics.Stopwatch

It has a property called

EllapsedMilliseconds
苍风燃霜 2024-07-14 20:12:10

TickCount64

对这个新函数进行一些快速测量,我发现(优化,发布 64 位,10 亿次循环):

Time function             Time   Time per
                          [ms]   call [ns]
------------------------------------------
Environment.TickCount    2265     2.265
Environment.TickCount64  2531     2.531
DateTime.UtcNow.Ticks   69016    69.016

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

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

TickCount64

Doing some quick measurements on this new function, I found (optimized, release 64-bit, 1000 million loops):

Time function             Time   Time per
                          [ms]   call [ns]
------------------------------------------
Environment.TickCount    2265     2.265
Environment.TickCount64  2531     2.531
DateTime.UtcNow.Ticks   69016    69.016

The measurements for not-optimized code were similar.
Test code:

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 );
}
孤独患者 2024-07-14 20:12:10

您应该使用 Stopwatch 类。

You should use the Stopwatch class instead.

浮生未歇 2024-07-14 20:12:10

我使用Environment.TickCount 是因为:

  1. Stopwatch 类不在Compact Framework 中。
  2. Stopwatch 使用与 TickCount 相同的底层计时机制,因此结果不会更加准确或不太准确。
  3. TickCount 的环绕问题在很大程度上不太可能发生(您必须让计算机运行 27 天,然后尝试测量恰好跨越环绕时刻的时间) ),即使你确实击中了它,结果也会是一个巨大的负时间跨度(所以它会有点突出)。

话虽这么说,如果您可以的话,我还建议您使用秒表。 或者您可以花大约 1 分钟编写一个类似秒表的类来包装 Environment.TickCount。

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

I use Environment.TickCount because:

  1. The Stopwatch class is not in the Compact Framework.
  2. Stopwatch uses the same underlying timing mechanism as TickCount, so the results won't be any more or less accurate.
  3. The wrap-around problem with TickCount is cosmically unlikely to be hit (you'd have to leave your computer running for 27 days and then try to measure a time that just happens to span the wrap-around moment), and even if you did hit it the result would be a huge negative time span (so it would kind of stand out).

That being said, I would also recommend using Stopwatch, if it's available to you. Or you could take about 1 minute and write a Stopwatch-like class that wraps Environment.TickCount.

BTW, I see nothing in the Stopwatch documentation that mentions the wrap-around problem with the underlying timer mechanism, so I wouldn't be surprised at all to find that Stopwatch suffers from the same problem. But again, I wouldn't spend any time worrying about it.

翻了热茶 2024-07-14 20:12:10

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

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

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

For one-shot timing, it's even simpler to write

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

I'd guess the cosmically unlikely wraparound in TickCount is even less of a concern for StopWatch, given that the ElapsedTicks field is a long. On my machine, StopWatch is high resolution, at 2.4e9 ticks per second. Even at that rate, it would take over 121 years to overflow the ticks field. Of course, I don't know what's going on under the covers, so take that with a grain of salt. However, I notice that the documentation for StopWatch doesn't even mention the wraparound issue, while the doc for TickCount does.

暖阳 2024-07-14 20:12:10

I was going to say wrap it into a stopwatch class, but Grzenio already said the right thing, so I will give him an uptick. Such encapsulation factors out the decision as to which way is better, and this can change in time. I remember being shocked at how expensive it can be getting the time on some systems, so having one place that can implement the best technique can be very important.

失退 2024-07-14 20:12:10

溢出补偿

如前所述,翻转可能会在 24.9 天后发生,或者,如果您使用 uint 转换,则可能会在 49.8 天后发生。
因为我不想P/Invoke 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;
            }
        }
    }
}

Overflow compensation

As said before, rollover may happen after 24.9 days, or, if you use an uint cast, after 49.8 days.
Because I did not want to P/Invoke GetTickCount64, I wrote this overflow compensation. The sample code is using 'byte' to keep the numbers handy. Please have a look at it; it still might contain errors:

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;
            }
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文