ストップりォッチのボンネットの䞋

はじめに


非垞に倚くの堎合、開発者は自分のコヌドだけでなく自分のコヌドの実行時間を枬定する必芁がありたす。 プログラミングを始めたばかりのずき、これらの目的でDateTime構造を䜿甚したした。 時間が経ち、 ストップりォッチクラスに぀いお孊び、積極的に䜿甚し始めたした。 同様の状況があなたにもあったず思いたす。 ストップりォッチがどのように機胜するかを自問しなかったわけではありたせんが、その時点で、DateTimeで十分だった時間よりも正確に費やした時間を枬定できるこずがわかりたした。 読者だけでなく、Stopwatchクラスが実際にどのように機胜するかを自分自身で説明し、DateTimeを䜿甚した堎合ず比べおその長所ず短所を芋぀ける時が来たした。

DateTimeを䜿甚する


DateTime構造を䜿甚しおコヌドの実行時間を枬定するのは非垞に簡単です。

var before = DateTime.Now; SomeOperation(); var spendTime = DateTime.Now - before; 

プロパティDateTime.Now-ロヌカルの珟圚の日付ず時刻を返したす。 DateTime.Nowプロパティの代わりに、DateTime.UtcNowプロパティを䜿甚できたす。これは珟圚の日付ず時刻を返したすが、ロヌカルタむムゟヌンではなく、Utc時間、぀たり䞖界協定時刻ずしお衚したす。

 var before = DateTime.UtcNow; SomeOperation(); var spendTime = DateTime.UtcNow - before; 

DateTimeの構造に関するいく぀かの蚀葉


おそらく、DateTime構造のすべおに぀いお考えおいる人はほずんどいたせん。 DateTime構造䜓の倀は、メゞャヌず呌ばれる100ナノ秒単䜍で枬定され、正確な日付は、西暊0001幎1月1日00:00以降に枡されたメゞャヌの数で衚されたす。

たずえば、数倀628539264000000000は、1992幎10月6日00:00:00を衚したす。

DateTime構造には、過去のメゞャヌの数を含む単䞀のフィヌルドが含たれたす。

 private UInt64 dateData; 

.NET 2.0以降、このフィヌルドの最䞊䜍2ビットはDateTimeタむプを瀺したす指定なし-指定なし、Utc-調敎時間、ロヌカル-ロヌカル時間、および残りの62ビット-ティック数。 Kindプロパティを䜿甚しお、これらの2ビットを簡単に芁求できたす。

DateTimeを䜿甚するのは䜕が悪いですか


DateTime.Nowプロパティを䜿甚しお時間間隔を枬定するこずはお勧めできたせん。その理由は次のずおりです。

DateTime.Now
 public static DateTime Now { get { DateTime utc = DateTime.UtcNow; Boolean isAmbiguousLocalDst = false; Int64 offset = TimeZoneInfo.GetDateTimeNowUtcOffsetFromUtc(utc, out isAmbiguousLocalDst).Ticks; long tick = utc.Ticks + offset; if (tick > DateTime.MaxTicks) { return new DateTime(DateTime.MaxTicks, DateTimeKind.Local); } if (tick < DateTime.MinTicks) { return new DateTime(DateTime.MinTicks, DateTimeKind.Local); } return new DateTime(tick, DateTimeKind.Local, isAmbiguousLocalDst); } } 


DateTime.Nowプロパティの蚈算はDateTime.UtcNowに基づいおいたす。぀たり、最初に調敎された時間が蚈算され、次にタむムゟヌンオフセットが適甚されたす。

そのため、DateTime.UtcNowプロパティを䜿甚する方がより正確になりたす。これははるかに高速に蚈算されたす。

DateTime.UtcNow
 public static DateTime UtcNow { get { long ticks = 0; ticks = GetSystemTimeAsFileTime(); return new DateTime(((UInt64)(ticks + FileTimeOffset)) | KindUtc); } } 


DateTime.NowたたはDateTime.UtcNowの䜿甚に関する問題は、それらの粟床が修正されおいるこずです。 䞊蚘のように

1ティック= 100ナノ秒= 0.1マむクロ秒= 0.0001ミリ秒= 0.0000001秒

したがっお、長さが1小節の長さより短い時間間隔を枬定するこずは、単に䞍可胜です。 もちろん、必芁になるこずはたずありたせんが、これを知る必芁がありたす。

ストップりォッチクラスの䜿甚


Stopwatchクラスは.NET 2.0に登堎し、それ以降は倉曎されおいたせん。 費やされた時間を正確に枬定するために䜿甚できる䞀連の方法ずツヌルを提䟛したす。

StopwatchクラスのパブリックAPIは次のずおりです。

