web-dev-qa-db-ja.com

Environment.TickCountとDateTime.Now

タイムスパンを計算するためにEnvironment.TickCountを使用しても大丈夫ですか?

int start = Environment.TickCount;
// Do stuff
int duration = Environment.TickCount - start;
Console.WriteLine("That took " + duration " ms");

TickCountは署名され、25日後にロールオーバーするため(32ビットすべてをヒットするのに50日かかりますが、数学を理解したい場合は署名ビットを破棄する必要があります)、リスクが高すぎて役に立たない。

代わりにDateTime.Nowを使用しています。これが最善の方法ですか?

DateTime start = DateTime.Now;
// Do stuff
TimeSpan duration = DateTime.Now - start;
Console.WriteLine("That took " + duration.TotalMilliseconds + " ms");
61
Jon B

ストップウォッチクラスを使用します。 msdnには適切な例があります: http://msdn.Microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx

    Stopwatch stopWatch = Stopwatch.StartNew();
    Thread.Sleep(10000);
    stopWatch.Stop();
    // Get the elapsed time as a TimeSpan value.
    TimeSpan ts = stopWatch.Elapsed;
68
Grzenio

Environment.TickCountは、 GetTickCount() WinAPI関数に基づいています。ミリ秒単位ですが、実際の精度は約15.6ミリ秒です。したがって、より短い時間間隔を測定することはできません(または0を取得します)

注:戻り値はInt32であるため、このカウンターは〜49.7日ごとにロールオーバーします。このような長い間隔の測定には使用しないでください。

DateTime.Ticksは、WinAPI関数 GetSystemTimeAsFileTime ()に基づいています。それは100ナノ秒(数十マイクロ秒)です。 DateTime.Ticksの実際の精度はシステムに依存します。 XPでは、システムクロックの増分は、Environment.TickCountと同じ約15.6 msです。 Windows 7では、精度は1ミリ秒です(Environemnt.TickCountはまだ15.6ミリ秒です)が、省電力スキームを使用すると(通常ラップトップで)、15.6ミリ秒に低下する可能性があります。

StopwatchQueryPerformanceCounter() WinAPI関数に基づいています(ただし、システムで高解像度のパフォーマンスカウンターがサポートされていない場合、DateTimeティックが使用されます)

StopWatchを使用する前に、2つの問題に注意してください。

  • マルチプロセッサシステムでは信頼できない場合があります(MS kb89598kb896256 を参照)
  • cPU周波数が変動する場合、信頼できない場合があります( this の記事を読んでください)

簡単なテストでシステムの精度を評価できます。

static void Main(string[] args)
{
    int xcnt = 0;
    long xdelta, xstart;
    xstart = DateTime.UtcNow.Ticks;
    do {
        xdelta = DateTime.UtcNow.Ticks - xstart;
        xcnt++;
    } while (xdelta == 0);

    Console.WriteLine("DateTime:\t{0} ms, in {1} cycles", xdelta / (10000.0), xcnt);

    int ycnt = 0, ystart;
    long ydelta;
    ystart = Environment.TickCount;
    do {
        ydelta = Environment.TickCount - ystart;
        ycnt++;
    } while (ydelta == 0);

    Console.WriteLine("Environment:\t{0} ms, in {1} cycles ", ydelta, ycnt);


    Stopwatch sw = new Stopwatch();
    int zcnt = 0;
    long zstart, zdelta;

    sw.Start();
    zstart = sw.ElapsedTicks; // This minimizes the difference (opposed to just using 0)
    do {
        zdelta = sw.ElapsedTicks - zstart;
        zcnt++;
    } while (zdelta == 0);
    sw.Stop();

    Console.WriteLine("StopWatch:\t{0} ms, in {1} cycles", (zdelta * 1000.0) / Stopwatch.Frequency, zcnt);
    Console.ReadKey();
}
87
mistika

ロールオーバーが心配なのはなぜですか?測定している期間が24.9日未満であり、相対期間を計算している限り、問題ありません。 (開始点と終了点で小なりまたは大なりの比較を直接実行するのではなく)その実行時間の一部にのみ関心がある限り、システムの実行時間は関係ありません。つまりこの:

 int before_rollover = Int32.MaxValue - 5;
 int after_rollover = Int32.MinValue + 7;
 int duration = after_rollover - before_rollover;
 Console.WriteLine("before_rollover: " + before_rollover.ToString());
 Console.WriteLine("after_rollover: " + after_rollover.ToString());
 Console.WriteLine("duration: " + duration.ToString());

