web-dev-qa-db-ja.com

.NET / C#でティック精度のタイムスタンプを取得する方法は?

これまで、タイムスタンプの取得にDateTime.Nowを使用していましたが、DateTime.Nowをループで印刷すると、約1秒の急激なジャンプで増加することがわかりました。 15ミリ秒。しかし、アプリケーションの特定のシナリオでは、可能な限り最も正確なタイムスタンプを取得する必要があります。できればティック(= 100 ns)の精度で取得する必要があります。何か案は?

更新:

どうやら、StopWatch/QueryPerformanceCounterを使用する方法ですが、時間の測定にしか使用できないため、アプリケーションの起動時にDateTime.Nowを呼び出すことを考えていました。 StopWatchを実行してから、StopWatchからの経過時間をDateTime.Nowから返された初期値に追加するだけです。少なくとも、正確な相対タイムスタンプが得られるはずですよね?あなたはそれについてどう思いますか(ハック)?

注:

StopWatch.ElapsedTicksStopWatch.Elapsed.Ticksとは異なります!前者は1ティック= 100 nsを想定して使用しましたが、この場合は1ティック= 1/StopWatch.Frequencyです。したがって、DateTimeに相当するティックを取得するには、StopWatch.Elapsed.Ticksを使用します。私はこれを難しい方法で学びました。

注2:

StopWatchアプローチを使用すると、リアルタイムと同期しなくなることに気付きました。約10時間後、5秒進んでいます。したがって、Xが1時間、30分、15分などになる可能性がある場合、Xごとに再同期する必要があると思います。再同期のたびに、最大20ミリ秒。

61
user65199

DateTime.Now読み取りは15ミリ秒ごと(または一部のシステムでは10ミリ秒ごと)にのみ更新されるため、これらの間隔で時間が量子化されます。コードがマルチスレッドOSで実行されているという事実に起因する追加の量子化効果があります。したがって、アプリケーションが「生きて」いないため、実際の現在時刻を測定していない部分があります。

超正確なタイムスタンプ値を探しているため(任意の期間を計るのではなく)、Stopwatchクラス自体は必要なことを行いません。 DateTime/Stopwatchハイブリッドの一種でこれを自分で行う必要があると思います。アプリケーションが起動すると、現在のDateTime.UtcNow値(つまり、アプリケーションが開始するときの粗解像度時間)そして、次のようにStopwatchオブジェクトも開始します。

DateTime _starttime = DateTime.UtcNow;
Stopwatch _stopwatch = Stopwatch.StartNew();

その後、高解像度のDateTime値が必要なときはいつでも、次のようになります。

DateTime highresDT = _starttime.AddTicks(_stopwatch.Elapsed.Ticks);

また、_starttimeと_stopwatchを定期的にリセットして、結果の時間をシステム時間と同期しすぎないようにすることもできます(実際にこれが起こるかどうかはわかりませんが、とにかく時間がかかる) 。

更新:ストップウォッチdoesがシステム時間と同期しなくなるように見えるため(毎秒0.5秒時間)、時間を確認するために呼び出し間で経過する時間に基づいてハイブリッドDateTimeクラスをリセットすることは理にかなっていると思います:

public class HiResDateTime
{
    private static DateTime _startTime;
    private static Stopwatch _stopWatch = null;
    private static TimeSpan _maxIdle = 
        TimeSpan.FromSeconds(10);

    public static DateTime UtcNow
    {
        get
        {
            if ((_stopWatch == null) || 
                (_startTime.Add(_maxIdle) < DateTime.UtcNow))
            {
                Reset();
            }
            return _startTime.AddTicks(_stopWatch.Elapsed.Ticks);
        }
    }

    private static void Reset()
    {
        _startTime = DateTime.UtcNow;
        _stopWatch = Stopwatch.StartNew();
    }
}

一定の間隔(1時間ごとなど)でハイブリッドタイマーをリセットすると、最後の読み取り時刻の前に時間を戻すリスクが発生します。これは、夏時間の小さな問題のようなものです。

56
MusiGenesis

高解像度のティックカウントを取得するには、静的なStopwatch.GetTimestamp()メソッドを使用してください:

long tickCount = System.Diagnostics.Stopwatch.GetTimestamp();
DateTime highResDateTime = new DateTime(tickCount);

.NETソースコードをご覧ください。

    public static long GetTimestamp() {
        if(IsHighResolution) {
            long timestamp = 0;    
            SafeNativeMethods.QueryPerformanceCounter(out timestamp);
            return timestamp;
        }
        else {
            return DateTime.UtcNow.Ticks;
        }   
    }

ここにソースコード: http://referencesource.Microsoft.com/#System/services/monitoring/system/diagnosticts/Stopwatch.cs,69c6c3137e12dab4

21
Marcus.D

[受け入れられた回答はスレッドセーフではないように思われます。また、独自の承認によりタイムスタンプが重複するため、この代替回答]

