4

我需要测量同一台机器上两个进程之间的通信延迟。我想出的最好方法是将消息序列化DateTime.UtcNowDateTime.Now似乎非常慢,以至于它极大地扭曲了我的测量结果)并将其与DateTime.UtcNow另一个消息进行比较过程。这是最好的吗?或者,还有更好的方法?

4

1 回答 1

2

如果您的目标是测量和比较进程之间的确切时间,则应使用 Windows API 函数QueryPerformanceCounter()。它返回的值在进程之间是同步的,因为它返回一个内部处理器值。

Stopwatch也在QueryPerformanceCounter()其实现中使用,但它不公开返回的绝对值,因此您不能使用它。

您将不得不使用 P/Invoke 来调用 QueryPerformanceCounter()但这很容易。

使用 P/Invoke 的开销很小。从 MSDN 文档

PInvoke 每次调用的开销在 10 到 30 条 x86 指令之间。除了这个固定成本之外,编组还会产生额外的开销。在托管和非托管代码中具有相同表示的 blittable 类型之间没有编组成本。例如,在 int 和 Int32 之间进行转换是免费的。

由于从返回的值QueryPerformanceCounter()是长的,因此不会产生额外的封送处理成本,因此您只剩下 10-30 条指令的开销。

另请参阅此 MSDN 博客,其中指出 UtcNow 的分辨率约为 10 毫秒 - 与性能计数器的分辨率相比,这是相当大的。(虽然我实际上不相信这适用于 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() 一百万次大约需要 32 毫秒。

最后,我们可以计算调用 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(),但它的分辨率要低得多。

所以你付了钱,你就可以选择了!

于 2013-04-23T09:04:15.227 回答