正しく印刷:

 before_rollover: 2147483642
 after_rollover: -2147483641
 duration: 13

符号ビットについて心配する必要はありません。 C#と同様に、C#でこれを処理できます。

これは、組み込みシステムで時間をカウントする際に私が以前に遭遇した一般的な状況です。たとえば、beforerollover <afterrolloverを直接比較することはありません。私は常に減算を実行して、ロールオーバーを考慮に入れた期間を見つけ、その期間に基づいて他の計算を行います。

24
Matthew
10
Joel Coehoorn

_Environment.TickCount_の機能を探しているが、新しいStopwatchオブジェクトを作成するオーバーヘッドがない場合は、静的なStopwatch.GetTimestamp()メソッド(_Stopwatch.Frequency_と共に使用できます) )長い期間を計算します。 GetTimestamp()longを返すので、非常に長い時間(これを書くために使用しているマシンでは100,000年以上)オーバーフローしません。また、10〜16ミリ秒の最大解像度を持つ_Environment.TickCount_よりもはるかに正確です。

9
Mark

つかいます

System.Diagnostics.Stopwatch

というプロパティがあります

EllapsedMilliseconds
8
Martin Kool

Environment.TickCountは、他のソリューションよりもはるかに高速であるようです。

Environment.TickCount 71
DateTime.UtcNow.Ticks 213
sw.ElapsedMilliseconds 1273

測定値は、次のコードによって生成されました。

static void Main( string[] args ) {
    const int max = 10000000;
    //
    //
    for ( int j = 0; j < 3; j++ ) {
        var sw = new Stopwatch();
        sw.Start();
        for ( int i = 0; i < max; i++ ) {
            var a = Environment.TickCount;
        }
        sw.Stop();
        Console.WriteLine( $"Environment.TickCount {sw.ElapsedMilliseconds}" );
        //
        //
        sw = new Stopwatch();
        sw.Start();
        for ( int i = 0; i < max; i++ ) {
            var a = DateTime.UtcNow.Ticks;
        }
        sw.Stop();
        Console.WriteLine( $"DateTime.UtcNow.Ticks {sw.ElapsedMilliseconds}" );
        //
        //
        sw = new Stopwatch();
        sw.Start();
        for ( int i = 0; i < max; i++ ) {
            var a = sw.ElapsedMilliseconds;
        }
        sw.Stop();
        Console.WriteLine( $"sw.ElapsedMilliseconds {sw.ElapsedMilliseconds}" );
    }
    Console.WriteLine( "Done" );
    Console.ReadKey();
}
6
cskwg

以下は、このスレッドで最も有用な回答とコメントになる可能性があるものの、更新および更新された要約と、追加のベンチマークおよびバリアントです。

