Почему QueryperformanceCounter отличается по времени от настенных часов? - PullRequest
5 голосов
/ 21 июня 2011

Привет! Я использую QueryperformanceCounter для определения времени блока кода в Delphi. По какой-то причине Миллисекундный номер, который я получил с помощью QueryPerformanceCounter, довольно сильно отличается от моего времени на настенных часах с помощью секундомера. Например, секундомер дает мне около 33 секунд, что кажется правильным, если не точностью, но использование QueryPerofomanceCounter даст мне число, например, 500 миллисекунд.

При выполнении моего кода я вижу, что QueryPerformanceFrequency дает мне правильную частоту ЦП для моего ЦП, 2,4 Г для Core2 E6600. Так что, если номер галочки правильный, (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.

Ответы [ 4 ]

4 голосов
/ 21 июня 2011

Этот код работает только у меня, может быть, вы можете попробовать это:

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

Фмс около 499,6 или что-то в этом роде.

Примечание: не полагайтесь на Now или TickCount для небольших чисел: они имеют интервал около 10 мс (в зависимости от версии Windows)! Таким образом, продолжительность «sleep (10)» может дать 0 мс, если вы используете Now и DateUtils.MillisecondsBetween

Примечание 2. Не полагайтесь на QueryPerformanceCounter в течение длительного времени, потому что это время может медленно уходить в течение дня (около 1 мс в минуту)

3 голосов
/ 21 июня 2011

Если ваше оборудование поддерживает динамическое масштабирование частоты, это означает, что QueryPerformanceFrequency не может возвращать статическое значение, непрерывно описывающее динамически изменяющееся. Всякий раз, когда начинается что-то агрессивное в вычислительном отношении, адаптация скорости процессора будет препятствовать точным измерениям.

По крайней мере, это было с моим ноутбуком - при переходе на более высокую тактовую частоту измерения на основе QueryPerformanceCounter были испорчены.

Таким образом, независимо от предлагаемой более высокой точности, я по-прежнему большую часть времени использую GetTickCount для таких целей (но измерения на основе DateTime также хороши, как упоминалось ранее, за исключением случаев, когда могут произойти переключения часового пояса), с некоторыми часть кода, которая начинает потреблять мощность процессора, так что скорость процессора достигает своего (постоянного) максимума, когда начинает выполняться соответствующая часть кода.

2 голосов
/ 21 июня 2011

Вы должны опубликовать фрагмент кода, демонстрирующий проблему ... но я бы предположил ошибку с вашей стороны:

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

Если вы сравниваете с секундомером, вы, вероятно, можете выбрать более простой маршрути просто перехватите TDateTime до и после (используя Now () ), а затем используйте метод DateUtils MilliSecondSpan () для вычисления разницы:

var
  MyStartDate:TDateTime;
  MyStopDate:TDateTime;
  MyTiming:Double;
begin
  MyStartDate := Now();
  DoSomethingYouWantTimed();
  MyStopDate := Now();
  MyTiming := MilliSecondSpan(MyStopDate, MyStartDate);
  DoSomethingWithTiming(MyTiming);
end;
0 голосов
/ 21 июня 2011

Я использую NTP-сервер для периодической синхронизации часов ПК, часов ПК в течение большого количества времени для настройки времени «тика» 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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...