同じマシン上の 2 つのプロセス間の通信遅延を測定する必要がDateTime.UtcNow
ありますDateTime.Now
。DateTime.UtcNow
処理する。これでいいのか?それとももっと良い方法がありますか?
1 に答える
プロセス間の正確な時間を測定して比較することが目的の場合は、Windows API 関数を使用する必要がありますQueryPerformanceCounter()
。内部プロセッサ値を返すため、返される値はプロセス間で同期されます。
Stopwatch
もQueryPerformanceCounter()
その実装で使用しますが、返される絶対値を公開しないため、使用できません。
P/Invokeを使用して QueryPerformanceCounter() を呼び出す必要がありますが、それは非常に簡単です。
P/Invoke を使用するオーバーヘッドはわずかです。MSDN のドキュメントから:
PInvoke には、呼び出しごとに 10 ~ 30 個の x86 命令のオーバーヘッドがあります。この固定コストに加えて、マーシャリングによって追加のオーバーヘッドが発生します。マネージド コードとアンマネージド コードで同じ表現を持つ blittable 型の間でマーシャリング コストは発生しません。たとえば、int と Int32 の間の変換にはコストがかかりません。
から返される値はQueryPerformanceCounter()
long であるため、追加のマーシャリング コストは発生しないため、10 ~ 30 命令のオーバーヘッドが残ります。
UtcNow の解像度が約 10 ミリ秒であると述べられているこの MSDN ブログも参照してください。これは、パフォーマンス カウンターの解像度と比較するとかなり大きいです。(実際にはこれが Windows 8 に当てはまるとは思いませんが、私の測定では、UtcNow の解像度がミリ秒であることを示しているようです)。
とにかく、P/Invoking QueryPerformanceCounter() が DateTime.UtcNow を使用するよりも解像度が高いことを実証するのは簡単です。
次のコードのリリース ビルドを実行すると (デバッガーの外部から実行)、ほとんどすべての DateTime.UtcNow 経過時間が 0 であるのに対し、すべての QueryPerformanceCounter() 時間はゼロではないことがわかります。
Thread.Sleep(0)
これは、 DateTime.UtcNowの解像度が、呼び出しの経過時間を測定するのに十分高くないためQueryPerformanceCounter()
です。
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace ConsoleApplication1
{
internal class Program
{
private static void Main(string[] args)
{
for (int i = 0; i < 100; ++i)
{
var t1 = DateTime.UtcNow;
Thread.Sleep(0);
var t2 = DateTime.UtcNow;
Console.WriteLine("UtcNow elapsed = " + (t2-t1).Ticks);
}
for (int i = 0; i < 100; ++i)
{
long q1, q2;
QueryPerformanceCounter(out q1);
Thread.Sleep(0);
QueryPerformanceCounter(out q2);
Console.WriteLine("QPC elapsed = " + (q2-q1));
}
}
[DllImport("kernel32.dll", SetLastError=true)]
static extern bool QueryPerformanceCounter(out long lpPerformanceCount);
}
}
ここで、QueryPerformanceCounter() の呼び出しのオーバーヘッドが非常に高く、呼び出しにかかる時間ではなく、呼び出しにThread.Sleep(0)
かかる時間を測定している可能性があることに気付きました。これは、次の 2 つの方法で排除できます。
まず、最初のループを次のように変更できます。
for (int i = 0; i < 100; ++i)
{
var t1 = DateTime.UtcNow;
long dummy;
QueryPerformanceCounter(out dummy);
Thread.Sleep(0);
QueryPerformanceCounter(out dummy);
var t2 = DateTime.UtcNow;
Console.WriteLine("UtcNow elapsed = " + (t2-t1).Ticks);
}
これで、UtcNow は Thread.Sleep(0)とQueryPerformanceCounter() への 2 つの呼び出しのタイミングを計る必要があります。しかし、実行すると、ほとんどすべての経過時間がゼロであることがわかります。
次に、QueryPerformanceCounter() を 100 万回呼び出すのにかかる時間を計ることができます。
var t1 = DateTime.UtcNow;
for (int i = 0; i < 1000000; ++i)
{
long dummy;
QueryPerformanceCounter(out dummy);
}
var t2 = DateTime.UtcNow;
Console.WriteLine("Elapsed = " + (t2-t1).TotalMilliseconds);
私のシステムでは、QueryPerformanceCounter() を 100 万回呼び出すのに約 32 ミリ秒かかります。
最後に、DateTime.UtcNow を 100 万回呼び出すのにかかる時間を計測できます。
var t1 = DateTime.UtcNow;
for (int i = 0; i < 1000000; ++i)
{
var dummy = DateTime.UtcNow;
}
var t2 = DateTime.UtcNow;
Console.WriteLine("Elapsed = " + (t2-t1).TotalMilliseconds);
私のシステムでは、約 10 ミリ秒かかります。これは、QueryPerformanceCounter() を呼び出すよりも約 3 倍高速です。
要約すれば
したがって、DateTime.UtcNow のオーバーヘッドは P/Invoking QueryPerformanceCounter() よりも低くなりますが、解像度ははるかに低くなります。
だからあなたはあなたのお金を払い、あなたはあなたの選択をします!