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.UtcNowTickCountシステム時刻を照会する必要があります。ユーザーが時計を変更するなどのことを幸いにも知らない一方で、NTPはUtcNowこれを考慮に入れる必要があることを忘れないでください。

これで、パフォーマンスの懸念を表明しましたが、与えた例では、カウンターをインクリメントするだけです。実際のコードでは、それよりもかなり多くの作業を行うことになると思います。かなりの量の作業を行っている場合、それはにかかる時間を小さくする可能性がありUtcNowます。他のことをする前に、それを測定して、存在しない問題を実際に解決しようとしているかどうかを確認する必要があります。

物事を改善する必要がある場合は、次のようにします。

  • 新しいスレッドを明示的に作成するのではなく、タイマーを使用できます。フレームワークにはさまざまな種類のタイマーがあり、正確な状況を知らなければ、どれを使用するのが最も賢明かはわかりませんが、スレッドを開始するよりも良い解決策のように感じます。
  • タスクの数回の反復を測定してから、実際に何回必要になるかを推測できます。次に、その半分の反復を実行し、それにかかる時間を考慮して、それに応じて残りのサイクル数を調整することをお勧めします。もちろん、反復ごとにかかる時間が大幅に変化する可能性がある場合、これは機能しません。
于 2010-11-02T07:13:37.223 に答える
20

FWIWは、NLogが各ログメッセージのタイムスタンプを取得するために使用するコードです。この場合、「作業」は現在の時刻の実際の取得です(もちろん、メッセージのログ記録である、おそらくはるかに高価な「作業」のコンテキストで発生します)。DateTime.NowNLogは、現在の目盛り数が前の目盛り数と異なる場合にのみ「リアルタイム」時間を取得することにより、現在の時刻を取得するコストを最小限に抑えます。これは実際には質問に直接当てはまりませんが、現在の時刻の取得を「高速化」するための興味深い方法です。

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このドットネットスレッドを参照してくださいBenchmarkDotNet

残念ながら、2.2と比較して.NET(Core)3へのジャンプでパフォーマンスの低下がありましたが、低下した値でのレポートでさえ、 (以前は)DateTime.UtcNowかなり揺れ動く時間、つまり710億分の1秒で発生しています。71 ns25 ns

これを概観すると、の速度が遅い場合でも、次の71nsことを意味します。

DateTime.UtcNowわずか1ミリ秒のコストで約14,000回電話をかけることができます!

以前のより速い時間25 ns(うまくいけば、彼らはこのパフォーマンスを取り戻すでしょう)では、DateTime.UtcNow1ミリ秒のコストで約40,000回呼び出すことができます。

ここでは古い.NETFrameworkの時代を見ていませんが、少なくとも新しいビットでは、それ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));
    }
}

これは、関数がコンパイラーによってインライン化されて最大速度を達成していることを示唆していると思います。時間を稼ぐためのより速い方法があるかもしれませんが、これまでのところ、私は1つを見ていません

于 2015-11-29T07:12:55.393 に答える