System.DateTime.Ticksの精度と高精度タイマ
前にも書いたが、.NET C#における処理の経過時間を測るコードの書き方としてはDateTimeクラスを使った
long start = System.DataTime.Now.Ticks; 〜処理〜 long elapsedTime = System.DataTime.Now.Ticks - start;
又は、.NET2.0で追加されたStopwatchクラスを使った
Stopwatch sw = new Stopwatch(); sw.Start(); 〜処理〜 sw.Stop(); long elapsedTime = sw.ElapsedMilliseconds;
が考えられるが、どちらもADO.NETのDbDataReader.Readを開始して終了するまでの時間を計測したりすると※1、あるはずの無い0msとかが返ってきたりして使い物にならない。このような場合いろいろな方法が考えられるが、Windows Multimedia SDK(winmm.dll)のAPIであるtimeGetTime()を使用して精度の高いタイマクラスを用意するのが簡単なようので、試しにC#で書いてみた。
- 1msの精度を指定した高解像度タイマクラスのサンプル ※2
public sealed class HighResolutionTimer : IDisposable { private uint startTime; public HighResolutionTimer() { Native.BeginPeriod(1); this.Restart(); } public void Restart() { this.startTime = Native.GetTime(); } public uint ElapsedMilliseconds { get { return Native.GetTime() - this.startTime; } } public void Dispose() { Native.EndPeriod(1); } private static class Native { [DllImport("winmm.dll", EntryPoint = "timeGetTime")] public static extern uint GetTime(); [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")] public static extern uint BeginPeriod(uint uMilliseconds); [DllImport("winmm.dll", EntryPoint = "timeEndPeriod")] public static extern uint EndPeriod(uint uMilliseconds); } }
使い方は簡単。
HighResolutionTimer timer = new HighResolutionTimer(); 〜処理〜 uint elapsedTime = timer.ElapsedMilliseconds; timer.Dispose();
汎用的に使うならばこのHighResolutionTimerクラスには問題がある。それはwinmm.dllで提供されるタイマーを使うのはこのクラスのインスタンスである自分自身だけと決め付けていることだ。
従って、実際には複数のインスタンスを生成した際にタイマの初期化を無効にする、インスタンスの破棄時に必要な時だけタイマ精度のリセットする等の制御が必要なので、真似て使う場合は用途に応じて各自書き換えて欲しい。特にタイマ精度のリセット(timeEndPeriod関数のコール)は重要で、これをしないと他でタイマを使う際にデフォルトの精度を期待しているプロセス(スレッド?)が異常な動作をするかもしれない。
※1 どんな処理の計測を行った場合にTickの差分が0で戻るかを明示的に書き換えた
※2 実際のタイマの精度はO/Sやプラットホームにより指定できる値が変化するかもしれない。
追記 : DateTime#Ticks及びStopwatchクラスのタイマは充分な精度があることが、4/5の検証で判明したため、訂正させて頂く。詳しくは4/5のエントリを見て頂きたい。なお、高精度版のタイマクラスに関しては別に使えないわけではないが、.NET2.0を使う場合はStopwatchクラスで充分に事足りるということだ。