代码之家  ›  专栏  ›  技术社区  ›  My Other Me

为什么DateTime.Now DateTime.UtcNow这么慢/太贵

  •  17
  • My Other Me  · 技术社区  · 15 年前

    我意识到这对于微观优化领域来说太远了,但是我很好奇为什么对DateTime.Now和DateTime.UtcNow的调用如此“昂贵”。我有一个示例程序,它运行两个场景来执行一些“工作”(添加到计数器),并尝试执行此操作1秒。我有好几种办法使它在有限的时间内完成这项工作。示例显示,DateTime.Now和DateTime.UtcNow的速度明显慢于Environment.TickCount,但与只让单独的线程休眠1秒,然后设置一个值来指示要停止的工作线程相比,这甚至慢得多。

    所以我的问题是:

    • 为什么读取布尔值比读取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;
        }
    
    }
    
    3 回复  |  直到 15 年前
        1
  •  22
  •   Jon Skeet    15 年前

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

    DateTime.UtcNow 需要查询系统时间-不要忘记 对用户改变时钟或NTP之类的事情一无所知, UtcNow

    现在您已经表达了一个性能问题—但是在您给出的示例中,您所做的只是递增一个计数器。我希望你的 真实的 代码,你会做更多的工作。如果你在做 重要的 工作量,可能会使 . 在做其他事情之前,你应该测量一下,看看你是否真的在试图解决一个不存在的问题。

    需要改进,然后:

    • 您可以使用计时器,而不是显式地创建新线程。框架中有各种类型的计时器,在不知道具体情况的情况下,我无法建议使用哪种计时器最明智——但这感觉比启动线程更好。
    • 您可以测量任务的几个迭代,然后猜测实际需要多少迭代。然后,您可能需要执行一半的迭代,评估所用的时间,然后相应地调整剩余周期的数量。当然,如果每次迭代所需的时间变化很大,这就不起作用。
        2
  •  18
  •   wageoghe    14 年前

    DateTime.Now )如果当前刻度计数不同于上一个刻度计数。这并不真的直接适用于你的问题,但它是一个有趣的方式“加快”当前时间检索。

    internal class CurrentTimeGetter    
    {        
      private static int lastTicks = -1;        
      private static DateTime lastDateTime = DateTime.MinValue;        
    
      /// <summary>        
      /// Gets the current time in an optimized fashion.        
      /// </summary>        
      /// <value>Current time.</value>        
    
      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 )可能会加速相对缓慢的操作( 日期时间。现在 )在某些情况下。

        3
  •  5
  •   Arsen Zahray    10 年前

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

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

        4
  •  5
  •   Nicholas Petersen    6 年前

    要了解最新的外形速度 DateTime.UtcNow / DateTimeOffset.UtcNow ,请看这个 dotnet thread BenchmarkDotNet 是用来做侧写的。

    不幸的是,与2.2相比,有一个跳跃到.NET(Core)3的perf回归,但即使使用回归值报告, 日期时间.UtcNow 71 ns (它曾经 25 ns ),即每秒71亿分之一。

    71ns ,意思是:

    日期时间.UtcNow 大约14000次,只需1毫秒!

    在之前更快的时间 (希望他们能找回这场表演),你可以打电话给 约40000次,花费1毫秒。

    我在这里不看旧的.NET Framework时代,但至少在新的时代,我认为可以肯定地说,至少现在这样说已经不准确了 是“慢/贵”(我很感激有人问这个问题!)。