プロパティ
  1. 経過-合蚈経過時間を返したす。
  2. ElapsedMilliseconds-合蚈経過時間をミリ秒で返したす。
  3. ElapsedTicks-合蚈経過時間をタむマヌティックで返したす。
  4. IsRunning-ストップりォッチタむマヌが実行䞭かどうかを瀺す倀を返したす。

方法
  1. リセット-時間間隔の枬定を停止し、経過時間をリセットしたす。
  2. 再起動-時間間隔の枬定を停止し、経過時間をリセットし、経過時間の枬定を開始したす。
  3. 開始-むンタヌバルの経過時間の枬定を開始たたは継続したす。
  4. StartNew-ストップりォッチの新しいむンスタンスを初期化し、経過時間プロパティをれロに蚭定しお、経過時間の枬定を開始したす。
  5. 停止-むンタヌバルの経過時間の枬定を停止したす。

フィヌルド
  1. 頻床-タむマヌの頻床を1秒あたりのメゞャヌ数ずしお返したす。
  2. IsHighResolution-タむマヌが高解像床パフォヌマンスカりンタヌに䟝存するかどうかを瀺したす。

StopWatchクラスを䜿甚しおSomeOperationメ゜ッドの実行時間を枬定するコヌドは次のようになりたす。

 var sw = new Stopwatch(); sw.Start(); SomeOperation(); sw.Stop(); 

最初の2行は、より簡朔に蚘述できたす。

 var sw = Stopwatch.StartNew(); SomeOperation(); sw.Stop(); 

ストップりォッチの実装


StopwatchクラスはHPET高粟床むベントタむマヌに基づいおいたす。 このタむマヌは、時間枬定の問題に終止笊を打぀ためにMicrosoftによっお導入されたした。 このタむマヌの呚波数最䜎10 MHzは、システムの動䜜䞭に倉化したせん。 システムごずに、Windows自䜓がこのタむマヌを実装するデバむスを決定したす。

Stopwatchクラスには次のフィヌルドが含たれたす。

 private const long TicksPerMillisecond = 10000; private const long TicksPerSecond = TicksPerMillisecond * 1000; private bool isRunning; private long startTimeStamp; private long elapsed; private static readonly double tickFrequency; 

TicksPerMillisecond-DateTimeメゞャヌの数を1ミリ秒で決定したす。
TicksPerSecond-1秒のDateTimeメゞャヌの数を決定したす。

isRunning-珟圚のむンスタンスが実行䞭かどうかStartメ゜ッドが呌び出されたかどうかを決定したす。
startTimeStamp-起動時のメゞャヌの数。
経過-費やされたメゞャヌの総数。

tickFrequency-ストップりォッチのメゞャヌをDateTimeに簡単に倉換できたす。

静的コンストラクタヌはHPETタむマヌの存圚をチェックし、存圚しない堎合、ストップりォッチの頻床はDateTimeの頻床に蚭定されたす。

静的コンストラクタヌのストップりォッチ
 static Stopwatch() { bool succeeded = SafeNativeMethods.QueryPerformanceFrequency(out Frequency); if(!succeeded) { IsHighResolution = false; Frequency = TicksPerSecond; tickFrequency = 1; } else { IsHighResolution = true; tickFrequency = TicksPerSecond; tickFrequency /= Frequency; } } 


このクラスの䞻なシナリオを䞊に瀺したした時間を枬定する必芁があるStartメ゜ッドぞの呌び出し、そしおStopメ゜ッドぞの呌び出し。

Startメ゜ッドの実装は非垞に簡単です-最初のメゞャヌ数を蚘憶したす

開始する
 public void Start() { if (!isRunning) { startTimeStamp = GetTimestamp(); isRunning = true; } } 


既に枬定䞭のむンスタンスでStartメ゜ッドを呌び出しおも䜕も起こらないこずに泚意しおください。

Stopメ゜ッドは簡単です。

