为什么 QueryperformanceCounter 的计时与挂钟不同?

发布于 2024-11-16 12:24:50 字数 2295 浏览 4 评论 0原文

您好,我正在使用 QueryperformanceCounter 对 Delphi 中的代码块进行计时。由于某种原因, 我使用 QueryPerformanceCounter 得到的毫秒数与使用秒表得到的挂钟时间有很大不同。例如,秒表给我大约 33 秒,如果不准确的话,这似乎是正确的,但使用 QueryPerofomanceCounter 会给我一个像 500 毫秒这样的数字。

当单步执行我的代码时,我可以看到 QueryPerformanceFrequency 为我的 CPU 提供了正确的 CPU 频率,Core2 E6600 为 2.4G。因此,如果刻度数正确,(tick number / Freq) * 1000 应该为我正在计时的代码提供正确的执行时间,但为什么不呢?

我知道对于我尝试计时的代码,QeuryPerformanceCounter 可能有点过分了,因为它花费了几秒钟而不是百万秒,但我更感兴趣的是了解挂钟和 QueryPerormanceCounter 之间的时间差异的原因。

我的硬件是 E6600 Core2,操作系统是 Windows 7 X64(如果相关)。

unit PerformanceTimer;

interface

uses Windows, SysUtils, DateUtils;

type TPerformanceTimer = class
  private
    fFrequency : TLargeInteger;
    fIsRunning: boolean;
    fIsHighResolution: boolean;
    fStartCount, FstopCount : TLargeInteger;
    procedure SetTickStamp(var lInt : TLargeInteger) ;
    function GetElapsedTicks: TLargeInteger;
    function GetElapsedMiliseconds: TLargeInteger;
  public
    constructor Create(const startOnCreate : boolean = false) ;
    procedure Start;
    procedure Stop;
    property IsHighResolution : boolean read fIsHighResolution;
    property ElapsedTicks : TLargeInteger read GetElapsedTicks;
    property ElapsedMiliseconds : TLargeInteger read GetElapsedMiliseconds;
    property IsRunning : boolean read fIsRunning;
end;

implementation

constructor TPerformanceTimer.Create(const startOnCreate : boolean = false) ;
begin
  inherited Create;

  fIsRunning := false;

  fIsHighResolution := QueryPerformanceFrequency(fFrequency) ;
  if NOT fIsHighResolution then
    fFrequency := MSecsPerSec;

  if startOnCreate then
    Start;
end;

function TPerformanceTimer.GetElapsedTicks: TLargeInteger;
begin
  result := fStopCount - fStartCount;
end;

procedure TPerformanceTimer.SetTickStamp(var lInt : TLargeInteger) ;
begin
  if fIsHighResolution then
    QueryPerformanceCounter(lInt)
  else
    lInt := MilliSecondOf(Now) ;
end;

function TPerformanceTimer.GetElapsedMiliseconds: TLargeInteger;
begin
  result := (MSecsPerSec * (fStopCount - fStartCount)) div fFrequency;
end;

procedure TPerformanceTimer.Start;
begin
  SetTickStamp(fStartCount) ;
  fIsRunning := true;
end;

procedure TPerformanceTimer.Stop;
begin
  SetTickStamp(fStopCount) ;
  fIsRunning := false;
end;

end.

Hi I am using QueryperformanceCounter to time a block of code in Delphi. For some reason, the
Millisecond number I got by using QueryPerformanceCounter is quite different from my wall clock time by using a stopwatch. For example The stopwatch give me about 33 seconds, which seems right if not accuracy, but using QueryPerofomanceCounter will give me a number like 500 Milliseconds.

When step though my code, I can see that QueryPerformanceFrequencygives me correct CPU frequency for my CPU, 2.4G for Core2 E6600. So if the tick number is correct, (tick number / Freq) * 1000 should give me correct execution time for the code I am timing, but why not?

I know that for the code I am trying to timing, QeuryPerformanceCounter is probably over-killing as it took seconds rather than MillionSeconds, but I am more interested in understanding the reason for the time difference between wall clock and QueryPerormanceCounter.

My Hardware is E6600 Core2 and OS is Windows 7 X64 if it is relevant.

