跨流程度量时间

本文关键字:度量 时间 程度量 | 更新日期: 2023-09-27 18:10:31

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

跨流程度量时间

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

Stopwatch在它的实现中也使用了QueryPerformanceCounter(),但是它没有公开返回的绝对值,所以你不能使用它。

你必须使用p/Invoke来调用QueryPerformanceCounter(),但这很容易。

使用p/Invoke的开销很小。来自MSDN文档:

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

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

也可以看看MSDN的博客,上面说UtcNow的分辨率大约是10ms——这与性能计数器的分辨率相比是相当大的。(虽然我不认为这对Windows 8来说是真的;我的测量似乎表明,UtcNow具有毫秒级的分辨率)。

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

如果您运行以下代码的发布版本(从外部调试器运行),您将看到几乎所有的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);

在我的系统上大约需要10ms,这比调用QueryPerformanceCounter()快3倍。

总之

所以DateTime。UtcNow的开销比P/调用QueryPerformanceCounter()要低,但分辨率要低得多。

所以你付你的钱,你做你的选择!