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

StackOverflow https://stackoverflow.com/questions/1416139

  •  06-07-2019
  •  | 
  •  

質問

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

更新:

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

注:

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

注2:

StopWatchアプローチを使用して、リアルタイムとの同期が外れていることに気付きました。約10時間後、5秒進んでいます。したがって、Xが1時間、30分、15分などになる可能性がある場合、Xごとに再同期する必要があると思います。再同期のたびにオフセットが変更されるため、再同期の最適な時間はどうなるかわかりません最大20ミリ秒。

役に立ちましたか?

解決

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

更新:ストップウォッチはシステム時間と同期していないようです(1時間に最大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時間ごとなど)でハイブリッドタイマーをリセットすると、ミニチュアの夏時間の問題のように、最後の読み取り時刻の前に時間を戻すリスクが生じます。

他のヒント

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

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

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

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

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

これらの提案はすべて難しすぎます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

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

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

1)。高解像度の絶対精度が必要な場合: DateTime.Now は使用できません 15ミリ秒間隔のクロックに基づいている場合(ただし、 「スライド」が可能フェーズ)。

代わりに、より優れた絶対解像度の外部ソース 精度時間(例:ntp)、以下の t1 は、 解決タイマー(StopWatch / QueryPerformanceCounter)。


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

サンプル DateTime.Now t1 )を、 高解像度タイマー(StopWatch / QueryPerformanceCounter) ( tt0 )。

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

t = t1 + (tt - tt0)

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

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

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

複数のデータベースに挿入し、事後に調整しようとすると、データベース時間のわずかなばらつきのために、取引注文の計算が間違っていることになります(あなたが言ったようにns単位でも増加します)

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

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

15ミリ秒(実際には15〜25ミリ秒)の精度は、Windows 55&nbsp; Hz / 65&nbsp; Hzタイマーの解像度に基づいています。これは、基本的なTimeSlice期間でもあります。影響を受けるのは Windows.Forms.Timer Threading.Thread.Sleep Threading.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を取得します。目的には十分です。

MusiGenesis 再同期のタイミングに関する回答

に関して、以下を追加します。

意味:再同期に使用する時間( MusiGenesis の回答の _maxIdle

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

  

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

したがって、2つの再同期間の時間( _maxIdle では SyncTime と呼ぶことができます)は、次の4つの関数である必要があります。

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

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

非同期の比率&lt; =精度の比率

例:精度を0.5s / hrsまたは1ms / dayなどよりも最悪にしたくない...(悪い英語:0.5s / hrs = 12s / dayよりも間違ったくない)

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

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

同期時間&gt; = DateTime.UtcNow 解像度

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

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

例を見てみましょう:

上記で説明した同期外れ率を想像してください。

  

約10時間後、5秒進んだ。

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

ポイントごとに:

  • DateTime.UtcNow の解像度: 1ms
  • 精度比&gt; =非同期比、 可能な限り最も正確なものを使用できます。精度比=非同期比
  • 必要な精度レベル: 1マイクロ秒
  • お使いのマシンの非同期率の推定値: 0.5s / hour (これも私の精度です)

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

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

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

したがって、ここで他の制約は次のとおりです。正確性比x SyncTime&lt;精度レベル、数値を切り上げることができるため、精度比x SyncTime&lt;精度レベル/ 2 は良好です。

問題は解決されました。

簡単に要約すると次のようになります:

  • タイマーの解像度を取得します。
  • tの推定値を計算する

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

scroll top