unit PerformanceTimer;

interface

uses Windows, SysUtils, DateUtils;

type TPerformanceTimer = class
  private
    fFrequency : TLargeInteger;
    fIsRunning: boolean;
    fIsHighResolution: boolean;
    fStartCount, FstopCount : TLargeInteger;
    procedure SetTickStamp(var lInt : TLargeInteger) ;
    function GetElapsedTicks: TLargeInteger;
    function GetElapsedMiliseconds: TLargeInteger;
  public
    constructor Create(const startOnCreate : boolean = false) ;
    procedure Start;
    procedure Stop;
    property IsHighResolution : boolean read fIsHighResolution;
    property ElapsedTicks : TLargeInteger read GetElapsedTicks;
    property ElapsedMiliseconds : TLargeInteger read GetElapsedMiliseconds;
    property IsRunning : boolean read fIsRunning;
end;

implementation

constructor TPerformanceTimer.Create(const startOnCreate : boolean = false) ;
begin
  inherited Create;

  fIsRunning := false;

  fIsHighResolution := QueryPerformanceFrequency(fFrequency) ;
  if NOT fIsHighResolution then
    fFrequency := MSecsPerSec;

  if startOnCreate then
    Start;
end;

function TPerformanceTimer.GetElapsedTicks: TLargeInteger;
begin
  result := fStopCount - fStartCount;
end;

procedure TPerformanceTimer.SetTickStamp(var lInt : TLargeInteger) ;
begin
  if fIsHighResolution then
    QueryPerformanceCounter(lInt)
  else
    lInt := MilliSecondOf(Now) ;
end;

function TPerformanceTimer.GetElapsedMiliseconds: TLargeInteger;
begin
  result := (MSecsPerSec * (fStopCount - fStartCount)) div fFrequency;
end;

procedure TPerformanceTimer.Start;
begin
  SetTickStamp(fStartCount) ;
  fIsRunning := true;
end;

procedure TPerformanceTimer.Stop;
begin
  SetTickStamp(fStopCount) ;
  fIsRunning := false;
end;

end.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

临走之时 2024-11-23 12:24:50

这段代码只适合我,也许你可以尝试一下:

  var
    ifrequency, icount1, icount2: Int64;
    fmsec: Double;
  begin
    QueryPerformanceFrequency(ifrequency);
    QueryPerformanceCounter(icount1);
    Sleep(500);
    QueryPerformanceCounter(icount2);
    fmsec := 1000 * ((icount2 - icount1) / ifrequency);
  end;

fmsec 大约是 499.6 或类似的东西。

注意:对于小数字,不要依赖 Now 或 TickCount:它们的间隔约为 10 毫秒(取决于 Windows 版本)!因此,如果您使用 Now 和 DateUtils.MillisecondsBetween,“sleep(10)”的持续时间可以给出 0ms

注意 2:不要长时间依赖 QueryPerformanceCounter,因为它的时间可能会在一天中慢慢消失(每分钟大约 1ms 差异)

This code just works for me, maybe you can try it:

  var
    ifrequency, icount1, icount2: Int64;
    fmsec: Double;
  begin
    QueryPerformanceFrequency(ifrequency);
    QueryPerformanceCounter(icount1);
    Sleep(500);
    QueryPerformanceCounter(icount2);
    fmsec := 1000 * ((icount2 - icount1) / ifrequency);
  end;

fmsec is about 499.6 or something like that.

Note: Don't rely on Now or TickCount for small numbers: they have an interval of about 10ms (depending on Windows version)! So duration of "sleep(10)" can give 0ms if you use Now and DateUtils.MillisecondsBetween

Note 2: Don't rely on QueryPerformanceCounter for long durations, because it's time can slowly go away during a day (about 1ms diff per minute)

执妄 2024-11-23 12:24:50

如果您的硬件支持动态频率缩放,则意味着 QueryPerformanceFrequency 无法返回连续描述动态变化值的静态值。每当计算量大的事情开始时,调整 CPU 速度就会妨碍精确测量。

至少,我的笔记本电脑经历过这种情况 - 当它更改为更高的时钟频率时,基于 QueryPerformanceCounter 的测量变得混乱。

