2011-06-21 33 views
5

您好我正在使用QueryperformanceCounter計時在Delphi的代碼塊。出於某種原因,使用QueryPerformanceCounter獲得的 毫秒數與通過使用秒錶的掛鐘時間完全不同。例如,秒錶給我大約33秒,這看起來不錯,如果不準確,但使用QueryPerofomanceCounter會給我一個數字,如500毫秒。爲什麼QueryperformanceCounter的時間與掛鐘不同?

當通過我的代碼步驟時,我可以看到QueryPerformanceFrequency爲我的CPU提供了正確的CPU頻率,爲Core2 E6600提供了2.4G的CPU頻率。所以如果打勾號是正確的,(tick number/Freq) * 1000應該給我正確的代碼執行時間,我爲什麼不是?

我知道對於我試圖計時的代碼來說,QeuryPerformanceCounter可能是過度查殺,因爲它花費了幾秒而不是百萬秒,但我更感興趣的是瞭解掛鐘和QueryPerormanceCounter之間時差的原因。

我的硬件是E6600 Core2,OS是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. 
+0

我猜,顯示的代碼也將有所幫助有通過(1000錯乘)10^3^2的幅度差 –

+0

只是做了一個小測試,一個單一的算術加操作甚至不會注意到的一個刻度一半時間。一個包含50個添加操作的循環將生成1或2個滴答。那麼使用QueryPerformanceCounter有什麼問題。 –

+0

@pstar:50個添加操作並不是很多。 'QueryPerformanceCounter'確實沒有錯。請顯示您實際測量的代碼。 – jpfollenius

回答

2

你應該張貼的代碼片段展示了問題......但我對您的部分承擔錯誤:

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

如果你比較秒錶,你可以很可能採取省事,只是捕捉到TDateTime類型之前和之後(通過使用現在()),然後使用DateUtils MilliSecondSpan()方法來計算差異:

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

您的假設是正確的!我不得不承認,我測量的代碼塊不完整。總之,還有其他部分代碼導致了我沒有想到的長時間延遲,並且使用Now()我使用QueryPerformanceCounter獲得了完全相同的時間量。你贏得了我的選票,因爲在我個人的情況下我的硬件工作正常,我的代碼測量時間正在工作,但我沒有測量正確的代碼路徑。 QueryPerformanceCounter無法正常工作的原因很多,不應超過我的案例規模:半秒與大約30秒。 –

4

此代碼只是對我的作品,也許你可以試試:

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的小數字:它們的間隔約爲10ms(取決於Windows版本)!因此,如果使用Now和DateUtils.MillisecondsBetween,則「睡眠(10)」的持續時間可以爲0ms

注2:不要長時間依賴QueryPerformanceCounter,因爲時間可能會在一天中緩慢消失(大約1ms diff每分鐘)

+0

這兩個問題已通過我在 – Misha

+1

以下的方式得到解決。最好的解決方案是確定QueryPerformanceCounter本身是否會導致我要說的問題。但正如我在我的評論中指出的那樣,QueryPerformanceFrequency沒有返回正確的頻率數字。所以你的代碼會正常工作,就像我測試的一樣。有趣的是,你的代碼總是會返回非常接近499.6的數字,並且它也發生在我的機器上。你知道原因嗎? –

0

我使用NTP服務器週期性地同步PC時鐘,PC時鐘在大量的時間內調整QueryPerformanceCounter「tick」時間以及校準的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; 
+0

我不確定這個答案是如何解決爲什麼Pstar的計時器在秒錶測量半分鐘時測量半秒的問題。 –

+0

與我的問題無關,似乎是一個很好的解決普通計時器問題的方法,我喜歡用PC時鐘調整QueryPerformanceFrequency的想法。但是我猜如果頻率沒有改變,精度將會小於QueryPerformanceCounter? –

3

如果你的硬件支持動態頻率調節,這意味着QueryPerformanceFrequency的不能返回一個靜態值連續描述一個動態變化的一個。無論什麼時候計算攻擊開始,適配的CPU速度都會阻止精確的測量。

至少,它在我的筆記本上經歷過 - 隨着它變成更高的時鐘頻率,基於QueryPerformanceCounter的測量結果被搞砸了。因此,無論提供的更高的精確度如何,我仍然在大多數時間使用GetTickCount來達到此目的(但如前所述,基於DateTime的測量也是可以的,除非時間區域切換可能發生),有些「溫暖「代碼片斷開始消耗CPU電源,因此CPU速度在相關代碼片開始執行時處於其(恆定)最大值。

+1

有些芯片組即使在某些臺式機上禁用CPU速度自適應功能時也不會返回準確或甚至線性的時序信息。即使 –

+1

@brezinczky所有現代的CPU都支持動態頻率調整,至少我知道Pentium 4以上支持,當然這取決於BIOS或用戶設置來啓用/禁用它。但是,我的結論仍然是,除非使用動態改變的QueryPerformanceFrequency,否則使用帶有靜態值的QueryPerformanceCounter進行計時將不會在大多數情況下工作,使其無用。但是,使用計數器本身作爲分析器的原因仍然有用。 –

相關問題