【问题标题】:How do you test running time of VBA code?你如何测试 VBA 代码的运行时间?
【发布时间】:2010-09-16 22:31:34
【问题描述】:

VBA 中是否有代码可以包装一个函数,让我知道它的运行时间,以便我可以比较函数的不同运行时间?

【问题讨论】:

    标签: optimization testing vba profiling performance


    【解决方案1】:

    正如 Mike Woodhouse 回答的那样,QueryPerformanceCounter 函数是对 VBA 代码进行基准测试的最准确的方法(当您不想使用定制的 dll 时)。我写了一个类(链接https://github.com/jonadv/VBA-Benchmark),使该功能易于使用:

    1. 只初始化基准类
    2. 在您的代码之间调用该方法。

    不需要编写减法、重新初始化时间和编写调试的代码。

    Sub TimerBenchmark()
    
    Dim bm As New cBenchmark
    
    'Some code here
    bm.TrackByName "Some code"
    
    End Sub
    

    这会自动打印一个可读的表格到即时窗口:

    IDnr  Name       Count  Sum of tics  Percentage  Time sum
    0     Some code      1          163     100,00%     16 us
          TOTAL          1          163     100,00%     16 us
    
    Total time recorded:             16 us
    

    当然,只有一段代码的表格不是很有用,但有多段代码,你的代码中的瓶颈在哪里会立即变得清晰。该类包含一个 .Wait 函数,其作用与 Application.Wait 相同,但只需要以秒为单位的输入,而不是时间值(编码需要大量字符)。

    Sub TimerBenchmark()
    
    Dim bm As New cBenchmark
    
    bm.Wait 0.0001 'Simulation of some code
    bm.TrackByName "Some code"
    
    bm.Wait 0.04 'Simulation of some (time consuming) code here
    bm.TrackByName "Bottleneck code"
    
    
    bm.Wait 0.00004 'Simulation of some code, with the same tag as above
    bm.TrackByName "Some code"
    
    End Sub
    

    打印带有百分比的表格并汇总具有相同名称/标签的代码:

    IDnr  Name             Count  Sum of tics  Percentage  Time sum
    0     Some code            2       21.374       5,07%   2,14 ms
    1     Bottleneck code      1      400.395      94,93%     40 ms
          TOTAL                3      421.769     100,00%     42 ms
    
    Total time recorded:             42 ms
    

    【讨论】:

      【解决方案2】:

      带 2 个小数位的秒数:

      Dim startTime As Single 'start timer
      MsgBox ("run time: " & Format((Timer - startTime) / 1000000, "#,##0.00") & " seconds") 'end timer
      

      毫秒:

      Dim startTime As Single 'start timer
      MsgBox ("run time: " & Format((Timer - startTime), "#,##0.00") & " milliseconds") 'end timer
      

      带逗号分隔符的毫秒数:

      Dim startTime As Single 'start timer
      MsgBox ("run time: " & Format((Timer - startTime) * 1000, "#,##0.00") & " milliseconds") 'end timer
      

      这里留给那些像我一样正在寻找一个简单的计时器,格式为秒到 2 个小数位的人。这些是我喜欢使用的又短又甜的小计时器。它们只在子函数或函数的开头占用一行代码,最后又占用一行代码。这些并不意味着疯狂准确,我个人通常不关心小于 1/100 秒的任何事情,但是毫秒计时器将为您提供这 3 个中最准确的运行时间。我也读过你如果它碰巧在跨越午夜时运行,可能会得到不正确的读数,这是一个罕见的例子,但仅供参考。

      【讨论】:

      • 只有第一个有用,因为Timer的分辨率是10ms。
      【解决方案3】:
      Sub Macro1()
          Dim StartTime As Double
          StartTime = Timer
      
              ''''''''''''''''''''
                  'Your Code'
              ''''''''''''''''''''
          MsgBox "RunTime : " & Format((Timer - StartTime) / 86400, "hh:mm:ss")
      End Sub
      

      输出:

      运行时间:00:00:02

      【讨论】:

        【解决方案4】:

        VBA 中的 Timer 函数为您提供自午夜以来经过的秒数,精确到 1/100 秒。

        Dim t as single
        t = Timer
        'code
        MsgBox Timer - t
        

        【讨论】:

        • 那是行不通的——你不能通过这样取平均值来获得更多的分辨率。
        • 不过,如果您在 VBA 中测量性能,获得 1/100 秒的分辨率也不错。 -- 单独调用计时调用可能需要几毫秒。如果调用速度如此之快,以至于您需要如此高的分辨率来计时,那么您可能不需要有关该调用的性能数据。
        • 注意:在 Mac 上,计时器仅精确到一秒 - 如果它在午夜之前开始并在午夜之后结束,这可能会得到负数
        【解决方案5】:

        对于新手,这些链接说明了如何对您想要时间监控的所有潜艇进行自动分析:

        http://www.nullskull.com/a/1602/profiling-and-optimizing-vba.aspx

        http://sites.mcpher.com/share/Home/excelquirks/optimizationlink 参见http://sites.mcpher.com/share/Home/excelquirks/downlable-items中的procProfiler.zip

        【讨论】:

          【解决方案6】:

          除非您的函数非常慢,否则您将需要一个非常高分辨率的计时器。我知道的最准确的是QueryPerformanceCounter。谷歌它以获取更多信息。尝试将以下内容推送到一个类中,称之为CTimer,然后你可以在全局某个地方创建一个实例,然后调用.StartCounter.TimeElapsed

          Option Explicit
          
          Private Type LARGE_INTEGER
              lowpart As Long
              highpart As Long
          End Type
          
          Private Declare Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As LARGE_INTEGER) As Long
          Private Declare Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As LARGE_INTEGER) As Long
          
          Private m_CounterStart As LARGE_INTEGER
          Private m_CounterEnd As LARGE_INTEGER
          Private m_crFrequency As Double
          
          Private Const TWO_32 = 4294967296# ' = 256# * 256# * 256# * 256#
          
          Private Function LI2Double(LI As LARGE_INTEGER) As Double
          Dim Low As Double
              Low = LI.lowpart
              If Low < 0 Then
                  Low = Low + TWO_32
              End If
              LI2Double = LI.highpart * TWO_32 + Low
          End Function
          
          Private Sub Class_Initialize()
          Dim PerfFrequency As LARGE_INTEGER
              QueryPerformanceFrequency PerfFrequency
              m_crFrequency = LI2Double(PerfFrequency)
          End Sub
          
          Public Sub StartCounter()
              QueryPerformanceCounter m_CounterStart
          End Sub
          
          Property Get TimeElapsed() As Double
          Dim crStart As Double
          Dim crStop As Double
              QueryPerformanceCounter m_CounterEnd
              crStart = LI2Double(m_CounterStart)
              crStop = LI2Double(m_CounterEnd)
              TimeElapsed = 1000# * (crStop - crStart) / m_crFrequency
          End Property
          

          【讨论】:

          • 我在 Excel VBA 中实现了这一点(添加了本知识库文章中提到的开销:support.microsoft.com/kb/172338。效果很好。谢谢。
          • 谢谢,这对我也很有效。 TimeElapsed() 以毫秒为单位给出结果。我没有实施任何开销补偿,因为我更担心开销计算中卡顿的影响,而不是完美的准确性。
          • 这是很多无意中听到的(在要管理的代码行中)——如果你能忍受大约 10 毫秒的精度,@Kodak 的下面的答案在一行代码中给出了同样的事情(导入 GetTickCount来自 kernel32)。
          • 你如何使用StartCounterTimeElapsed?我在开始时做了一个CTimerWith StartCounter 的实例计时器,我只是在我的子开始后写了.StartCounter.TimeElapsed,它回答了我Invalid use of property。当我让.StartCounter 独自一人时,它告诉我没有设置对象。
          • 对于 excel 2010:Declare PtrSafe Functionstackoverflow.com/questions/21611744/…
          【解决方案7】:

          如果您想像秒表一样返回时间,您可以使用 以下 API 以毫秒为单位返回系统启动后的时间:

          Public Declare Function GetTickCount Lib "kernel32.dll" () As Long
          Sub testTimer()
          Dim t As Long
          t = GetTickCount
          
          For i = 1 To 1000000
          a = a + 1
          Next
          
          MsgBox GetTickCount - t, , "Milliseconds"
          End Sub
          

          http://www.pcreview.co.uk/forums/grab-time-milliseconds-included-vba-t994765.html 之后(因为 winmm.dll 中的 timeGetTime 对我不起作用,而且 QueryPerformanceCounter 对于所需任务来说太复杂了)

          【讨论】:

          • 这是一个很好的答案。注意:返回数据的精度以毫秒为单位,然而,计数器仅准确到大约 1/100 秒(也就是说,它可能关闭10 到 16 毫秒)通过 MSDN:msdn.microsoft.com/en-us/library/windows/desktop/…
          • hmm,如果这里的分辨率和 Timer 一样,那我就用 Timer
          • Public Declare Function ... 部分是什么?在我的底部添加代码时会产生错误
          • 您需要将此公共声明移至模块顶部
          【解决方案8】:

          多年来,我们一直使用基于 winmm.dll 中 timeGetTime 的解决方案来实现毫秒精度。见http://www.aboutvb.de/kom/artikel/komstopwatch.htm

          这篇文章是德文的,但下载中的代码(一个包装 dll 函数调用的 VBA 类)很简单,可以使用和理解,但无法阅读文章。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-02-03
            • 2013-06-18
            • 2022-01-09
            相关资源
            最近更新 更多