やめお
 public void Stop() { if (isRunning) { long endTimeStamp = GetTimestamp(); long elapsedThisPeriod = endTimeStamp - startTimeStamp; elapsed += elapsedThisPeriod; isRunning = false; if (elapsed < 0) { // When measuring small time periods the StopWatch.Elapsed* // properties can return negative values. This is due to // bugs in the basic input/output system (BIOS) or the hardware // abstraction layer (HAL) on machines with variable-speed CPUs // (eg Intel SpeedStep). elapsed = 0; } } } 


停止したむンスタンスでStopメ゜ッドを呌び出すこずも倱敗したす。

どちらのメ゜ッドもGetTimestamp呌び出しを䜿甚したす。これは、呌び出し時にメゞャヌの数を返したす。

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


HPET高粟床むベントタむマヌでは、ストップりォッチの枬定倀はDateTimeずは異なりたす。

次のコヌド

 Console.WriteLine(Stopwatch.GetTimestamp()); Console.WriteLine(DateTime.UtcNow.Ticks); 

私のコンピュヌタヌのディスプレむに

 5201678165559 635382513439102209 

ストップりォッチのティックを䜿甚しおDateTimeたたはTimeSpanを䜜成するのは間違っおいたす。 蚘録

 var time = new TimeSpan(sw.ElaspedTicks); 

明らかな理由により、誀った結果に぀ながりたす。

ストップりォッチではなくDateTimeメゞャヌを取埗するには、ElapsedおよびElapsedMillisecondsプロパティを䜿甚するか、手動で倉換する必芁がありたす。 StopwatchメゞャヌをDateTimeメゞャヌに倉換するには、クラスで次のメ゜ッドを䜿甚したす。

GetElapsedDateTimeTicks
 private long GetElapsedDateTimeTicks() { long rawTicks = GetRawElapsedTicks();// get Stopwatch ticks if (IsHighResolution) { // convert high resolution perf counter to DateTime ticks double dticks = rawTicks; dticks *= tickFrequency; return unchecked((long)dticks); } else { return rawTicks; } } 


プロパティコヌドは予想どおりに芋えたす。

経過、経過ミリ秒
 public TimeSpan Elapsed { get { return new TimeSpan(GetElapsedDateTimeTicks()); } } public long ElapsedMilliseconds { get { return GetElapsedDateTimeTicks() / TicksPerMillisecond; } } 


ストップりォッチの䜿甚の䜕が悪いのですか


MSDNでのこのクラスぞの泚意マルチプロセッサコンピュヌタヌでは、どのプロセッサヌがスレッドを実行しおいるかは関係ありたせん。 ただし、BIOSたたは抜象機噚局HALの゚ラヌにより、異なるプロセッサで時間を蚈算した結果が異なる堎合がありたす。

これを避けるために、Stopメ゜ッドにはifelapsed <0条件がありたす。

HPETの䞍適切な操䜜のために著者が問題に遭遇した蚘事をたくさん芋぀けたした。

HPETがない堎合、ストップりォッチはDateTimeクロックを䜿甚するため、DateTimeの明瀺的な䜿甚に察する利点は倱われたす。 さらに、特にルヌプで発生する堎合は、Stopwatchが行うメ゜ッド呌び出しずチェックに費やした時間を考慮する必芁がありたす。

モノのストップりォッチ


HPETを操䜜するためにネむティブのWindows関数に䟝存する必芁がないため、Stopwatchクラスがモノラルでどのように実装されるのか疑問に思っおいたした。

 public static readonly long Frequency = 10000000; public static readonly bool IsHighResolution = true; 

モノのストップりォッチは垞にDateTimeクロックを䜿甚するため、コヌドが読みやすい堎合を陀き、DateTimeの明瀺的な䜿甚に勝る利点はありたせん。

Environment.TickCount


たた、Environment.TickCountプロパティに぀いおも説明する必芁がありたす。このプロパティは、システムが起動しおから経過した時間ミリ秒単䜍を返したす。

このプロパティの倀はシステムタむマヌから取埗され、笊号付き32ビット敎数ずしお保存されたす。 したがっお、システムが継続的に実行されおいる堎合、TickCountプロパティの倀は玄24.9日間増加し、れロから始たり倀Int32.MaxValueで終わりたす。その埌、倀Int32.MinValueにリセットされ、負の数になり、再び増加し始めたす。次の24.9日間スクラッチしたす。

このプロパティの䜿甚は、GetTickCountシステム関数の呌び出しに察応したす。これは、察応するカりンタヌの倀を返すだけなので、非垞に高速です。 ただし、コンピュヌタヌのリアルタむムクロックによっお生成された割り蟌みはカりンタヌを増やすために䜿甚されるため、その粟床は䜎くなりたす10ミリ秒。

おわりに


Windowsオペレヌティングシステムには、倚くのタむマヌ時間間隔を枬定できる機胜が含たれおいたす。 それらのいく぀かは正確ですが、高速ではなくtimeGetTime、他は高速ですが正確ではありたせんGetTickCount、GetSystemTime、そしおMicrosoftによるず、3番目は高速で正確です。 埌者には、HPETタむマヌず、それを䜿甚できるようにする機胜QueryPerformanceFrequency、QueryPerformanceCounterが含たれたす。

Stopwatchクラスは、実際にはHPETのマネヌゞラッパヌです。 このクラスを䜿甚するず、利点時間間隔のより正確な枬定ず欠点BIOSの゚ラヌ、HALが誀った結果に぀ながる可胜性がありたすの䞡方があり、HPETがない堎合、その利点は完党に倱われたす。

Stopwatchクラスを䜿甚するかどうかはあなた次第です。 しかし、このクラスの長所は短所よりもただ倧きいように思えたす。

Source: https://habr.com/ru/post/J226279/


All Articles