4

同じマシン上の 2 つのプロセス間の通信遅延を測定する必要がDateTime.UtcNowありますDateTime.NowDateTime.UtcNow処理する。これでいいのか?それとももっと良い方法がありますか?

4

1 に答える 1

2

プロセス間の正確な時間を測定して比較することが目的の場合は、Windows API 関数を使用する必要がありますQueryPerformanceCounter()。内部プロセッサ値を返すため、返される値はプロセス間で同期されます。

StopwatchQueryPerformanceCounter()その実装で使用しますが、返される絶対値を公開しないため、使用できません。

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() よりも低くなりますが、解像度ははるかに低くなります。

だからあなたはあなたのお金を払い、あなたはあなたの選択をします!

于 2013-04-23T09:04:15.227 に答える