为什么DateTime.Now DateTime.UtcNow如此慢/昂贵

我意识到这在微优化领域太过分了,但我很想知道为什么调用DateTime.Now和DateTime.UtcNow是如此“昂贵”。 我有一个示例程序运行几个做“一些”工作的场景(添加到一个计数器)并试图这样做1秒钟。 我有几个接近让它在有限的时间内完成工作。 这些示例显示DateTime.Now和DateTime.UtcNow明显慢于Environment.TickCount,但与仅让一个单独的线程hibernate1秒然后设置一个值以指示工作线程停止相比,即使这样也很慢。

所以我的问题是:

  • 我知道UtcNow更快,因为它没有时区信息,为什么它仍然比TickCount慢得多?
  • 为什么读取布尔值比使用int更快?
  • 处理这些类型场景的理想方法是什么,您需要允许某些东西在有限的时间内运行,但是您不想浪费更多的时间来检查时间而不是实际工作?

请原谅示例的详细程度:

class Program { private static volatile bool done = false; private static volatile int doneInt = 0; private static UInt64 doneLong = 0; private static ManualResetEvent readyEvent = new ManualResetEvent(false); static void Main(string[] args) { MethodA_PrecalcEndTime(); MethodB_CalcEndTimeEachTime(); MethodC_PrecalcEndTimeUsingUtcNow(); MethodD_EnvironmentTickCount(); MethodX_SeperateThreadBool(); MethodY_SeperateThreadInt(); MethodZ_SeperateThreadLong(); Console.WriteLine("Done..."); Console.ReadLine(); } private static void MethodA_PrecalcEndTime() { int cnt = 0; var doneTime = DateTime.Now.AddSeconds(1); var startDT = DateTime.Now; while (DateTime.Now <= doneTime) { cnt++; } var endDT = DateTime.Now; Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt); } private static void MethodB_CalcEndTimeEachTime() { int cnt = 0; var startDT = DateTime.Now; while (DateTime.Now <= startDT.AddSeconds(1)) { cnt++; } var endDT = DateTime.Now; Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt); } private static void MethodC_PrecalcEndTimeUsingUtcNow() { int cnt = 0; var doneTime = DateTime.UtcNow.AddSeconds(1); var startDT = DateTime.Now; while (DateTime.UtcNow <= doneTime) { cnt++; } var endDT = DateTime.Now; Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt); } private static void MethodD_EnvironmentTickCount() { int cnt = 0; int doneTick = Environment.TickCount + 1000; // <-- should be sane near where the counter clocks... var startDT = DateTime.Now; while (Environment.TickCount <= doneTick) { cnt++; } var endDT = DateTime.Now; Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt); } private static void MethodX_SeperateThreadBool() { readyEvent.Reset(); Thread counter = new Thread(CountBool); Thread waiter = new Thread(WaitBool); counter.Start(); waiter.Start(); waiter.Join(); counter.Join(); } private static void CountBool() { int cnt = 0; readyEvent.WaitOne(); var startDT = DateTime.Now; while (!done) { cnt++; } var endDT = DateTime.Now; Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt); } private static void WaitBool() { readyEvent.Set(); Thread.Sleep(TimeSpan.FromSeconds(1)); done = true; } private static void MethodY_SeperateThreadInt() { readyEvent.Reset(); Thread counter = new Thread(CountInt); Thread waiter = new Thread(WaitInt); counter.Start(); waiter.Start(); waiter.Join(); counter.Join(); } private static void CountInt() { int cnt = 0; readyEvent.WaitOne(); var startDT = DateTime.Now; while (doneInt<1) { cnt++; } var endDT = DateTime.Now; Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt); } private static void WaitInt() { readyEvent.Set(); Thread.Sleep(TimeSpan.FromSeconds(1)); doneInt = 1; } private static void MethodZ_SeperateThreadLong() { readyEvent.Reset(); Thread counter = new Thread(CountLong); Thread waiter = new Thread(WaitLong); counter.Start(); waiter.Start(); waiter.Join(); counter.Join(); } private static void CountLong() { int cnt = 0; readyEvent.WaitOne(); var startDT = DateTime.Now; while (doneLong < 1) { cnt++; } var endDT = DateTime.Now; Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt); } private static void WaitLong() { readyEvent.Set(); Thread.Sleep(TimeSpan.FromSeconds(1)); doneLong = 1; } } 

TickCount只是读取一个不断增加的计数器。 这是你能做的最简单的事情。

DateTime.UtcNow需要查询系统时间 – 并且不要忘记,虽然TickCount对用户更改时钟或NTP等事情一无所知,但UtcNow必须考虑到这一点。

现在你已经表达了一个性能问题 – 但是在你给出的例子中,你所做的只是增加一个计数器。 我希望在你真正的代码中,你会做更多的工作。 如果你UtcNow的工作,这可能UtcNow的时间UtcNow 。 在做任何其他事情之前,你应该测量一下,看看你是否真的试图解决一个不存在的问题。

如果你确实需要改进,那么:

