【问题标题】:Calculating the speed of routines?计算例程的速度?
【发布时间】:2011-05-17 12:12:29
【问题描述】:

确定处理例程(例如函数过程)所需时间的最佳和最准确的方法是什么?

我问是因为我目前正在尝试优化我的应用程序中的一些功能,当我测试更改时,仅通过查看它很难确定是否有任何改进。因此,如果我可以返回一个准确或接近准确的处理例程所花费的时间,那么我就会更清楚地了解代码是否进行了任何更改。

我考虑过使用 GetTickCount,但我不确定这是否接近准确?

如果有一个可重复使用的函数/程序来计算例程的时间,并像这样使用它会很有用:

// < prepare for calcuation of code
...
ExecuteSomeCode; // < code to test
...
// < stop calcuating code and return time it took to process

我期待听到一些建议。

谢谢。

克雷格。

【问题讨论】:

    标签: performance delphi optimization gettickcount


    【解决方案1】:

    据我所知,最准确的方法是使用QueryPerformanceFrequency

    代码:

    var
      Freq, StartCount, StopCount: Int64;
      TimingSeconds: real;
    begin
      QueryPerformanceFrequency(Freq);
      QueryPerformanceCounter(StartCount);
      // Execute process that you want to time: ...
      QueryPerformanceCounter(StopCount);
      TimingSeconds := (StopCount - StartCount) / Freq;
      // Display timing: ... 
    end; 
    

    【讨论】:

    • 除以 1000 将秒转换为毫秒
    • @David:你的意思是乘法,不是吗?
    • @Ken,@David,在划分之前划分分隔符可能是一个更好的主意!即Freq := Freq div 1000TimingMilliseconds := (Stop - Start) / Freq之前。
    • 天哪,我怎么没想到这样做!
    • @RBA,根据support.microsoft.com/kb/172338,使用 QueryPerformanceCount 时的开销为每次调用 19 微秒。如果您使用 RDTSC,则开销是 CPU 的几个周期。在较短的例程中,QueryPerformanceCounter 的开销可能远大于代码的总运行时间。
    【解决方案2】:

    试试 Eric Grange 的Sampling Profiler

    【讨论】:

    • +1 我总是更喜欢使用分析器而不是滚动我自己的例程。但是,一旦您完成优化以进行现实检查,请尝试使用您的手表进行测量!
    • 我读了简介。我无法从措辞中判断它是否 1)对调用堆栈进行采样,2)按行报告包含该行的样本的百分比,3)在挂钟时间进行采样。你知道有没有?
    • 要精确测量单个例程,采样分析器可能不是最佳选择,最好是在您必须了解较大代码的速度慢的地方然后向下钻取。当涉及到相对较小的代码片段时,经典分析器通常会返回更精确的信息。
    • @Mike:我不知道它在内部是如何工作的,我什至不确定我是否理解你在问什么,但我认为它会定期检查当前正在执行的代码并保持命中计算每个调用的函数/方法。它还分析调用堆栈并保留所有调用者的统计信息。
    • @ldsandon 你是对的,如果是关于精确测量,我的回答没有帮助。分析器更擅长向您显示哪些代码经常执行,因此改进它会产生更大的影响。
    【解决方案3】:

    从 Delphi 6 开始,您可以使用 x86 时间戳计数器。
    这会计算 CPU 周期,在 1 Ghz 处理器上,每个计数需要一纳秒。
    没有比这更准确的了。

    function RDTSC: Int64; assembler;
    asm
      // RDTSC can be executed out of order, so the pipeline needs to be flushed
      // to prevent RDTSC from executing before your code is finished.  
      // Flush the pipeline
      XOR eax, eax
      PUSH EBX
      CPUID
      POP EBX
      RDTSC  //Get the CPU's time stamp counter.
    end;
    

    在 x64 上,以下代码更准确,因为它不受 CPUID 延迟的影响。

      rdtscp        // On x64 we can use the serializing version of RDTSC
      push rbx      // Serialize the code after, to avoid OoO sneaking in
      push rax      // subsequent instructions prior to executing RDTSCP.
      push rdx      // See: http://www.intel.de/content/dam/www/public/us/en/documents/white-papers/ia-32-ia-64-benchmark-code-execution-paper.pdf
      xor eax,eax
      cpuid
      pop rdx
      pop rax
      pop rbx
      shl rdx,32
      or rax,rdx
    

    使用上述代码获取执行代码前后的时间戳。
    最准确的方法,简单易行。

    请注意,您需要至少运行 10 次测试才能获得好的结果,第一次通过时缓存会变冷,随机硬盘读取和中断可能会影响您的计时。
    因为这个东西非常准确,如果你只计算第一次运行的时间,它可能会给你错误的想法。

    为什么不应该使用 QueryPerformanceCounter()
    QueryPerformanceCounter() 如果 CPU 速度变慢,会给出相同的 时间,它可以补偿 CPU 限制。如果您的 CPU 由于过热或其他原因而减速,RDTSC 将为您提供相同数量的周期。
    因此,如果您的 CPU 开始过热并需要减速,QueryPerformanceCounter() 会说您的例程需要更多时间(这是误导),RDTSC 会说它需要相同数量的周期 (这是准确的)。
    这是您想要的,因为您对代码使用的 CPU 周期数量感兴趣,而不是挂钟时间。

    来自最新的英特尔文档:http://software.intel.com/en-us/articles/measure-code-sections-using-the-enhanced-timer/?wapkw=%28rdtsc%29

    使用处理器时钟

    这个计时器非常准确。在具有 3GHz 处理器的系统上,此计时器可以测量持续时间少于 1 纳秒的事件。 [...] 如果在目标代码运行时频率发生变化,则最终读数将是多余的,因为初始读数和最终读数不是使用相同的时钟频率获取的。 在此期间发生的时钟滴答数将是准确的,但经过的时间将是未知的。

    何时不使用 RDTSC
    RDTSC 对于基本时序很有用。如果您在单 CPU 机器上为多线程代码计时,RDTSC 可以正常工作。如果您有多个 CPU,则 startcount 可能来自一个 CPU,而 endcount 来自另一个 CPU。
    所以不要使用 RDTSC 在多 CPU 机器上对多线程代码进行计时。在单 CPU 机器上运行良好,或者在多 CPU 机器上运行单线程代码也很好。
    还要记住,RDTSC 计算 CPU 周期。如果有一些需要时间但不使用 CPU 的东西,比如磁盘 IO 或网络,那么 RDTSC 就不是一个好工具。

    但是文档说 RDTSC 在现代 CPU 上并不准确
    RDTSC 不是跟踪时间的工具,它是跟踪 CPU 周期的工具。
    为此,它是唯一准确的工具。跟踪时间的例程在现代 CPU 上并不准确,因为 CPU 时钟不像以前那样是绝对的。

    【讨论】:

    • RDTSC 不能在具有现代 CPU 的 Windows 上使用。事实上,它的结果取决于当前的 CPU 状态:现代 CPU 可以改变它的频率(例如 TurboBoost 技术),而多核设计使得获得准确结果更加困难,因此不使用 rdtsc。 QueryPerformanceCounter() 将被使用。见msdn.microsoft.com/en-us/library/ee417693%28VS.85%29.aspx
    • @Bouchez,这正是您想要使用 RDTSC 的原因!如果您的处理器速度变慢,RDTSC 仍会以周期为单位提供正确的运行时间。至于多线程问题,标准 Delphi 代码是单线程的,因此该问题仅适用于您使用多线程的情况。当 CPU 变慢时,QueryPerformanceCounter()没有给出准确的结果。
    • @Johan 今天到底谁拥有单核开发 PC?仅使用 RDTSC 将线程关联设置为 ONE CPU 运行应用程序有什么好处? QueryPerformanceCounter 开销不是问题,因为您将计算时间戳的差异。而且您引用的文档不是“最新”文档。页面顶部的日期是页面布局刷新的日期。这份英特尔文档涉及 Pentium II,并引用了 2003 年的 SpeedStep 技术!!!!!!
    • 如果你使用 CPUID,你必须在执行之前 PUSH EBX 和之后 POP EBX。这是因为 CPUID(也)修改了 EBX 寄存器,但 Delphi 希望 ASM 语句/例程保留 EBX。
    • @A.Bouchez - 您可以将执行线程的亲和性设置为一个逻辑处理器,然后确保使用此方法从时间戳计数器获得可比较的读数。
    【解决方案4】:

    您没有指定您的 Delphi 版本,但 Delphi XE 在单元诊断中声明了一个 TStopWatch。这将使您能够以合理的精度测量运行时间。

    uses
      Diagnostics;
    var
      sw: TStopWatch;
    begin
      sw := TStopWatch.StartNew;
      <dosomething>
      Writeln(Format('runtime: %d ms', [sw.ElapsedMilliseconds]));
    end;
    

    【讨论】:

    • 我没有Delphi XE,但我相信和这里delphi.about.com/od/windowsshellapi/a/…是同一类
    • 在 XE 中,我会使用 AQ Time 进行分析,以准确了解性能在哪里很重要。
    • @RBA,XE 实现是基于记录而不是类,但其他的东西看起来很相似。
    • TStopWatch 中的“T”向我建议这是一门课。很高兴知道如何在 XE 中实现。
    • 为什么? 'T' 的标准 Delphi 用法是表示“类型”,如“这是一种数据类型”。几乎所有类型都以 T 开头,但其中许多不是类。请参阅 TRect 和 TDateTime 仅举几例。
    【解决方案5】:

    我问是因为我目前正在尝试 优化一些功能

    人们很自然地认为衡量是找出要优化什么的方法,但有更好的方法。

    如果某件事需要花费足够多的时间 (F) 以值得优化,那么如果你只是随机暂停它,F 就是你在行动中抓住它的概率。 这样做几次,你就会清楚地看到它为什么这样做,直到确切的代码行。

    More on that. Here's an example.

    修好它,然后做一个整体测量,看看你节省了多少,应该是 F 左右。 冲洗并重复。

    【讨论】:

    • +1 以获得出色的洞察力。但请注意不要优化空闲循环:-)。
    • @Johan:感谢您的提醒。我会小心的:-)
    【解决方案6】:

    以下是我为检查函数的持续时间所做的一些程序。我把它们放在我称为uTesting 的单元中,然后在测试期间将它们放入uses 子句中。

    声明

      Procedure TST_StartTiming(Index : Integer = 1);
        //Starts the timer by storing now in Time
        //Index is the index of the timer to use. 100 are available
    
      Procedure TST_StopTiming(Index : Integer = 1;Display : Boolean = True; DisplaySM : Boolean = False);
        //Stops the timer and stores the difference between time and now into time
        //Displays the result if Display is true
        //Index is the index of the timer to use. 100 are available
    
      Procedure TST_ShowTime(Index : Integer = 1;Detail : Boolean = True; DisplaySM : Boolean = False);
        //In a ShowMessage displays time
        //Uses DateTimeToStr if Detail is false else it breaks it down (H,M,S,MS)
        //Index is the index of the timer to use. 100 are available
    

    声明的变量

    var
      Time : array[1..100] of TDateTime;
    

    实施

      Procedure TST_StartTiming(Index : Integer = 1);
      begin
        Time[Index] := Now;
      end; 
    
      Procedure TST_StopTiming(Index : Integer = 1;Display : Boolean = True; DisplaySM : Boolean = False);
      begin
        Time[Index] := Now - Time[Index];
        if Display then TST_ShowTime;
      end;
    
      Procedure TST_ShowTime(Index : Integer = 1;Detail : Boolean = True; DisplaySM : Boolean = False);
      var
        H,M,S,MS : Word;
      begin
        if Detail then
          begin
            DecodeTime(Time[Index],H,M,S,MS);
            if DisplaySM then
            ShowMessage('Hour   =   ' + FloatToStr(H)  + #13#10 +
                        'Min     =   ' + FloatToStr(M)  + #13#10 +
                        'Sec      =   ' + FloatToStr(S)  + #13#10 +
                        'MS      =   ' + FloatToStr(MS) + #13#10)
            else
            OutputDebugString(PChar('Hour   =   ' + FloatToStr(H)  + #13#10 +
                        'Min     =   ' + FloatToStr(M)  + #13#10 +
                        'Sec      =   ' + FloatToStr(S)  + #13#10 +
                        'MS      =   ' + FloatToStr(MS) + #13#10));
          end
        else
          ShowMessage(TimeToStr(Time[Index]));
          OutputDebugString(Pchar(TimeToStr(Time[Index])));
      end;
    

    【讨论】:

      【解决方案7】:
      【解决方案8】:

      clock_gettime()是高解,精确到纳秒,你也可以用rtdsc,精确到CPU周期,最后你可以直接用gettimeofday()

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-04-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多