(コメントごとに)本当に気になっているのが、厳密に昇順で割り当てられ、システム時間に可能な限り近い一意のタイムスタンプである場合、この代替アプローチを試すことができます。

public class HiResDateTime
{
   private static long lastTimeStamp = DateTime.UtcNow.Ticks;
   public static long UtcNowTicks
   {
       get
       {
           long orig, newval;
           do
           {
               orig = lastTimeStamp;
               long now = DateTime.UtcNow.Ticks;
               newval = Math.Max(now, orig + 1);
           } while (Interlocked.CompareExchange
                        (ref lastTimeStamp, newval, orig) != orig);

           return newval;
       }
   }
}
19
Ian Mercer

これらの提案はすべて難しすぎるようです! Windows 8またはServer 2012以降を使用している場合は、次のようにGetSystemTimePreciseAsFileTimeを使用します。

[DllImport("Kernel32.dll", CallingConvention = CallingConvention.Winapi)]
static extern void GetSystemTimePreciseAsFileTime(out long filetime);

public DateTimeOffset GetNow()
{
    long fileTime;
    GetSystemTimePreciseAsFileTime(out fileTime);
    return DateTimeOffset.FromFileTime(fileTime);
}

これは、DateTime.Nowよりもはるかに優れた精度を備えています。

詳細については、MSDNを参照してください: http://msdn.Microsoft.com/en-us/library/windows/desktop/hh706895(v = vs.85).aspx

11
Jamezor

オペレーティングシステムに認識されている最も正確な日付と時刻を返します。

オペレーティングシステムは、QueryPerformanceCounterおよびQueryPerformanceFrequency(.NET Stopwatchクラス)を介してより高い解像度のタイミングも提供します。これらを使用すると、間隔を計ることができますが、日付と時刻は提供しません。あなたはこれらがあなたに非常に正確な時間と日を与えることができると主張するかもしれませんが、私はそれらがどれほどひどく長い間隔でゆがんでいるかわかりません。

8
Jason Kresowaty

1)。高解像度の絶対精度が必要な場合:間隔が15µmsのクロックに基づいている場合、DateTime.Nowは使用できません(位相を「スライド」できない限り)。

代わりに、より高い解像度の絶対精度時間(たとえば、ntp)の外部ソース、以下のt1を高解像度タイマー(StopWatch/QueryPerformanceCounter)と組み合わせることができます。


2)。高解像度が必要な場合:

DateTime.Nowt1)を高解像度タイマー(StopWatch/QueryPerformanceCounter)(tt0)の値と一緒に1回サンプリングします。

高解像度タイマーの現在の値がttである場合、現在の時間tは次のとおりです。

t = t1 + (tt - tt0)

3)。別の方法としては、金融イベントの絶対時間と順序を解く方法があります:絶対時間の1つの値(15ミリ秒の解像度、場合によっては数分オフ)と順序の1つの値(たとえば、毎回1つずつ値を増やして保存します)それ)。注文の開始値は、システムのグローバル値に基づいたり、アプリケーションが開始する絶対時間から導出したりできます。

このソリューションは、クロック/タイマーの基になるハードウェア実装(システムによって異なる場合があります)に依存しないため、より堅牢です。

4
Peter Mortensen

これは非常に単純な作業には多すぎます。取引とともにデータベースにDateTimeを挿入するだけです。次に、取引注文を取得するには、値が増加するID列を使用します。

複数のデータベースに挿入し、事後に調整しようとすると、データベース時間のわずかなばらつきのために取引注文を誤って計算します(あなたが言ったようにnsの増分でさえ)

複数データベースの問題を解決するには、各取引がヒットして注文を取得する単一のサービスを公開します。サービスは、DateTime.UtcNow.Ticksと増加する取引番号を返すことができます。

上記のソリューションのいずれかを使用して、ネットワーク上のある場所からより多くのレイテンシーで取引を行う場合でも、最初に取引を行う可能性があります(実際)。このため、取引はユーザーのコンソールではなくデータベースに置かれていると見なす必要があります。

2
Nathaniel

15µms(実際には15-25µmsの可能性があります)の精度は、Windows 55Hz/65Hzタイマーの解像度に基づいています。これは、基本的なTimeSlice期間でもあります。影響を受けるのは Windows.Forms.TimerThreading.Thread.SleepThreading.Timer などです。

正確な間隔を取得するには、 ストップウォッチクラス を使用する必要があります。可能な場合は高解像度を使用します。以下のステートメントを試してみてください。

Console.WriteLine("H = {0}", System.Diagnostics.Stopwatch.IsHighResolution);
Console.WriteLine("F = {0}", System.Diagnostics.Stopwatch.Frequency);
Console.WriteLine("R = {0}", 1.0 /System.Diagnostics.Stopwatch.Frequency);

R = 6E-08秒、つまり60 nsを取得します。あなたの目的には十分でしょう。

2
Henk Holterman

MusiGenesis再同期のタイミングに対する回答について以下を追加します。

意味:再同期(_maxIdle inMusiGenesis回答)