  • 您可以使用计时器而不是显式创建新线程。 框架中有各种各样的计时器,并且在不知道您的具体情况的情况下,我无法建议使用哪个最合理 – 但它感觉比起一个线程更好的解决方案。
  • 您可以测量任务的几次迭代,然后猜测实际需要多少次。 您可能希望执行一半多次迭代,计算所花费的时间,然后相应地调整剩余周期数。 当然,如果每次迭代所花费的时间变化很大,那么这不起作用。

FWIW这里是NLog用于获取每条日志消息的时间戳的一些代码。 在这种情况下,“工作”是当前时间的实际检索(被授予,它发生在可能更昂贵的“工作”位,消息记录的上下文中)。 如果当前的滴答计数与先前的滴答计数不同,NLog通过仅获得“实际”时间(通过DateTime.Now )来最小化获得当前时间的成本。 这并不直接适用于您的问题,但它是一种“加速”当前时间检索的有趣方式。

 internal class CurrentTimeGetter { private static int lastTicks = -1; private static DateTime lastDateTime = DateTime.MinValue; ///  /// Gets the current time in an optimized fashion. ///  /// Current time. public static DateTime Now { get { int tickCount = Environment.TickCount; if (tickCount == lastTicks) { return lastDateTime; } DateTime dt = DateTime.Now; lastTicks = tickCount; lastDateTime = dt; return dt; } } } // It would be used like this: DateTime timeToLog = CurrentTimeGetter.Now; 

在您的问题的上下文中,您可能会“改善”您的时间循环代码的性能,如下所示:

 private static void MethodA_PrecalcEndTime() { int cnt = 0; var doneTime = DateTime.Now.AddSeconds(1); var startDT = CurrentTimeGetter.Now; while (CurrentTimeGetter.Now <= doneTime) { cnt++; } var endDT = DateTime.Now; Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt); } } 

如果频繁调用CurrentTimeGetter.Now使得返回的时间连续多次相同,则只需支付Environment.TickCount的开销。 我不能说它是否真的有助于NLog日志记录的性能,你会注意到或不。

我不知道这对你的问题真的有帮助,或者你是否还需要任何帮助,但我认为它可以作为一个有趣的例子,利用更快的操作( Environment.Ticks )来加速相对慢的速度在某些情况下操作( DateTime.Now )。

据我所知, DateTime.UtcNow (不要与DateTime.Now混淆,这是慢得多)是你获得时间的最快方法。 实际上,以@wageoghe提出的方式缓存它会显着降低性能(在我的测试中,这是3.5倍)。

在ILSpy中,UtcNow看起来像这样:

 [__DynamicallyInvokable] public static DateTime UtcNow { [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"), SecuritySafeCritical] get { long systemTimeAsFileTime = DateTime.GetSystemTimeAsFileTime(); return new DateTime((ulong)(systemTimeAsFileTime + 504911232000000000L | 4611686018427387904L)); } } 

我认为,这表明该函数由编译器内联以实现最大速度。 可能有更快的方式来获得时间,但到目前为止,我还没有看到过