c# – 测量跨进程的时间

我需要测量同一台机器上两个进程之间的通信延迟.我想出的最好的方法是将DateTime.UtcNow(DateTime.Now似乎非常慢,以至于它极大地扭曲了我的测量结果)序列化到消息中并进行比较它与另一个进程中的DateTime.UtcNow一起使用.这是不是很好?或者,还有更好的方法? 最佳答案 如果您的目标是测量和比较进程之间的确切时间,则应使用
Windows API函数
QueryPerformanceCounter().它返回的值在进程之间同步,因为它返回内部处理器值.

秒表在其实现中也使用QueryPerformanceCounter(),但它不公开返回的绝对值,因此您无法使用它.

你将不得不use P/Invoke to call QueryPerformanceCounter()但它很容易.

使用P / Invoke的开销很小. From the MSDN documentation

PInvoke has an overhead of between 10 and 30 x86 instructions per call. In addition to this fixed cost, marshaling creates additional overhead. There is no marshaling cost between blittable types that have the same representation in managed and unmanaged code. For example, there is no cost to translate between int and Int32.

由于QueryPerformanceCounter()返回的值很长,因此不会产生额外的编组成本,因此您需要支付10-30条指令的开销.

另请参阅this MSDN blog,其中指出UtcNow的分辨率约为10ms – 与性能计数器的分辨率相比,这是非常大的. (虽然我实际上并不认为这对Windows 8来说是正确的;我的测量结果似乎表明UtcNow具有毫秒级的分辨率).

无论如何,很容易证明P / Invoking QueryPerformanceCounter()具有比使用DateTime.UtcNow更高的分辨率.

如果您运行以下代码的发布版本(从OUTSIDE运行调试器),您将看到几乎所有DateTime.UtcNow经过的时间都是0,而所有QueryPerformanceCounter()都是非零的.

这是因为DateTime.UtcNow的分辨率不足以测量调用Thread.Sleep(0)所用的时间,而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)需要多长时间.我们可以通过两种方式消除它:

首先,我们可以修改第一个循环如下:

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().但如果你运行它,你仍然会看到几乎所有经过的时间都为零.

其次,我们可以花费多长时间调用QueryPerformanceCounter()一百万次:

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()大约需要32ms.

最后,我们可以计算调用DateTime.UtcNow一百万次所需的时间:

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(),但它的分辨率要低得多.

所以你付钱给你,你就做出了选择!

点赞