Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why QueryperformanceCounter timed different from wall clock?

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.
like image 771
Paul L Avatar asked Jun 21 '11 03:06

Paul L


3 Answers

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)

like image 70
André Avatar answered Sep 28 '22 05:09

André


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.

like image 24
brezniczky Avatar answered Sep 28 '22 03:09

brezniczky


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;
like image 40
Darian Miller Avatar answered Sep 28 '22 03:09

Darian Miller