因此,无论提供的精度更高,我仍然在大多数情况下使用 GetTickCount 来实现此类目的(但基于 DateTime 的测量也可以,如前所述,除非可能发生时区切换),并带有一些“预热”代码开始消耗 CPU 功率的片段,因此当相关代码片段开始执行时,CPU 速度处于(恒定)最大值。

If your hardware supports dynamic frequency scaling, it implies that QueryPerformanceFrequency cannot return a static value continuously describing a dynamically changing one. Whenever something computationally aggressive starts, the adapting CPU speed will prevent exact measurements.

At least, it was experienced with my notebook - as it changed to the higher clock rate, QueryPerformanceCounter based measurements were messed up.

So, regardless of the higher accuracy offered, I still use GetTickCount most of the time for such purposes (but DateTime based measurements are also OK, as mentioned before, except if time zone switches may occur), with some "warm-up" code piece that starts eating up the CPU power so the CPU speed is at its (constant) maximum as the relevant code piece starts executing.

岁月如刀 2024-11-23 12:24:50

您应该发布一个代码片段来演示问题...但我会假设您犯了一个错误:

Milliseconds := 1000 * ((StopCount - StartCount) / Frequency);

如果您与秒表进行比较,您可能会采取更简单的路线,只需捕获之前和之后的 TDateTime (通过使用 < em>Now()),然后使用 DateUtils MilliSecondSpan() 方法计算差值:

var
  MyStartDate:TDateTime;
  MyStopDate:TDateTime;
  MyTiming:Double;
begin
  MyStartDate := Now();
  DoSomethingYouWantTimed();
  MyStopDate := Now();
  MyTiming := MilliSecondSpan(MyStopDate, MyStartDate);
  DoSomethingWithTiming(MyTiming);
end;

You should post a code snippet demonstrating the problem...but I would assume an error on your part:

Milliseconds := 1000 * ((StopCount - StartCount) / Frequency);

If you are comparing to a stop watch, you can likely take the easier route and just capture the TDateTime before and after (by using Now()) and then use the DateUtils MilliSecondSpan() method to calculate difference:

var
  MyStartDate:TDateTime;
  MyStopDate:TDateTime;
  MyTiming:Double;
begin
  MyStartDate := Now();
  DoSomethingYouWantTimed();
  MyStopDate := Now();
  MyTiming := MilliSecondSpan(MyStopDate, MyStartDate);
  DoSomethingWithTiming(MyTiming);
end;
夏日浅笑〃 2024-11-23 12:24:50

我使用 NTP 服务器定期同步 PC 时钟,在很长一段时间内同步 PC 时钟以调整 QueryPerformanceCounter“滴答”时间,并使用校准的 QueryPerformanceCounter 时间来进行精确的时间测量。在时钟漂移较低的良好服务器上,这意味着我在一段时间内的精度远小于一毫秒,并且所有机器的时钟时间同步在一两毫秒内。下面附上一些相关代码:

function NowInternal: TDateTime;
const
  // maximum time in seconds between synchronising the high-resolution clock
  MAX_SYNC_TIME = 10;
var
  lPerformanceCount: Int64;
  lResult: TDateTime;
  lDateTimeSynchronised: Boolean;