最初に最初に:他の人がコメントで指摘したように、物事はここ数年で変化し、「近代的な」Windows(Win XP ++)および.NET、および最新のハードウェアはありません。 Stopwatch()を使用しない理由はほとんどありません。詳細は [〜#〜] msdn [〜#〜] を参照してください。

「QPCの精度は、電源管理またはターボブーストテクノロジーによるプロセッサ周波数の変更の影響を受けますか?
No.プロセッサーに不変TSCがある場合、QPCはこれらの種類の変更の影響を受けません。プロセッサに不変のTSCがない場合、QPCは、プロセッサ周波数の変更やTurbo Boostテクノロジの影響を受けないプラットフォームハードウェアタイマーに戻ります。

QPCは、マルチプロセッサシステム、マルチコアシステム、およびハイパースレッディングを備えたシステムで確実に動作しますか?
はい

QPCがマシンで動作することをどのように判断して検証しますか?
このようなチェックを実行する必要はありません。

どのプロセッサに非不変TSCがありますか? [..さらに読む..] "

ただし、Stopwatch()の精度が必要ない場合、または少なくともストップウォッチ(静的ベースとインスタンスベース)およびその他の可能なバリアントのパフォーマンスについて正確に知りたい場合は、読み続けてください。

上記のベンチマークをcskwgから引き継ぎ、さらに多くの亜種のコードを拡張しました。私はVS 2017で数年前のi7 4700 MQとC#7で測定しました(より正確には、.NET 4.5.2でコンパイルされ、バイナリリテラルにもかかわらず、それはC#6です(これは文字列リテラルと')。特にStopwatch()のパフォーマンスは、上記のベンチマークと比較して改善されているようです。

これは、ループ内での1,000万回の繰り返しの結果の例です。常に絶対値は重要ではありませんが、他のハードウェアでは相対値でさえ異なる場合があります。

32ビット、最適化なしのリリースモード:

測定:GetTickCount64()[ms]:275
測定値:Environment.TickCount [ms]:45
測定値:DateTime.UtcNow.Ticks [ms]: 167
測定値:ストップウォッチ:.ElapsedTicks [ms]:277
測定値:ストップウォッチ:.ElapsedMilliseconds [ms]:548
測定値:静的Stopwatch.GetTimestamp [ms]:193
測定値:ストップウォッチ+ DateTimeへの変換[ms]:551
DateTime.Now.Ticks [ms]と比較してください: 9010

32ビット、リリースモード、最適化:

測定:GetTickCount64()[ms]:198
測定値:Environment.TickCount [ms]:39
測定値:DateTime.UtcNow.Ticks [ms]:66 (!)
測定値:ストップウォッチ:.ElapsedTicks [ms]:175
測定値:ストップウォッチ:.ElapsedMilliseconds [ms]: 491
測定値:静的Stopwatch.GetTimestamp [ms]:175
測定値:ストップウォッチ+ DateTimeへの変換[ms]: 510
DateTime.Now.Ticks [ms]と比較してください: 8460

64ビット、最適化なしのリリースモード:

測定:GetTickCount64()[ms]:205
測定値:Environment.TickCount [ms]:39
測定値:DateTime.UtcNow.Ticks [ms]: 127
測定値:ストップウォッチ:.ElapsedTicks [ms]:209
測定値:ストップウォッチ:.ElapsedMilliseconds [ms]:285
測定値:静的Stopwatch.GetTimestamp [ms]:187
測定値:ストップウォッチ+ DateTimeへの変換[ms]:319
DateTime.Now.Ticks [ms]:3040と比較してください

64ビット、リリースモード、最適化:

測定:GetTickCount64()[ms]:148
測定値:Environment.TickCount [ms]:31 (それでも価値はありますか?)
測定値:DateTime.UtcNow.Ticks [ms]:76 (!)
測定値:ストップウォッチ:.ElapsedTicks [ms]:178
測定値:ストップウォッチ:.ElapsedMilliseconds [ms]:226
測定値:静的Stopwatch.GetTimestamp [ms]:175
測定値:ストップウォッチ+ DateTimeへの変換[ms]:246
DateTime.Now.Ticks [ms]:3020と比較してください

非常に興味深いかもしれませんが、DateTime値を作成してストップウォッチ時間を出力することは、ほとんどコストがないようです。実用的な方法よりもアカデミックな方法で興味深いのは、静的なストップウォッチが(予想どおり)少し高速であることです。いくつかの最適化ポイントは非常に興味深いものです。たとえば、32ビットのみのStopwatch.ElapsedMillisecondsが、他のバリアント(静的なものなど)と比較して非常に遅い理由を説明できません。これとDateTime.Nowは、64ビットで速度を2倍以上にします。

数百万の実行に対してのみ、ストップウォッチの時間が重要になります。これが本当に当てはまる場合(ただし、マイクロ最適化が早すぎることに注意してください)、GetTickCount64()を使用するのは興味深いかもしれませんが、特にDateTime.UtcNowを使用すると、64ビット( long)ストップウォッチよりも精度は低いが、より高速なタイマー。32ビットの「ugい」Environment.TickCountをいじる必要はありません。

予想どおり、DateTime.Nowはすべての中で最も遅いです。

実行すると、コードは現在のストップウォッチの精度なども取得します。

完全なベンチマークコードは次のとおりです。

using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using static System.Environment;

[...]

    [DllImport("kernel32.dll") ]
    public static extern UInt64 GetTickCount64(); // Retrieves a 64bit value containing ticks since system start

    static void Main(string[] args)
    {
        const int max = 10_000_000;
        const int n = 3;
        Stopwatch sw;

        // Following Process&Thread lines according to tips by Thomas Maierhofer: https://codeproject.com/KB/testing/stopwatch-measure-precise.aspx
        // But this somewhat contradicts to assertions by MS in: https://msdn.Microsoft.com/en-us/library/windows/desktop/dn553408%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396#Does_QPC_reliably_work_on_multi-processor_systems__multi-core_system__and_________systems_with_hyper-threading
        Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1); // Use only the first core
        Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
        Thread.CurrentThread.Priority = ThreadPriority.Highest;
        Thread.Sleep(2); // warmup

        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; // only int capacity, enough for a bit more than 24 days
            }
            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; // just init
            sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < max; i++)
            {
                var a = new DateTime(sw.Elapsed.Ticks); // using variable dt here seems to make nearly no difference
            }
            sw.Stop();
            //Console.WriteLine($"Measured: Stopwatch+conversion to DateTime [s] with millisecs: {dt:s.fff}");
            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) ; // microsecond resolution: 1 million ticks per sec
        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);  // tickCount means millisec -> there are 10.000 milliseconds in 100 nanoseconds, which is the tick resolution in .NET, e.g. used for TimeSpan
        Console.WriteLine($"- Approximated capacity (maxtime) of TickCount [dd:hh:mm:ss] {maxTimeForTickCountInteger:dd:HH:mm:ss}");
        // this conversion from seems not really accurate, it will be between 24-25 days.
        Console.WriteLine($"{NewLine}Done.");

        while (Console.KeyAvailable)
            Console.ReadKey(false);
        Console.ReadKey();
    }
