在线程中精确测量代码执行时间 (C#)

13

我正尝试在多个线程上尽可能准确地测量一些代码片段的执行时间,考虑到上下文切换和线程停机时间。该应用程序是使用C#(VS 2008)实现的。例如:

public void ThreadFunc ()
{
    // Some code here

    // Critical block #1 begins here
    long lTimestamp1 = Stopwatch.GetTimestamp ();

    CallComplex3rdPartyFunc (); // A

    long lTimestamp2 = Stopwatch.GetTimestamp ();
    // Critical block #1 ends here

    // Some code here

    // Critical block #2 begins here
    long lTimestamp3 = Stopwatch.GetTimestamp ();

    CallOtherComplex3rdPartyFunc (); // B

    long lTimestamp4 = Stopwatch.GetTimestamp ();
    // Critical block #2 ends here

    // Save timestamps for future analysis.
}

public int Main ( string[] sArgs )
{
    // Some code here

    int nCount = SomeFunc ();

    for ( int i = 0; i < nCount; i++ )
    {
        Thread oThread = new Thread ( ThreadFunc );
        oThread.Start ();
    }

    // Some code here

    return ( 0 );
}
我想尽可能准确地测量以上两个关键代码块的执行时间。标记为AB的两个调用是潜在的长函数调用,有时可能需要几秒钟才能执行,但在某些情况下,它们可能只需几毫秒就能完成。
我在许多线程上运行上述代码 - 1到200个线程之间,具体取决于用户输入。运行此代码的计算机具有2-16个核心 - 用户在较弱的机器上使用较低的线程计数。
问题在于AB都是潜在的长函数,因此在其执行期间很可能会发生至少一个上下文切换 - 可能不止一个。因此,代码获取lTimestamp1,然后另一个线程开始执行(当前线程等待)。最终,当前线程重新获得控制并检索lTimestamp2。
这意味着lTimestamp1lTimestamp2之间的持续时间包括线程实际上没有运行的时间 - 它正在等待再次调度,而其他线程在执行。然而,滴答数仍然增加,因此持续时间现在实际上是
Code block time = A + B + some time spent in other threads 而我希望它仅为
Code block time = A + B
这在更多线程的情况下尤其成问题,因为它们都有机会运行,所以上述时间将更长,而所有其他线程都在该线程再次获得运行机会之前运行。
那么我的问题是:是否可能以某种方式计算线程运行的时间,然后相应地调整上述时间?我希望完全消除(减去)第三项,或者至少尽可能减少它。代码运行数百万次,因此最终时间是从大量样本计算并平均的。
我不需要关注剖析器产品等 - 应用程序需要尽可能准确地计时这些标记部分。函数AB是第三方函数,我无法以任何方式更改它们。我也意识到,当使用纳秒精度测量时间并且3rd-party函数内部可能存在开销时,可能会出现波动,但我仍然需要进行此测量。
任何建议都将不胜感激- C++或x86汇编代码也可以。
编辑:似乎不可能实现这一点。Scott下面的想法(使用GetThreadTimes)很好,但不幸的是,GetThreadTimes()是一个有缺陷的API,它几乎永远不返回正确的数据。感谢所有答复!

除非修改.NET运行时并在那里收集时间(即使如此也会不准确!),否则无法做到这一点! - Yahia
5
好的,我会尽力进行翻译。以下是需要翻译的内容:Related: https://dev59.com/S1DTa4cB1Zd3GeqPNOcB相关链接:https://dev59.com/S1DTa4cB1Zd3GeqPNOcB - H H
3
我不知道这是评论还是解决方案,但你为什么不直接对代码进行分析和测量呢?我是说单独进行。即使使用操作系统,线程切换始终会存在。 - gbianchi
1
我认为没有不使用分析器的方法来完成这个任务。同时值得一提的是,在CPU密集型任务中,在16核机器上创建200个线程是适得其反的。 - Yaur
1
如果有其他可以被调度的线程,该线程将只花费时间等待被调度。为什么不将系统中的每个其他线程的优先级降低到从未运行的低水平?然后你只测量在你的线程中花费的时间,因为它是实际运行的系统中唯一的线程。 - Eric Lippert
显示剩余4条评论
2个回答

12

可以使用本机API调用GetThreadTimes来完成。这是一篇CodeProject上的文章演示了如何使用它。

第二个选择是使用QueryThreadCycleTime。这不会给你时间,但它会告诉你当前线程已经执行的周期数。

请注意,你不能直接将cycles->seconds进行转换,因为许多处理器(特别是移动处理器)不以固定速度运行,所以没有常数可供乘以以获取经过的秒数。但如果你使用的处理器不会改变其速度,那么从周期中获取挂钟时间将成为一个简单的数学问题。


他应该与Thread.BeginThreadAffinity一起使用,以确保代码在执行期间保持在同一物理线程上。 - user7116
Scott和sixlettervariables - 谢谢,这看起来很有前途。我得读完整篇CodeProject文章,但似乎这是一个不错的线索。 - xxbbcc
1
很抱歉,我必须取消“正确答案”的标记,尽管您的答案是最好的且最接近的。不幸的是,GetThreadTimes()是一个无用的API - 它的分辨率为15毫秒,并且当线程进入等待状态或让出时,它经常报告0作为内核时间和/或用户时间。看起来我正在尝试进行的这种测量在Windows上是不可能的。 - xxbbcc
@xxbbcc 我知道这是一个比较旧的问题,你可能不再需要它了,但请看一下我发布的更新。 - Scott Chamberlain
@ScottChamberlain 感谢您的更新 - 我知道可变速 CPU (这也是虚拟机中的一个问题)。我正在寻找一种收集频率信息和计时的方法,但即使有信息可用(很少),它们大多数情况下都不可靠。因为大多数硬件根本不报告有用的性能数字,所以我放弃了尝试。 - xxbbcc

2

1
@Yaur:我理解问题的关键点,我相信我们没有技术工具来处理上下文切换。 - sll
sll:不幸的是,仅通过 Stopwatch 我无法做到这一点,因为我不知道操作系统何时停止当前线程以切换到另一个线程。因此,我的问题确切在于即使我的当前线程未运行但正在等待重新调度时,底层 CPU 时钟仍然会流逝。显然,这不仅是应用程序中“当前”线程的问题,而且适用于我创建的所有线程,因为所有这些线程都试图进行相同的测量。 - xxbbcc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接