begin
  // check that the the high-resolution performance counter frequency has been
  // initialised
  fDateTimeCritSect.Enter;
  try
    if (fPerformanceFrequency < 0) and
       not QueryPerformanceFrequency(fPerformanceFrequency) then
      fPerformanceFrequency := 0;

    if fPerformanceFrequency > 0 then begin
      // get the return value from the the high-resolution performance counter
      if (fWindowsStartTime <> CSI_NULL_DATE_TIME) and
         QueryPerformanceCounter(lPerformanceCount) then
        lResult := fWindowsStartTime +
                   lPerformanceCount / fPerformanceFrequency / SecsPerDay
      else
        lResult := CSI_NULL_DATE_TIME;

      if (MilliSecondsBetween(lResult, Now) >= MAX_CLOCK_DIFF) or
         (SecondsBetween(Now, fLastSyncTime) >= MAX_SYNC_TIME) then begin
        // resynchronise the high-resolution clock due to clock differences or
        // at least every 10 seconds
        lDateTimeSynchronised := SyncDateTime;

        // get the return value from the the high-resolution performance counter
        if (fWindowsStartTime <> CSI_NULL_DATE_TIME) and
           QueryPerformanceCounter(lPerformanceCount) then
          lResult := fWindowsStartTime +
                     lPerformanceCount / fPerformanceFrequency / SecsPerDay;

      end else
        lDateTimeSynchronised := False;

      if MilliSecondsBetween(lResult, Now) >= (MAX_CLOCK_DIFF * 2) then
        // default the return value to the standard low-resolution value if
        // anything has gone wrong
        Result := Now
      else
        Result := lResult;

    end else begin
      lDateTimeSynchronised := False;

      // default the return value to the standard low-resolution value because
      // we cannot use the high-resolution clock
      Result := Now;
    end;
  finally
    fDateTimeCritSect.Leave;
  end;

  if lDateTimeSynchronised then
    CsiGlobals.AddLogMsg('High-resolution clock synchronised', CSI_LC_CLOCK);
end;

function SyncDateTime: Boolean;
var
  lPriorityClass: Cardinal;
  lThreadPriority: Integer;
  lInitTime: TDateTime;
  lNextTime: TDateTime;
  lPerformanceCount: Int64;
  lHighResCurrentTime: TDateTime;
  lLowResCurrentTime: TDateTime;
begin
  // synchronise the high-resolution date/time structure (boost the thread
  // priority as high as possible during synchronisation)
  lPriorityClass := CsiGetProcessPriorityClass;
  lThreadPriority := CsiGetCurrentThreadPriority;
  try
    CsiSetProcessPriorityClass(REALTIME_PRIORITY_CLASS);
    CsiSetCurrentThreadPriority(THREAD_PRIORITY_TIME_CRITICAL);

    // loop until the low-resolution date/time value changes (this will load the
    // CPU, but only for a maximum of around 15 milliseconds)
    lInitTime := Now;
    lNextTime := Now;
    while lNextTime = lInitTime do
      lNextTime := Now;

    // adjust the high-resolution performance counter frequency for clock drift
    if (fWindowsStartTime <> CSI_NULL_DATE_TIME) and
       QueryPerformanceCounter(lPerformanceCount) then begin
      lHighResCurrentTime := fWindowsStartTime +
                             lPerformanceCount / fPerformanceFrequency /
                             SecsPerDay;
      lLowResCurrentTime := Now;
      if MilliSecondsBetween(lHighResCurrentTime, lLowResCurrentTime) <
         (MAX_CLOCK_DIFF * 2) then
        fPerformanceFrequency := Round((1 +
                                       (lHighResCurrentTime -
                                        lLowResCurrentTime) /
                                       (lLowResCurrentTime - fLastSyncTime)) *
                                       fPerformanceFrequency);
    end;

    // save the Windows start time by extrapolating the high-resolution
    // performance counter value back to zero
    if QueryPerformanceCounter(lPerformanceCount) then begin
      fWindowsStartTime := lNextTime -
                           lPerformanceCount / fPerformanceFrequency /
                           SecsPerDay;
      fLastSyncTime := Now;
      Result := True;

    end else
      Result := False;
  finally
    CsiSetCurrentThreadPriority(lThreadPriority);
    CsiSetProcessPriorityClass(lPriorityClass);
  end;
end;

I use an NTP server to sync the PC clock periodically, the PC clock over a large amount of time to adjust the QueryPerformanceCounter "tick" time, and the calibrated QueryPerformanceCounter time for precise time measurements. On a good server where the clock drift is low it means that I have accuracy over time periods to much less than a millisecond and the clock times of all my machines synchronised to within a millsecond or two. Some of the relevant code is attached below:

function NowInternal: TDateTime;
const
  // maximum time in seconds between synchronising the high-resolution clock
  MAX_SYNC_TIME = 10;
var
  lPerformanceCount: Int64;
  lResult: TDateTime;
  lDateTimeSynchronised: Boolean;
