测量跨过程的时间

时间:2013-04-23 08:56:42

标签: c# .net datetime benchmarking

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

1 个答案:

答案 0 :(得分:2)

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

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

你必须use P/Invoke to call QueryPerformanceCounter(),但这很容易。

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

  

PInvoke每次调用的开销为10到30 x86。除了这个固定成本之外,编组还会产生额外的开销。在管理和非托管代码中具有相同表示的blittable类型之间没有编组成本。例如,在int和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(),但它的分辨率要低得多。

所以你支付了钱,然后你就做出了选择!