6
Philm

Environment.TickCountを使用する理由は次のとおりです。

  1. StopwatchクラスはCompact Frameworkにはありません。
  2. ストップウォッチはTickCountと同じ基本的なタイミングメカニズムを使用するため、結果の精度は多少なりとも変わりません。
  3. TickCountのラップアラウンドの問題は、宇宙的にヒットする可能性は低いです(コンピューターを27日間実行したままにして、ラップアラウンドの瞬間にまたがる時間を測定する必要があります)、そしてあなたがそれをヒットしたとしても、結果は巨大な負のタイムスパンになります(そのため、それは一種の目立つでしょう)。

そうは言っても、ストップウォッチを使用できる場合は、ストップウォッチを使用することもお勧めします。または、約1分かかり、Environment.TickCountをラップするストップウォッチのようなクラスを記述できます。

ところで、ストップウォッチのドキュメントには、基礎となるタイマーメカニズムのラップアラウンドの問題について言及しているものは何もないので、ストップウォッチが同じ問題に苦しんでいることを発見してもまったく驚かないでしょう。しかし、再び、私はそれについて心配する時間を費やしません。

0
MusiGenesis

私はそれをストップウォッチのクラスにラップするつもりでしたが、Grzenioはすでに正しいことを言っていたので、彼にアップティックを与えます。そのようなカプセル化は、どちらの方法がより良いかに関する決定を除外し、これは時間の経過とともに変化する可能性があります。一部のシステムで時間を費やすことができることにショックを受けたのを覚えているので、最高のテクニックを実装できる場所を1つ持つことは非常に重要です。

0
dongilmore

ワンショットタイミングの場合は、さらに簡単に記述できます

Stopwatch stopWatch = Stopwatch.StartNew();
...dostuff...
Debug.WriteLine(String.Format("It took {0} milliseconds",
                              stopWatch.EllapsedMilliseconds)));

ElapsedTicksフィールドが長いことを考えると、TickCountでの宇宙的に起こりそうもないラップアラウンドは、StopWatchにとってはそれほど心配ではないと思います。私のマシンでは、StopWatchは毎秒2.4e9ティックの高解像度です。そのレートであっても、ティックフィールドをオーバーフローさせるには121年以上かかります。もちろん、私はカバーの下で何が起こっているのかわからないので、塩の粒でそれを取る。ただし、StopWatchのドキュメントではラップアラウンドの問題についても言及されておらず、TickCountのドキュメントでは言及されています。

0
vanmelle

代わりに Stopwatch クラスを使用する必要があります。

0
Igal Tabachnik