begin
  // check that the the high-resolution performance counter frequency has been
  // initialised
  fDateTimeCritSect.Enter;
  try
    if (fPerformanceFrequency < 0) and
       not QueryPerformanceFrequency(fPerformanceFrequency) then
      fPerformanceFrequency := 0;

    if fPerformanceFrequency > 0 then begin
      // get the return value from the the high-resolution performance counter
      if (fWindowsStartTime <> CSI_NULL_DATE_TIME) and
         QueryPerformanceCounter(lPerformanceCount) then
        lResult := fWindowsStartTime +
                   lPerformanceCount / fPerformanceFrequency / SecsPerDay
      else
        lResult := CSI_NULL_DATE_TIME;

      if (MilliSecondsBetween(lResult, Now) >= MAX_CLOCK_DIFF) or
         (SecondsBetween(Now, fLastSyncTime) >= MAX_SYNC_TIME) then begin
        // resynchronise the high-resolution clock due to clock differences or
        // at least every 10 seconds
        lDateTimeSynchronised := SyncDateTime;

        // get the return value from the the high-resolution performance counter
        if (fWindowsStartTime <> CSI_NULL_DATE_TIME) and
           QueryPerformanceCounter(lPerformanceCount) then
          lResult := fWindowsStartTime +
                     lPerformanceCount / fPerformanceFrequency / SecsPerDay;

      end else
        lDateTimeSynchronised := False;

      if MilliSecondsBetween(lResult, Now) >= (MAX_CLOCK_DIFF * 2) then
        // default the return value to the standard low-resolution value if
        // anything has gone wrong
        Result := Now
      else
        Result := lResult;

    end else begin
      lDateTimeSynchronised := False;

      // default the return value to the standard low-resolution value because
      // we cannot use the high-resolution clock
      Result := Now;
    end;
  finally
    fDateTimeCritSect.Leave;
  end;

  if lDateTimeSynchronised then
    CsiGlobals.AddLogMsg('High-resolution clock synchronised', CSI_LC_CLOCK);
end;

function SyncDateTime: Boolean;
var
  lPriorityClass: Cardinal;
  lThreadPriority: Integer;
  lInitTime: TDateTime;
  lNextTime: TDateTime;
  lPerformanceCount: Int64;
  lHighResCurrentTime: TDateTime;
  lLowResCurrentTime: TDateTime;
begin
  // synchronise the high-resolution date/time structure (boost the thread
  // priority as high as possible during synchronisation)
  lPriorityClass := CsiGetProcessPriorityClass;
  lThreadPriority := CsiGetCurrentThreadPriority;
  try
    CsiSetProcessPriorityClass(REALTIME_PRIORITY_CLASS);
    CsiSetCurrentThreadPriority(THREAD_PRIORITY_TIME_CRITICAL);

    // loop until the low-resolution date/time value changes (this will load the
    // CPU, but only for a maximum of around 15 milliseconds)
    lInitTime := Now;
    lNextTime := Now;
    while lNextTime = lInitTime do
      lNextTime := Now;

    // adjust the high-resolution performance counter frequency for clock drift
    if (fWindowsStartTime <> CSI_NULL_DATE_TIME) and
       QueryPerformanceCounter(lPerformanceCount) then begin
      lHighResCurrentTime := fWindowsStartTime +
                             lPerformanceCount / fPerformanceFrequency /
                             SecsPerDay;
      lLowResCurrentTime := Now;
      if MilliSecondsBetween(lHighResCurrentTime, lLowResCurrentTime) <
         (MAX_CLOCK_DIFF * 2) then
        fPerformanceFrequency := Round((1 +
                                       (lHighResCurrentTime -
                                        lLowResCurrentTime) /
                                       (lLowResCurrentTime - fLastSyncTime)) *
                                       fPerformanceFrequency);
    end;

    // save the Windows start time by extrapolating the high-resolution
    // performance counter value back to zero
    if QueryPerformanceCounter(lPerformanceCount) then begin
      fWindowsStartTime := lNextTime -
                           lPerformanceCount / fPerformanceFrequency /
                           SecsPerDay;
      fLastSyncTime := Now;
      Result := True;

    end else
      Result := False;
  finally
    CsiSetCurrentThreadPriority(lThreadPriority);
    CsiSetProcessPriorityClass(lPriorityClass);
  end;
end;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文