28

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

所以我的问题是:

  • 我知道 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;
    }

}
4

4 回答 4

27

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

DateTime.UtcNow需要查询系统时间 - 不要忘记,虽然TickCount用户更改时钟或 NTP 之类的事情非常无知,UtcNow但必须考虑到这一点。

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

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

  • 您可以使用计时器而不是显式创建新线程。框架中有各种计时器,在不知道您的确切情况的情况下,我无法建议使用哪种最明智 - 但感觉比启动线程更好。
  • 您可以测量任务的几次迭代,然后猜测实际需要多少次。然后,您可能希望执行一半的迭代,评估所花费的时间,然后相应地调整剩余周期数。当然,如果每次迭代所花费的时间变化很大,这将不起作用。
于 2010-11-02T07:13:37.223 回答
20

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

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) 来潜在地加快相对较慢的操作 ( DateTime.Now)在某些情况下。

于 2011-01-06T17:21:07.110 回答
9

有关DateTime.UtcNow/的分析速度的最新信息DateTimeOffset.UtcNow,请参阅此dotnet 线程BenchmarkDotNet用于分析。

不幸的是,与 2.2 相比,跳到 .NET (Core) 3 时出现了性能回归,但即使使用回归值进行报告,DateTime.UtcNow也出现了相当惊人的时间71 ns(它曾经是25 ns),即 71 亿分之一秒。

从这个角度来看,即使速度较慢71ns,这意味着:

您只需 1 毫秒即可调用DateTime.UtcNow~ 14,000 次!

在以前更快的时间25 ns(希望他们能恢复这种性能),您可以调用DateTime.UtcNow约 40,000 次,成本为 1 毫秒。

我不是在这里查看旧的 .NET Framework 时代,但至少对于较新的位,我认为可以有把握地说,说“慢/昂贵”至少不再准确DateTime.UtcNow(我很欣赏但是有人问了问题!)。

于 2019-08-13T22:39:36.890 回答
5

据我所知,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));
    }
}

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

于 2015-11-29T07:12:55.393 回答