このソリューションでは完全に正確ではないことがわかっているため、再同期します。しかし、暗黙的に必要なのはIan Mercerソリューションと同じものです:

厳密な昇順で割り当てられる一意のタイムスタンプ

したがって、2つの再同期(_maxIdle呼び出してみましょうSyncTime)は、次の4つの機能である必要があります。

  • DateTime.UtcNow 解決
  • 必要な精度の比率
  • 必要な精度レベル
  • マシンの非同期率の推定

明らかに、この変数の最初の制約は次のとおりです。

非同期比<=精度比

たとえば、精度を0.5秒/時間または1ミリ秒/日などよりも低くする必要はありません(悪い英語では:0.5秒/時間= 12秒/日よりも間違ったくないです)。

そのため、ストップウォッチがPCで提供するものよりも高い精度を達成することはできません。それは非同期率に依存しますが、これは一定ではないかもしれません。

もう1つの制約は、2つの再同期間の最小時間です。

同期時間> = DateTime.UtcNow解像度

ここで精度と精度がリンクされているのは、(たとえば、DBに格納するために)高い精度を使用しているが、低い精度を使用している場合、Ian Mercerステートメントそれは厳密な昇順です。

注:DateTime.UtcNowのデフォルトの解像度は15ms(私のマシンでは1ms)よりも大きいようですリンクをたどってください: 高精度DateTime.UtcNow

例を見てみましょう:

上記で説明した非同期の比率を想像してください。

約10時間後、5秒進んでいます。

マイクロ秒の精度が必要だとします。私のタイマー解像度は1msです(上記の注を参照)

ポイントごとに:

  • DateTime.UtcNow解像度:1ms
  • 精度比> =非同期率、可能な限り最も正確なものを使用してみましょう:精度比=非同期率
  • 必要な精度レベル:1マイクロ秒
  • マシンの同期外れ率の推定:0.5s/hour(これも私の精度です)

10秒ごとにリセットする場合、リセットの1ms前の9.999秒を想像してください。ここでは、この間隔中に電話をかけます。関数がプロットする時間は、0.5/3600 * 9.999s eq 1.39ms進んでいます。 10.000390秒の時間を表示します。 UtcNowチェックの後、390micro秒以内に電話をかけると、前の番号よりも劣った番号になります。この非同期の比率がCPUの負荷などに応じてランダムである場合、さらに悪化します。

今、SyncTimeを最小値に設定したとしましょう> 1msごとに再同期します

同じ考え方をすると、0.139マイクロ秒<私が望む精度よりも先に進むことになります。したがって、9.999ミリ秒で関数を呼び出すと、リセットの1マイクロ秒前に9.999がプロットされます。そして、私は10.000をプロットします。順調です。

したがって、ここで他の制約は次のとおりです:precision-ratio x SyncTime <precision level、数値を切り上げることができるため、accuracy-ratio x SyncTime <precision level/2良いです。

問題は解決されました。

だから簡単に要約すると:

  • タイマーの解像度を取得します。
  • 非同期率の推定値を計算します。
  • 精度比> =同期外れ率の推定値、最高精度=同期外れ率
  • 以下を考慮して、精度レベルを選択してください。
  • タイマー解像度<= SyncTime <= PrecisionLevel /(2 * accuracy-ratio)
  • 達成できる最高の精度はtimer-resolution * 2 * out-of-sync ratio

上記の比率(0.5/hr)の場合、正しいSyncTimeは3.6msになるため、3msに切り捨てられます。

上記の比率と1msのタイマー解像度で。 one-tick Precisionレベル(0.1microsec)が必要な場合、以下の非同期比が必要です:180ms /時間

独自の回答に対する最後の回答MusiGenesis状態:

@Hermann:過去2時間(リセット修正なしで)同様のテストを実行しており、ストップウォッチベースのタイマーは2時間後に約400ミリ秒先までしか実行されていないため、スキュー自体が変動しているように見えます(ただし、まだかなり厳しい)。スキューがこれほど悪いことにはかなり驚いています。これがストップウォッチがSystem.Diagnosticsにある理由だと思います。 – MusiGenesis

ストップウォッチの精度は200ms /時間に近く、ほぼ180ms /時間です。私たちの番号とこの番号がとても近い理由へのリンクはありますか?わからない。しかし、この精度は、ティック精度を達成するのに十分です

最高の精度レベル:上記の例では、0.27マイクロ秒です。

しかし、9.999msから再同期までの間に複数回呼び出すとどうなりますか。

関数への2回の呼び出しは、同じTimeStampが返されて終了する可能性があります(両方の時間は9.999になります(精度が向上しないため)。これを回避するには、上記の関係によってSyncTimeにリンクされているため、精度レベルに触れることはできません。そのため、これらの場合にはIan Mercerソリューションを実装する必要があります。

私の答えにコメントすることをheしないでください。

1
user3091460

ベンチマークを実行するためにタイムスタンプが必要な場合は、DateTime.Nowよりもはるかに精度の高い StopWatch を使用します。

0
Piotr Czapla