C# タイマーの周波数が極端にずれているのはなぜですか?
質問
両方 System.Timers.Timer
そして System.Threading.Timer
要求された間隔とは大幅に異なる間隔で発射する。例えば:
new System.Timers.Timer(1000d / 20);
1 秒あたり 20 回ではなく 16 回起動するタイマーが生成されます。
長すぎるイベント ハンドラーによる副作用がないことを確認するために、次の小さなテスト プログラムを作成しました。
int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };
// Test System.Timers.Timer
foreach (int frequency in frequencies)
{
int count = 0;
// Initialize timer
System.Timers.Timer timer = new System.Timers.Timer(1000d / frequency);
timer.Elapsed += delegate { Interlocked.Increment(ref count); };
// Count for 10 seconds
DateTime start = DateTime.Now;
timer.Enabled = true;
while (DateTime.Now < start + TimeSpan.FromSeconds(10))
Thread.Sleep(10);
timer.Enabled = false;
// Calculate actual frequency
Console.WriteLine(
"Requested frequency: {0}\nActual frequency: {1}\n",
frequency, count / 10d);
}
出力は次のようになります。
要求された内容:5Hz;実際の:4.8Hz
要求された内容:10Hz;実際の:9.1Hz
要求された内容:15Hz;実際の:12.7Hz
要求された内容:20Hz;実際の:16Hz
要求された内容:30Hz;実際の:21.3Hz
要求された内容:50Hz;実際の:31.8Hz
要求された内容:75Hz;実際の:63.9Hz
要求された内容:100Hz;実際の:63.8Hz
要求された内容:200Hz;実際の:63.9Hz
要求された内容:500Hz;実際の:63.9Hz
実際の周波数は、要求された周波数から最大 36% 逸脱します。(そして明らかに 64 Hz を超えることはできません。) Microsoft がこのタイマーを推奨していることを考慮すると、このタイマーは「精度が高い」という理由で推奨されています。 System.Windows.Forms.Timer
, 、これは私を困惑させます。
ところで、これらはランダムな偏差ではありません。それらは毎回同じ値です。他のタイマー クラスにも同様のテスト プログラムを作成します。 System.Threading.Timer
, 、まったく同じ結果が表示されます。
実際のプログラムでは、1 秒あたり正確に 50 サンプルで測定値を収集する必要があります。これにはまだリアルタイム システムは必要ありません。また、1 秒あたり 50 ではなく 32 のサンプルを取得するのは非常にイライラします。
何か案は?
@クリス:おっしゃるとおり、間隔はすべて 1/64 秒あたりの整数倍であるように見えます。ところで、イベント ハンドラーに Thread.Sleep(...) を追加しても、違いはありません。そう考えるとこれは理にかなっています System.Threading.Timer
スレッド プールを使用するため、各イベントは空きスレッドで発生します。
解決
実際には、最大 100 Hz で異なる数値が得られますが、多少の大きな偏差はありますが、ほとんどの場合、要求された数値に近くなります (XP SP3 を最新の .NET SP で実行している場合)。
System.Timer.Timer は System.Threading.Timer を使用して実装されているため、同じ結果が表示される理由はこれで説明されています。タイマーは何らかのスケジューリングアルゴリズムなどを使用して実装されていると思います。(これは内部呼び出しです。おそらく Rotor 2.0 を参照すると、何らかの解明が得られるかもしれません)。
Sleep とコールバックを呼び出す別のスレッド (またはその組み合わせ) を使用して、一種のタイマーを実装することをお勧めします。結果についてはわかりませんが。
そうでなければ、あなたは見てみるかもしれません マルチメディアタイマー (PInvoke)。
他のヒント
winmm.dll を使用すると、より多くの CPU 時間を使用できますが、制御が向上します。
winmm.dll タイマーを使用するように変更された例は次のとおりです。
const String WINMM = "winmm.dll";
const String KERNEL32 = "kernel32.dll";
delegate void MMTimerProc (UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2);
[DllImport(WINMM)]
static extern uint timeSetEvent(
UInt32 uDelay,
UInt32 uResolution,
[MarshalAs(UnmanagedType.FunctionPtr)] MMTimerProc lpTimeProc,
UInt32 dwUser,
Int32 fuEvent
);
[DllImport(WINMM)]
static extern uint timeKillEvent(uint uTimerID);
// Library used for more accurate timing
[DllImport(KERNEL32)]
static extern bool QueryPerformanceCounter(out long PerformanceCount);
[DllImport(KERNEL32)]
static extern bool QueryPerformanceFrequency(out long Frequency);
static long CPUFrequency;
static int count;
static void Main(string[] args)
{
QueryPerformanceFrequency(out CPUFrequency);
int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };
foreach (int freq in frequencies)
{
count = 0;
long start = GetTimestamp();
// start timer
uint timerId = timeSetEvent((uint)(1000 / freq), 0, new MMTimerProc(TimerFunction), 0, 1);
// wait 10 seconds
while (DeltaMilliseconds(start, GetTimestamp()) < 10000)
{
Thread.Sleep(1);
}
// end timer
timeKillEvent(timerId);
Console.WriteLine("Requested frequency: {0}\nActual frequency: {1}\n", freq, count / 10);
}
Console.ReadLine();
}
static void TimerFunction(UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2)
{
Interlocked.Increment(ref count);
}
static public long DeltaMilliseconds(long earlyTimestamp, long lateTimestamp)
{
return (((lateTimestamp - earlyTimestamp) * 1000) / CPUFrequency);
}
static public long GetTimestamp()
{
long result;
QueryPerformanceCounter(out result);
return result;
}
そして、これが私が得る出力です:
Requested frequency: 5
Actual frequency: 5
Requested frequency: 10
Actual frequency: 10
Requested frequency: 15
Actual frequency: 15
Requested frequency: 20
Actual frequency: 19
Requested frequency: 30
Actual frequency: 30
Requested frequency: 50
Actual frequency: 50
Requested frequency: 75
Actual frequency: 76
Requested frequency: 100
Actual frequency: 100
Requested frequency: 200
Actual frequency: 200
Requested frequency: 500
Actual frequency: 500
お役に立てれば。
これらのクラスはリアルタイムでの使用を目的としたものではなく、Windows などのオペレーティング システムの動的スケジューリングの性質の影響を受けます。リアルタイムの実行が必要な場合は、おそらく組み込みハードウェアを検討することになるでしょう。100% 確信はありませんが、.netcpu はチップ上の小型の .NET ランタイムのリアルタイム バージョンである可能性があると思います。
http://www.arm.com/markets/emerging_applications/armpp/8070.html
もちろん、それらの間隔に付加されたコードが非リアルタイム オペレーティング システムで実行されることを考慮すると、それらの間隔の精度がどれほど重要であるかを評価する必要があります。もちろん、これが純粋に学術的な質問である場合は別ですが(その場合、はい、興味深いです!):P)。
実際のタイマー周波数は 63.9 Hz またはその整数倍のようです。
これは、タイマーの分解能が約 15 ミリ秒 (またはその整数倍、つまり 15 ミリ秒) であることを意味します。30 ミリ秒、45 ミリ秒など)。
これは、「ティック」の整数倍に基づくタイマーであることが予想されます (たとえば、DOS では、「ティック」の値は 55 ミリ秒 / 18 Hz でした)。
なぜティック数が偶数の 15 ミリ秒ではなく 15.65 ミリ秒なのかわかりません。実験として、タイマー ハンドラー内で数ミリ秒スリープするとどうなるでしょうか。ティック間は 15 ミリ秒で、タイマー ハンドラーでは各ティックで 0.65 ミリ秒が表示されるでしょうか?
Windows (およびその上で実行される .NET) は、プリエンプティブなマルチタスク オペレーティング システムです。特定のスレッドはいつでも別のスレッドによって停止される可能性があり、プリエンプトするスレッドが適切に動作しない場合、必要なときに制御を取り戻すことができなくなります。 必要 それ。
簡単に言えば、これが、正確なタイミングを取得することが保証できない理由であり、Windows と .NET が特定の種類のソフトウェアに適さないプラットフォームである理由です。必要なときに正確に制御できないために命が危険にさらされている場合は、別のプラットフォームを選択してください。
リアルタイム環境に移行する必要がある場合、私は過去に (カスタム シリアル デバイスからの) 確定的サンプリングが必要だったときに RTX を使用したことがあり、非常にうまくいきました。
これはマルチメディアタイマーを使用したタイマーの優れた実装ですhttp://www.softwareinteractions.com/blog/2009/12/7/using-the-multimedia-timer-from-c.html
タイマーの周波数がオフになる理由はたくさんあります。ハードウェアの例では、同じスレッドが別の処理でビジー状態であるなど...
より正確な時刻が必要な場合は、System.Diagnostics 名前空間の Stopwatch クラスを使用します。
問題の一部は、タイマーが 2 つあることです。 遅れ アカウントへ。これは、OS でのタイマーの実装方法によって異なる場合があります。
- 待ち時間をリクエストする時間
- #1 が発生してからプロセスがスケジューリング キューで順番を取得するまでの時間。
タイマーは#1の制御は良好ですが、#2の制御はほとんどありません。再度実行したいという信号を OS に送信できますが、OS は必要に応じて自由に起動できます。
あなたのコメントに基づくと、タイマーはまったく使用すべきではありません。クォンタムを失わないように、ストップウォッチを備えたループを使用して間隔を確認し、スピンロックを使用する必要があります。