这里是一个更新和刷新的摘要,列出了此主题中最有用的答案和评论以及额外的基准和变体:
首先:正如其他人在评论中指出的那样,随着“现代”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(使用了字符串字面量和'using static')。特别是与提到的基准测试相比,Stopwatch()的性能似乎有所改善。
这是一个在循环中重复1000万次的结果示例,如常规一样,绝对值并不重要,但即使相对值在其他硬件上也可能有所不同:
32位、未经优化的发布模式:
测量结果: GetTickCount64() [ms]: 275
测量结果: Environment.TickCount [ms]: 45
测量结果: DateTime.UtcNow.Ticks [ms]: 167
测量结果: Stopwatch: .ElapsedTicks [ms]: 277
测量结果: Stopwatch: .ElapsedMilliseconds [ms]: 548
测量结果: static Stopwatch.GetTimestamp [ms]: 193
测量结果: Stopwatch+conversion to DateTime [ms]: 551
与DateTime.Now.Ticks [ms]相比较: 9010
32位,发布模式,优化:
测量结果:GetTickCount64() [毫秒]:198
测量结果:Environment.TickCount [毫秒]:39
测量结果:DateTime.UtcNow.Ticks [毫秒]:66(!)
测量结果:Stopwatch: .ElapsedTicks [毫秒]:175
测量结果:Stopwatch: .ElapsedMilliseconds [毫秒]:491
测量结果:static Stopwatch.GetTimestamp [毫秒]:175
测量结果:Stopwatch+转换为DateTime [毫秒]:510
将其与DateTime.Now.Ticks [毫秒]进行比较:8460
64位,发布模式,未优化:
测量结果:GetTickCount64() [毫秒]:205
测量结果:Environment.TickCount [毫秒]:39
测量结果:DateTime.UtcNow.Ticks [毫秒]:
127
测量结果:Stopwatch: .ElapsedTicks [毫秒]:209
测量结果:Stopwatch: .ElapsedMilliseconds [毫秒]:285
测量结果:static Stopwatch.GetTimestamp [毫秒]:187
测量结果:Stopwatch+conversion to DateTime [毫秒]:319
与 DateTime.Now.Ticks [毫秒] 比较:3040
64 位,Release 模式,已优化:
测量结果如下:
GetTickCount64() [ms]:148
Environment.TickCount [ms]:31
(还值得吗?)
DateTime.UtcNow.Ticks [ms]:76
(!)
Stopwatch: .ElapsedTicks [ms]:178
Stopwatch: .ElapsedMilliseconds [ms]:226
static Stopwatch.GetTimestamp [ms]:175
Stopwatch+转换为 DateTime [ms]:246
与此相比,DateTime.Now.Ticks [ms] 为 3020。
有趣的是,
创建一个 DateTime 值以打印出 Stopwatch 时间似乎几乎没有成本。更有学术意义而非实际意义的是,静态 Stopwatch 稍微快一些(正如预期)。一些优化点也很有趣。例如,我无法解释为什么 Stopwatch.ElapsedMilliseconds 只有 32 位时与其其他变体相比速度如此缓慢,例如静态变量。这样做并且使用 64 位可以使它们的速度增加一倍以上。
你可以看到:只有执行数达到百万级别,Stopwatch 的时间才开始变得重要。如果真是这样的话(但要小心过早进行微优化),使用 GetTickCount64(),尤其是 DateTime.UtcNow,可能会很有意思。它们提供了一个 64 位(long)计时器,比 Stopwatch 更快,精度较低,因此您不必处理 32 位“丑陋”的 Environment.TickCount。
如预期的那样,DateTime.Now 是最慢的。
如果你运行它,代码还会检索你当前的 Stopwatch 精度等信息。
以下是完整的基准测试代码:
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using static System.Environment;
[...]
[DllImport("kernel32.dll") ]
public static extern UInt64 GetTickCount64();
static void Main(string[] args)
{
const int max = 10_000_000;
const int n = 3;
Stopwatch sw;
Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1);
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
Thread.CurrentThread.Priority = ThreadPriority.Highest;
Thread.Sleep(2);
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;
}
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;
sw = new Stopwatch();
sw.Start();
for (int i = 0; i < max; i++)
{
var a = new DateTime(sw.Elapsed.Ticks);
}
sw.Stop();
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) ;
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);
Console.WriteLine($"- Approximated capacity (maxtime) of TickCount [dd:hh:mm:ss] {maxTimeForTickCountInteger:dd:HH:mm:ss}");
Console.WriteLine($"{NewLine}Done.");
while (Console.KeyAvailable)
Console.ReadKey(false);
Console.ReadKey();
}
UtcNow
,也存在定时NTP同步的问题:在这些更新后,系统时间变化10秒的情况并不少见(在我的电脑上)。 - vgruint duration = unchecked((int)((uint)Environment.TickCount - (uint)start));
...将为您提供正确的答案,而不受溢出的影响。(除非您绝对需要它,否则最好跳过将其转换回int
的步骤。) - AnorZaken