2011-06-21 7 views
5

こんにちは私は、Delphiのコードブロックを実行するためにQueryperformanceCounterを使用しています。何らかの理由で、私がQueryPerformanceCounterを使用して取得したのミリ秒数は、ストップウオッチを使用して私の壁時計の時間と大きく異なります。例えば、ストップウォッチは約33秒ですが、正確ではないと思われますが、QueryPerofomanceCounterを使用すると500ミリ秒のような数値が得られます。なぜQueryperformanceCounterはウォールクロックと異なるタイミングですか?

私のコードをステップ実行すると、CPUのCPU周波数が正しいか、Core2 E6600の場合は、QueryPerformanceFrequencygivesが正しいことがわかります。だから、ティックナンバーが正しければ、(tick number/Freq) * 1000は私にタイミングのコードの正しい実行時間を与えるはずですが、どうしてですか?

QeuryPerformanceCounterは、MillionSecondsではなく数秒かかるため、おそらく時間がかかりすぎていることを知っていますが、壁時計と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

10^3^2(1000で間違った乗算)の大きさの差があるので、コードを表示すると助かります –

+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の間に0msを与えることができます

注2:1日のうちにゆっくりと時間が離れることがあるため、長時間にわたってQueryPerformanceCounterに頼らないでください(約1ms毎分差分)

+0

両方の問題は、私が以下のことを行う方法で解決されました – Misha

+1

QueryPerformanceCounter自体が私が言う問題を引き起こしたかどうかを判断する最良のソリューションです。しかし、私のコメントでは、QueryPerformanceFrequencyが正しい周波数番号を返すと述べました。だから私のテストでは、あなたのコードは正常に動作します。あなたのコードでは、平均して499.6に近い数字が常に返ってくるというのは面白いことです。私のマシンでも同じことが起こりました。理由を知っていますか? –

0

私は、定期的にQueryPerformanceCounterの「目盛り」の時間を調整するために多くの時間、および正確な時間測定のためのキャリブレーションのQueryPerformanceCounterの時間をかけてPCのクロックをPCのクロックを同期するNTPサーバを使用しています。クロックドリフトが低い良いサーバーでは、時間の経過とともにミリ秒よりもずっと低い精度と、すべてのマシンのクロック時間が1ミリ秒または2ミリ秒以内に同期していることを意味します。関連するコードの一部は、以下に添付されています

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ベースの測定もOKです)対応するコードピースが実行を開始するときにCPU速度がその(一定の)最大値になるようにCPUパワーを摂取し始める「アップアップ」コードピース。

+1

一部のデスクトップであっても、CPU速度適応機能が無効になっていても、正確なタイミング情報やリニアタイミング情報を返さないチップセットがあります。 even –

+1

@brezinczky最新のCPUはすべて、動的周波数スケーリングをサポートします。少なくとも、Pentium 4以降はサポートしています。もちろん、BIOSまたはユーザ設定によって有効/無効になります。しかし、私の結論は、動的に変化するQueryPerformanceFrequencyを使用しない限り、タイミングのために静的な値を持つQueryPerformanceCounterを使用することはほとんどの場合有効にならず、役に立たなくなることです。しかし、プロファイラーのためにカウンター自体を使用する理由はまだまだ有用です。 –